251205:0000 Just start debug backend/frontend
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class InitialSchema1701234567890 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Organizations
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE organizations (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
organization_code VARCHAR(20) NOT NULL UNIQUE,
|
||||
organization_name VARCHAR(200) NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP NULL,
|
||||
INDEX idx_org_code (organization_code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
|
||||
// Permissions
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE permissions (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
permission_code VARCHAR(50) NOT NULL UNIQUE,
|
||||
description TEXT NULL,
|
||||
resource VARCHAR(50) NOT NULL,
|
||||
action VARCHAR(50) NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
|
||||
// Roles
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE roles (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
role_name VARCHAR(50) NOT NULL UNIQUE,
|
||||
description TEXT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
|
||||
// Role Permissions
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE role_permissions (
|
||||
role_id INT NOT NULL,
|
||||
permission_id INT NOT NULL,
|
||||
PRIMARY KEY (role_id, permission_id),
|
||||
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
|
||||
// Users
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE users (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
email VARCHAR(100) NOT NULL UNIQUE,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
first_name VARCHAR(100) NOT NULL,
|
||||
last_name VARCHAR(100) NOT NULL,
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
deleted_at TIMESTAMP NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
|
||||
// User Roles
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE user_roles (
|
||||
user_id INT NOT NULL,
|
||||
role_id INT NOT NULL,
|
||||
PRIMARY KEY (user_id, role_id),
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
|
||||
// Correspondences
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE correspondences (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
document_number VARCHAR(50) NOT NULL UNIQUE,
|
||||
subject VARCHAR(255) NOT NULL,
|
||||
body TEXT NULL,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
status VARCHAR(50) DEFAULT 'Draft',
|
||||
created_by_id INT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (created_by_id) REFERENCES users(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
|
||||
// RFAs
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE rfas (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
rfa_number VARCHAR(50) NOT NULL UNIQUE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
discipline_code VARCHAR(20) NULL,
|
||||
status VARCHAR(50) DEFAULT 'Draft',
|
||||
created_by_id INT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (created_by_id) REFERENCES users(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
|
||||
// Drawings
|
||||
await queryRunner.query(`
|
||||
CREATE TABLE drawings (
|
||||
id INT PRIMARY KEY AUTO_INCREMENT,
|
||||
drawing_number VARCHAR(50) NOT NULL UNIQUE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
drawing_type VARCHAR(50) NOT NULL,
|
||||
revision VARCHAR(10) NOT NULL,
|
||||
status VARCHAR(50) DEFAULT 'Draft',
|
||||
uploaded_by_id INT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (uploaded_by_id) REFERENCES users(id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS drawings`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS rfas`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS correspondences`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS user_roles`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS users`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS role_permissions`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS roles`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS permissions`);
|
||||
await queryRunner.query(`DROP TABLE IF EXISTS organizations`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { Organization } from '../../modules/organizations/entities/organization.entity';
|
||||
|
||||
export async function seedOrganizations(dataSource: DataSource) {
|
||||
const repo = dataSource.getRepository(Organization);
|
||||
|
||||
const orgs = [
|
||||
{ organizationCode: 'กทท.', organizationName: 'การท่าเรือแห่งประเทศไทย' },
|
||||
{
|
||||
organizationCode: 'สคฉ.3',
|
||||
organizationName: 'โครงการพัฒนาท่าเรือแหลมฉบัง ระยะที่ 3',
|
||||
},
|
||||
{
|
||||
organizationCode: 'สคฉ.3-01',
|
||||
organizationName: 'ตรวจรับพัสดุ ที่ปรึกษาควบคุมงาน',
|
||||
},
|
||||
{
|
||||
organizationCode: 'สคฉ.3-02',
|
||||
organizationName: 'ตรวจรับพัสดุ งานทางทะเล',
|
||||
},
|
||||
{
|
||||
organizationCode: 'สคฉ.3-03',
|
||||
organizationName: 'ตรวจรับพัสดุ อาคารและระบบสาธารณูปโภค',
|
||||
},
|
||||
{
|
||||
organizationCode: 'สคฉ.3-04',
|
||||
organizationName: 'ตรวจรับพัสดุ ตรวจสอบผลกระทบสิ่งแวดล้อม',
|
||||
},
|
||||
{
|
||||
organizationCode: 'สคฉ.3-05',
|
||||
organizationName: 'ตรวจรับพัสดุ เยียวยาการประมง',
|
||||
},
|
||||
{
|
||||
organizationCode: 'สคฉ.3-06',
|
||||
organizationName: 'ตรวจรับพัสดุ งานก่อสร้าง ส่วนที่ 3',
|
||||
},
|
||||
{
|
||||
organizationCode: 'สคฉ.3-07',
|
||||
organizationName: 'ตรวจรับพัสดุ งานก่อสร้าง ส่วนที่ 4',
|
||||
},
|
||||
{
|
||||
organizationCode: 'สคฉ.3-xx',
|
||||
organizationName: 'ตรวจรับพัสดุ ที่ปรึกษาออกแบบ ส่วนที่ 4',
|
||||
},
|
||||
{ organizationCode: 'TEAM', organizationName: 'Designer Consulting Ltd.' },
|
||||
{
|
||||
organizationCode: 'คคง.',
|
||||
organizationName: 'Construction Supervision Ltd.',
|
||||
},
|
||||
{ organizationCode: 'ผรม.1', organizationName: 'Contractor งานทางทะเล' },
|
||||
{ organizationCode: 'ผรม.2', organizationName: 'Contractor งานก่อสร้าง' },
|
||||
{
|
||||
organizationCode: 'ผรม.3',
|
||||
organizationName: 'Contractor งานก่อสร้าง ส่วนที่ 3',
|
||||
},
|
||||
{
|
||||
organizationCode: 'ผรม.4',
|
||||
organizationName: 'Contractor งานก่อสร้าง ส่วนที่ 4',
|
||||
},
|
||||
{ organizationCode: 'EN', organizationName: 'Third Party Environment' },
|
||||
{ organizationCode: 'CAR', organizationName: 'Third Party Fishery Care' },
|
||||
];
|
||||
|
||||
for (const org of orgs) {
|
||||
const exists = await repo.findOneBy({
|
||||
organizationCode: org.organizationCode,
|
||||
});
|
||||
if (!exists) {
|
||||
await repo.save(repo.create(org));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +1,24 @@
|
||||
import { config } from 'dotenv';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { seedWorkflowDefinitions } from '../seeds/workflow-definitions.seed'; // Import ฟังก์ชัน Seed ที่คุณมี
|
||||
// Import Entities ที่เกี่ยวข้อง
|
||||
import { WorkflowDefinition } from '../../modules/workflow-engine/entities/workflow-definition.entity';
|
||||
import { WorkflowHistory } from '../../modules/workflow-engine/entities/workflow-history.entity';
|
||||
import { WorkflowInstance } from '../../modules/workflow-engine/entities/workflow-instance.entity';
|
||||
import { databaseConfig } from '../../config/database.config';
|
||||
import { seedOrganizations } from './organization.seed';
|
||||
import { seedUsers } from './user.seed';
|
||||
|
||||
// โหลด Environment Variables (.env)
|
||||
config();
|
||||
|
||||
const runSeed = async () => {
|
||||
// ตั้งค่าการเชื่อมต่อฐานข้อมูล (ควรตรงกับ docker-compose หรือ .env ของคุณ)
|
||||
const dataSource = new DataSource({
|
||||
type: 'mariadb',
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: parseInt(process.env.DB_PORT || '3306'),
|
||||
username: process.env.DB_USERNAME || 'root',
|
||||
password: process.env.DB_PASSWORD || 'Center#2025',
|
||||
database: process.env.DB_DATABASE || 'lcbp3_dev',
|
||||
// สำคัญ: ต้องใส่ Entities ที่เกี่ยวข้องทั้งหมดเพื่อให้ TypeORM รู้จัก
|
||||
entities: [
|
||||
WorkflowDefinition,
|
||||
WorkflowInstance,
|
||||
WorkflowHistory,
|
||||
// ใส่ Entity อื่นๆ ถ้าจำเป็น หรือใช้ path pattern: __dirname + '/../../modules/**/*.entity{.ts,.js}'
|
||||
],
|
||||
synchronize: false, // ห้ามใช้ true บน Production
|
||||
});
|
||||
async function runSeeds() {
|
||||
const dataSource = new DataSource(databaseConfig as any);
|
||||
await dataSource.initialize();
|
||||
|
||||
try {
|
||||
console.log('🔌 Connecting to database...');
|
||||
await dataSource.initialize();
|
||||
console.log('✅ Database connected.');
|
||||
console.log('🌱 Seeding database...');
|
||||
|
||||
console.log('🌱 Running Seeds...');
|
||||
await seedWorkflowDefinitions(dataSource);
|
||||
console.log('✅ Seeding completed successfully.');
|
||||
await seedOrganizations(dataSource);
|
||||
await seedUsers(dataSource);
|
||||
|
||||
console.log('✅ Seeding completed!');
|
||||
} catch (error) {
|
||||
console.error('❌ Error during seeding:', error);
|
||||
console.error('❌ Seeding failed:', error);
|
||||
} finally {
|
||||
if (dataSource.isInitialized) {
|
||||
await dataSource.destroy();
|
||||
console.log('🔌 Database connection closed.');
|
||||
}
|
||||
await dataSource.destroy();
|
||||
}
|
||||
};
|
||||
|
||||
runSeed();
|
||||
|
||||
/*
|
||||
npx ts-node -r tsconfig-paths/register src/database/run-seed.ts
|
||||
|
||||
**หรือเพิ่มใน `package.json` (แนะนำ):**
|
||||
คุณสามารถเพิ่ม script ใน `package.json` เพื่อให้เรียกใช้ได้ง่ายขึ้นในอนาคต:
|
||||
|
||||
"scripts": {
|
||||
"seed": "ts-node -r tsconfig-paths/register src/database/seeds/run-seed.ts"
|
||||
}
|
||||
|
||||
http://googleusercontent.com/immersive_entry_chip/1
|
||||
|
||||
### 💡 ข้อควรระวัง
|
||||
1. **Environment Variables:** ตรวจสอบให้แน่ใจว่าค่า Config (Host, User, Password) ในไฟล์ `run-seed.ts` หรือ `.env` นั้นถูกต้องและตรงกับ Docker Container ที่กำลังรันอยู่
|
||||
2. **Entities:** หากฟังก์ชัน Seed มีการเรียกใช้ Entity อื่นนอกเหนือจาก `WorkflowDefinition` ต้องนำมาใส่ใน `entities: [...]` ของ `DataSource` ให้ครบ ไม่อย่างนั้นจะเจอ Error `RepositoryNotFoundError`
|
||||
|
||||
*/
|
||||
runSeeds();
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
import { User } from '../../modules/users/entities/user.entity';
|
||||
import { Role } from '../../modules/auth/entities/role.entity';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
|
||||
export async function seedUsers(dataSource: DataSource) {
|
||||
const userRepo = dataSource.getRepository(User);
|
||||
const roleRepo = dataSource.getRepository(Role);
|
||||
|
||||
// Create Roles
|
||||
const rolesData = [
|
||||
{
|
||||
roleName: 'Superadmin',
|
||||
description:
|
||||
'ผู้ดูแลระบบสูงสุด: สามารถทำทุกอย่างในระบบ, จัดการองค์กร, และจัดการข้อมูลหลักระดับ Global',
|
||||
},
|
||||
{
|
||||
roleName: 'Org Admin',
|
||||
description:
|
||||
'ผู้ดูแลองค์กร: จัดการผู้ใช้ในองค์กร, จัดการบทบาท / สิทธิ์ภายในองค์กร, และดูรายงานขององค์กร',
|
||||
},
|
||||
{
|
||||
roleName: 'Document Control',
|
||||
description:
|
||||
'ควบคุมเอกสารขององค์กร: เพิ่ม / แก้ไข / ลบเอกสาร, และกำหนดสิทธิ์เอกสารภายในองค์กร',
|
||||
},
|
||||
{
|
||||
roleName: 'Editor',
|
||||
description:
|
||||
'ผู้แก้ไขเอกสารขององค์กร: เพิ่ม / แก้ไขเอกสารที่ได้รับมอบหมาย',
|
||||
},
|
||||
{
|
||||
roleName: 'Viewer',
|
||||
description: 'ผู้ดูเอกสารขององค์กร: ดูเอกสารที่มีสิทธิ์เข้าถึงเท่านั้น',
|
||||
},
|
||||
{
|
||||
roleName: 'Project Manager',
|
||||
description:
|
||||
'ผู้จัดการโครงการ: จัดการสมาชิกในโครงการ, สร้าง / จัดการสัญญาในโครงการ, และดูรายงานโครงการ',
|
||||
},
|
||||
{
|
||||
roleName: 'Contract Admin',
|
||||
description:
|
||||
'ผู้ดูแลสัญญา: จัดการสมาชิกในสัญญา, สร้าง / จัดการข้อมูลหลักเฉพาะสัญญา, และอนุมัติเอกสารในสัญญา',
|
||||
},
|
||||
];
|
||||
|
||||
const roleMap = new Map();
|
||||
for (const r of rolesData) {
|
||||
let role = await roleRepo.findOneBy({ roleName: r.roleName });
|
||||
if (!role) {
|
||||
role = await roleRepo.save(roleRepo.create(r));
|
||||
}
|
||||
roleMap.set(r.roleName, role);
|
||||
}
|
||||
|
||||
// Create Users
|
||||
const usersData = [
|
||||
{
|
||||
username: 'superadmin',
|
||||
email: 'superadmin@example.com',
|
||||
firstName: 'Super',
|
||||
lastName: 'Admin',
|
||||
roleName: 'Superadmin',
|
||||
},
|
||||
{
|
||||
username: 'admin',
|
||||
email: 'admin@example.com',
|
||||
firstName: 'Admin',
|
||||
lastName: 'คคง.',
|
||||
roleName: 'Org Admin',
|
||||
},
|
||||
{
|
||||
username: 'editor01',
|
||||
email: 'editor01@example.com',
|
||||
firstName: 'DC',
|
||||
lastName: 'C1',
|
||||
roleName: 'Editor',
|
||||
},
|
||||
{
|
||||
username: 'viewer01',
|
||||
email: 'viewer01@example.com',
|
||||
firstName: 'Viewer',
|
||||
lastName: 'สคฉ.03',
|
||||
roleName: 'Viewer',
|
||||
},
|
||||
];
|
||||
|
||||
const salt = await bcrypt.genSalt();
|
||||
const passwordHash = await bcrypt.hash('password123', salt); // Default password
|
||||
|
||||
for (const u of usersData) {
|
||||
const exists = await userRepo.findOneBy({ username: u.username });
|
||||
if (!exists) {
|
||||
const user = userRepo.create({
|
||||
username: u.username,
|
||||
email: u.email,
|
||||
firstName: u.firstName,
|
||||
lastName: u.lastName,
|
||||
passwordHash,
|
||||
roles: [roleMap.get(u.roleName)],
|
||||
});
|
||||
await userRepo.save(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user