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:
Sathira Sri Sathara
2026-09-03 13:33:26 +05:30
parent 624fce31c5
commit 267e80e2ec
40 changed files with 1682 additions and 261 deletions
@@ -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);
}
},
};