feat: stabilize API startup and lifecycle management
- Refactor server initialization to separate concerns and improve error handling. - Implement centralized environment validation using Zod. - Introduce database, Redis, and queue lifecycle management. - Add health check endpoints for liveness and readiness. - Enhance error handling middleware for better response structure. - Implement rate limiting for API endpoints. - Add request ID middleware for traceability. - Create Sequelize CLI configuration and baseline migration for schema management. - Establish CI workflow with Gitea for testing and syntax checks. - Document foundational changes and migration strategy in PHASE_0_FOUNDATION_STABILIZATION.md. - Add Docker Compose configuration for local development and testing. - Implement unit and integration tests for critical functionality.
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
"use strict";
|
||||
|
||||
const tableName = (value) => typeof value === "string" ? value : value.tableName;
|
||||
|
||||
module.exports = {
|
||||
async up(queryInterface, Sequelize) {
|
||||
const existing = new Set((await queryInterface.showAllTables()).map(tableName));
|
||||
const create = async (name, columns, options = {}) => {
|
||||
if (!existing.has(name)) await queryInterface.createTable(name, columns, options);
|
||||
};
|
||||
const timestamps = {
|
||||
createdAt: { type: Sequelize.DATE, allowNull: false },
|
||||
updatedAt: { type: Sequelize.DATE, allowNull: false },
|
||||
};
|
||||
|
||||
await create("users", {
|
||||
id: { type: Sequelize.STRING, primaryKey: true }, firstName: { type: Sequelize.STRING, allowNull: false },
|
||||
lastName: { type: Sequelize.STRING, allowNull: false }, email: { type: Sequelize.STRING, allowNull: false, unique: true },
|
||||
password: { type: Sequelize.STRING, allowNull: false },
|
||||
accountType: { type: Sequelize.ENUM("admin", "superadmin", "manager", "business_customer", "rider", "customer", "support_agent"), defaultValue: "customer" },
|
||||
accountStatus: { type: Sequelize.ENUM("PENDING_VERIFICATION", "ACTIVE", "SUSPENDED", "DEACTIVATED"), allowNull: false, defaultValue: "PENDING_VERIFICATION" },
|
||||
emailVerifiedAt: { type: Sequelize.DATE, allowNull: true, defaultValue: null }, ...timestamps,
|
||||
});
|
||||
await create("profiles", {
|
||||
profile_id: { type: Sequelize.STRING, primaryKey: true },
|
||||
user_id: { type: Sequelize.STRING, allowNull: false, unique: true, references: { model: "users", key: "id" } },
|
||||
theme: { type: Sequelize.STRING, allowNull: false, defaultValue: "light" }, notificationsEnabled: { type: Sequelize.BOOLEAN, defaultValue: true },
|
||||
profilePicture_id: { type: Sequelize.STRING, allowNull: true }, backgroundImage_id: { type: Sequelize.STRING, allowNull: true },
|
||||
dob: { type: Sequelize.DATE, allowNull: true }, phone_number: { type: Sequelize.STRING, allowNull: true }, ...timestamps,
|
||||
});
|
||||
await create("customers", {
|
||||
customer_id: { type: Sequelize.STRING, primaryKey: true },
|
||||
user_id: { type: Sequelize.STRING, allowNull: false, unique: true, references: { model: "users", key: "id" } },
|
||||
address: { type: Sequelize.STRING, allowNull: false }, phoneNumber: { type: Sequelize.STRING, allowNull: false }, ...timestamps,
|
||||
});
|
||||
await create("business_customers", {
|
||||
business_customer_id: { type: Sequelize.STRING, primaryKey: true },
|
||||
user_id: { type: Sequelize.STRING, allowNull: false, unique: true, references: { model: "users", key: "id" } },
|
||||
businessName: { type: Sequelize.STRING, allowNull: false }, businessRegistrationNumber: { type: Sequelize.STRING, allowNull: false },
|
||||
businessType: { type: Sequelize.STRING, allowNull: false }, contactName: { type: Sequelize.STRING, allowNull: false },
|
||||
phoneNumber: { type: Sequelize.STRING, allowNull: false }, businessEmail: { type: Sequelize.STRING, allowNull: false },
|
||||
expectedMonthlyVolume: { type: Sequelize.STRING, allowNull: false }, note: { type: Sequelize.STRING, allowNull: true }, ...timestamps,
|
||||
});
|
||||
await create("roles", {
|
||||
role_id: { type: Sequelize.STRING, primaryKey: true }, roleName: { type: Sequelize.STRING, allowNull: false },
|
||||
roleDescription: { type: Sequelize.TEXT, allowNull: true }, ...timestamps,
|
||||
});
|
||||
await create("permission", {
|
||||
permission_id: { type: Sequelize.STRING, primaryKey: true }, permissionName: { type: Sequelize.STRING, allowNull: false },
|
||||
permissionDescription: { type: Sequelize.TEXT, allowNull: true }, page: { type: Sequelize.STRING, allowNull: false },
|
||||
module: { type: Sequelize.STRING, allowNull: false }, action: { type: Sequelize.STRING, allowNull: false }, ...timestamps,
|
||||
});
|
||||
await create("rolePermission", {
|
||||
rp_id: { type: Sequelize.STRING, primaryKey: true }, role_id: { type: Sequelize.STRING, allowNull: false, references: { model: "roles", key: "role_id" } },
|
||||
permission_id: { type: Sequelize.STRING, allowNull: false, references: { model: "permission", key: "permission_id" } }, ...timestamps,
|
||||
});
|
||||
await create("userPermission", {
|
||||
up_id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
|
||||
user_id: { type: Sequelize.STRING, allowNull: false, references: { model: "users", key: "id" } },
|
||||
permission_id: { type: Sequelize.STRING, allowNull: false, references: { model: "permission", key: "permission_id" } }, ...timestamps,
|
||||
});
|
||||
await create("uploads", {
|
||||
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, file_path: { type: Sequelize.STRING, allowNull: false },
|
||||
file_type: { type: Sequelize.STRING, allowNull: false }, file_size: { type: Sequelize.INTEGER, allowNull: false },
|
||||
original_name: { type: Sequelize.STRING, allowNull: false }, use_for: { type: Sequelize.STRING, allowNull: false },
|
||||
uploaded_by: { type: Sequelize.STRING, allowNull: false }, ...timestamps,
|
||||
});
|
||||
await create("UserActivity", {
|
||||
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, user_id: { type: Sequelize.STRING, allowNull: false },
|
||||
username: { type: Sequelize.STRING, allowNull: false }, activity_description: { type: Sequelize.STRING, allowNull: false },
|
||||
activity_type: { type: Sequelize.STRING, allowNull: true }, module: { type: Sequelize.STRING, allowNull: true },
|
||||
activity_time: { type: Sequelize.DATE, allowNull: false }, activity_date: { type: Sequelize.DATEONLY, allowNull: false }, ...timestamps,
|
||||
});
|
||||
await create("Document", {
|
||||
doc_id: { type: Sequelize.STRING, primaryKey: true }, reference_no: { type: Sequelize.STRING, allowNull: false },
|
||||
doc_type: { type: Sequelize.STRING, allowNull: true, defaultValue: "N/A" }, data: { type: Sequelize.JSON, allowNull: false },
|
||||
status: { type: Sequelize.STRING, allowNull: false, defaultValue: "DRAFT" }, ...timestamps,
|
||||
});
|
||||
await create("DocumentType", {
|
||||
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
|
||||
doc_type_name: { type: Sequelize.STRING, allowNull: true, defaultValue: "N/A" }, description: { type: Sequelize.JSON, allowNull: false }, ...timestamps,
|
||||
});
|
||||
await create("referenceNumbers", {
|
||||
id: { type: Sequelize.STRING, primaryKey: true }, sequence_key: { type: Sequelize.STRING, allowNull: false, unique: true },
|
||||
current_number: { type: Sequelize.INTEGER, allowNull: false, defaultValue: 0 }, last_ref_number: { type: Sequelize.STRING }, ...timestamps,
|
||||
});
|
||||
await create("notification", {
|
||||
notification_id: { type: Sequelize.STRING, primaryKey: true }, notificationHeadline: { type: Sequelize.STRING, allowNull: false },
|
||||
notificationDescription: { type: Sequelize.TEXT, allowNull: true }, notificationType: { type: Sequelize.ENUM("USER", "ANNOUNCEMENT"), allowNull: false },
|
||||
isActive: { type: Sequelize.BOOLEAN, defaultValue: true }, dateCreated: { type: Sequelize.DATE, defaultValue: Sequelize.fn("NOW") }, ...timestamps,
|
||||
});
|
||||
await create("user_notification", {
|
||||
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, user_id: { type: Sequelize.STRING(255), allowNull: false },
|
||||
notification_id: { type: Sequelize.STRING, allowNull: false }, isRead: { type: Sequelize.BOOLEAN, defaultValue: false }, ...timestamps,
|
||||
});
|
||||
},
|
||||
|
||||
async down(queryInterface) {
|
||||
for (const name of ["user_notification", "notification", "referenceNumbers", "DocumentType", "Document", "UserActivity", "uploads", "userPermission", "rolePermission", "permission", "roles", "business_customers", "customers", "profiles", "users"]) {
|
||||
await queryInterface.dropTable(name);
|
||||
}
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user