طراحی ساختار دیتابیس (ER-Diagram)

در ادامه یک مثال از بخش طراحی دیتابیس آورده شده است

Screenshot 2025-05-14 102836
  1. طراحی ساختار دیتابیس (ER-Diagram)
    - مشخص کردن موجودیت‌ها (Entities): User، Request و RequestStatus
    - تعریف روابط: هر User می‌تواند چند Request داشته باشد؛ هر Request چند RequestStatus
    - تعیین کلیدهای اصلی (PK) و خارجی (FK) در هر جدول
  2. تنظیم کانفیگ Sequelize برای PostgreSQL
    - نصب پکیج‌ها:
    npm install sequelize pg pg-hstore
    - ایجاد فایل کانفیگ src/configs/database.js:
    import { Sequelize } from 'sequelize';
    const sequelize = new Sequelize(
      process.env.DB_NAME,
      process.env.DB_USER,
      process.env.DB_PASS,
      {
        host: process.env.DB_HOST,
        dialect: 'postgres',
        logging: false,
      }
    );
    

    export default sequelize;

  3. ایجاد مدل User
    - تعریف فیلدهای پایه مثل userId، name، email و role
    import { DataTypes } from 'sequelize';
    import sequelize from '../configs/database.js';
    

    const User = sequelize.define('User', { userId: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true, }, name: { type: DataTypes.STRING(100), allowNull: false, }, email: { type: DataTypes.STRING(150), allowNull: false, unique: true, }, role: { type: DataTypes.ENUM('user', 'expert', 'admin'), defaultValue: 'user', }, }, { tableName: 'users', timestamps: true, });

    export default User;

  4. ایجاد مدل Request
    - فیلدهای requestId، userId، expertId، text و trackingCode
    import { DataTypes } from 'sequelize';
    import sequelize from '../configs/database.js';
    import User from './user.js';
    import { v4 as uuidv4 } from 'uuid';
    

    const Request = sequelize.define('Request', { requestId: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true, }, userId: { type: DataTypes.INTEGER, references: { model: User, key: 'userId' }, allowNull: true, }, expertId: { type: DataTypes.INTEGER, references: { model: User, key: 'userId' }, allowNull: true, }, text: { type: DataTypes.TEXT, allowNull: false, }, trackingCode: { type: DataTypes.STRING(20), allowNull: false, unique: true, defaultValue: () => uuidv4().slice(0, 20), }, }, { tableName: 'requests', timestamps: true, });

    export default Request;

  5. ایجاد مدل RequestStatus
    - ENUM وضعیت‌ها، ارجاع به requestId و نگهداری previous_state
    import { DataTypes } from 'sequelize';
    import sequelize from '../configs/database.js';
    import Request from './request.js';
    

    const RequestStatus = sequelize.define('RequestStatus', { statusId: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true, }, requestId: { type: DataTypes.INTEGER, references: { model: Request, key: 'requestId' }, allowNull: false, }, statusType: { type: DataTypes.ENUM('started','pending','accepted','rejected','done'), defaultValue: 'started', }, expertComment: { type: DataTypes.TEXT, defaultValue: '', }, userComment: { type: DataTypes.TEXT, defaultValue: '', }, files: { type: DataTypes.JSONB, allowNull: true, }, previous_state: { type: DataTypes.INTEGER, references: { model: 'request_statuses', key: 'statusId' }, allowNull: true, }, }, { tableName: 'request_statuses', timestamps: true, hooks: { beforeCreate: async (status) => { const last = await RequestStatus.findOne({ where: { requestId: status.requestId }, order: [['statusId','DESC']], }); if (last) status.previous_state = last.statusId; } } }); export default RequestStatus;

  6. تنظیم ارتباطات (Associations)
    - در فایل src/models/index.js یا انتهای هر مدل:
    import User from './user.js';
    import Request from './request.js';
    import RequestStatus from './requestStatus.js';
    // User ↔ Request
    User.hasMany(Request, { foreignKey: 'userId', as: 'Requests' });
    Request.belongsTo(User, { foreignKey: 'userId', as: 'Customer' });
    User.hasMany(Request, { foreignKey: 'expertId', as: 'ExpertRequests' });
    Request.belongsTo(User, { foreignKey: 'expertId', as: 'Expert' });
    // Request ↔ RequestStatus
    Request.hasMany(RequestStatus, { foreignKey: 'requestId', as: 'Statuses' });
    RequestStatus.belongsTo(Request, { foreignKey: 'requestId', as: 'Request' });
    export { User, Request, RequestStatus };
  7. پیاده‌سازی الگوی وضعیت (State Pattern)
    - ساخت پوشه src/models/state/ و ایجاد کلاس‌های وضعیت:
    import { handleReqRejected } from '../../utils/status.utils.js';
    class AcceptedState {
    constructor(context) { this.context = context; }
    async transitionTo(statusType) {
    if (!statusType) throw new Error('Invalid statusType');
    if (statusType === 'rejected') {
    return await handleReqRejected(this.context);
    }
    throw new Error(`Cannot transition from accepted to ${statusType}`);
    }
    }
    

    export default AcceptedState;

    - در سرویس Request فراخوانی وضعیت:
    import AcceptedState from './state/acceptedState.js';
    import PendingState from './state/pendingState.js';
    // …
    class RequestService {
    constructor(request) {
    this.request = request;
    this.state = this._getStateInstance(request.currentStatus);
    }
    _getStateInstance(type) {
    switch(type) {
    case 'accepted': return new AcceptedState(this.request);
    // …
    }
    }
    async changeStatus(toType) {
    return this.state.transitionTo(toType);
    }
    }