ojamoto_models 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.vscode/settings.json +3 -0
  2. package/LICENSE +21 -0
  3. package/certificates/global-bundle.pem +2736 -0
  4. package/config/config.js +52 -0
  5. package/index.js +3 -0
  6. package/migrations/2026000001-create-users.js +97 -0
  7. package/migrations/2026000002-create-and-seed-roles.js +43 -0
  8. package/migrations/2026000003-create-admin-users.js +83 -0
  9. package/migrations/2026000004-create-devices.js +67 -0
  10. package/migrations/2026000005-create-products.js +87 -0
  11. package/migrations/2026000006-create-addresses.js +74 -0
  12. package/migrations/2026000007-create-wallets.js +44 -0
  13. package/migrations/2026000008-create-kyc-verification.js +51 -0
  14. package/migrations/2026000009-create-seller-profiles.js +80 -0
  15. package/migrations/2026000010-create-user-roles.js +48 -0
  16. package/migrations/2026000011-create-sessions.js +61 -0
  17. package/migrations/2026000012-create-escrows.js +80 -0
  18. package/migrations/2026000013-create-wallet-transactions.js +70 -0
  19. package/migrations/2026000014-create-product-images.js +43 -0
  20. package/migrations/2026000015-create-escrow-events.js +50 -0
  21. package/migrations/2026000016-create-logistics.js +55 -0
  22. package/migrations/2026000017-create-ratings.js +56 -0
  23. package/migrations/2026000018-create-disputes.js +84 -0
  24. package/migrations/2026000019-create-dispute-evidence.js +50 -0
  25. package/migrations/2026000020-create-dispute-messages.js +44 -0
  26. package/migrations/2026000021-create-notifications.js +50 -0
  27. package/migrations/2026000022-create-audit-logs.js +57 -0
  28. package/migrations/2026000023-create-webhook-logs.js +46 -0
  29. package/migrations/2026000024-create-user-otps.js +75 -0
  30. package/migrations/2026000025-create-password-reset-otps.js +43 -0
  31. package/migrations/2026000026-create-seller-badges.js +43 -0
  32. package/migrations/2026000027-add-fulltext-index-for-search.js +15 -0
  33. package/migrations/2026000028-create-admin-notifications.js +53 -0
  34. package/migrations/2026000029-create-pin-reset-otp.js +30 -0
  35. package/models/Address.js +76 -0
  36. package/models/AdminNotification.js +59 -0
  37. package/models/AdminUser.js +115 -0
  38. package/models/AuditLog.js +70 -0
  39. package/models/Device.js +82 -0
  40. package/models/Dispute.js +109 -0
  41. package/models/DisputeEvidence.js +62 -0
  42. package/models/DisputeMessage.js +57 -0
  43. package/models/Escrow.js +110 -0
  44. package/models/EscrowEvent.js +63 -0
  45. package/models/KycVerification.js +62 -0
  46. package/models/Logistics.js +62 -0
  47. package/models/Notification.js +58 -0
  48. package/models/PasswordResetOTP.js +52 -0
  49. package/models/PinResetOTP.js +48 -0
  50. package/models/Product.js +100 -0
  51. package/models/ProductImage.js +54 -0
  52. package/models/Rating.js +74 -0
  53. package/models/Role.js +43 -0
  54. package/models/SellerBadges.js +45 -0
  55. package/models/SellerProfile.js +92 -0
  56. package/models/Session.js +67 -0
  57. package/models/User.js +198 -0
  58. package/models/UserOTP.js +84 -0
  59. package/models/UserRole.js +42 -0
  60. package/models/Wallet.js +63 -0
  61. package/models/WalletTransaction.js +81 -0
  62. package/models/WebhookLog.js +52 -0
  63. package/models/index.js +23 -0
  64. package/package.json +30 -0
  65. package/services/constants.js +162 -0
package/models/User.js ADDED
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+
3
+ const { Model, DataTypes } = require("sequelize");
4
+ const { USER_STATUS } = require("../services/constants");
5
+
6
+ module.exports = (sequelize) => {
7
+ class User extends Model {
8
+ static associate(models) {
9
+ // Roles (many-to-many)
10
+ User.belongsToMany(models.Role, {
11
+ through: models.UserRole,
12
+ foreignKey: "user_id",
13
+ otherKey: "role_id",
14
+ as: "roles",
15
+ });
16
+
17
+ // Address
18
+ User.hasMany(models.Address, {
19
+ foreignKey: "user_id",
20
+ as: "addresses",
21
+ });
22
+
23
+ // KYC
24
+ User.hasOne(models.KycVerification, {
25
+ foreignKey: "user_id",
26
+ as: "kyc",
27
+ });
28
+
29
+ // Wallet
30
+ User.hasOne(models.Wallet, {
31
+ foreignKey: "user_id",
32
+ as: "wallet",
33
+ });
34
+
35
+ // Seller profile
36
+ User.hasOne(models.SellerProfile, {
37
+ foreignKey: "user_id",
38
+ as: "sellerProfile",
39
+ });
40
+
41
+ // Products listed by this seller
42
+ User.hasMany(models.Product, {
43
+ foreignKey: "seller_id",
44
+ as: "products",
45
+ });
46
+
47
+ // Devices
48
+ User.hasMany(models.Device, {
49
+ foreignKey: "user_id",
50
+ as: "devices",
51
+ });
52
+
53
+ // Sessions
54
+ User.hasMany(models.Session, {
55
+ foreignKey: "user_id",
56
+ as: "sessions",
57
+ });
58
+
59
+ // Escrows as buyer
60
+ User.hasMany(models.Escrow, {
61
+ foreignKey: "buyer_id",
62
+ as: "buyerEscrows",
63
+ });
64
+
65
+ // Escrows as seller
66
+ User.hasMany(models.Escrow, {
67
+ foreignKey: "seller_id",
68
+ as: "sellerEscrows",
69
+ });
70
+
71
+ // Disputes opened by this user
72
+ User.hasMany(models.Dispute, {
73
+ foreignKey: "opened_by",
74
+ as: "openedDisputes",
75
+ });
76
+
77
+ // Ratings given (as buyer)
78
+ User.hasMany(models.Rating, {
79
+ foreignKey: "buyer_id",
80
+ as: "givenRatings",
81
+ });
82
+
83
+ // Ratings received (as seller)
84
+ User.hasMany(models.Rating, {
85
+ foreignKey: "seller_id",
86
+ as: "receivedRatings",
87
+ });
88
+
89
+ // Notifications
90
+ User.hasMany(models.Notification, {
91
+ foreignKey: "user_id",
92
+ as: "notifications",
93
+ });
94
+
95
+ // Audit logs
96
+ User.hasMany(models.AuditLog, {
97
+ foreignKey: "user_id",
98
+ as: "auditLogs",
99
+ });
100
+
101
+ // Wallet transactions
102
+ User.hasMany(models.WalletTransaction, {
103
+ foreignKey: "user_id",
104
+ as: "walletTransactions",
105
+ });
106
+ }
107
+ }
108
+
109
+ User.init(
110
+ {
111
+ user_id: {
112
+ type: DataTypes.INTEGER,
113
+ primaryKey: true,
114
+ autoIncrement: true,
115
+ },
116
+ uuid: {
117
+ type: DataTypes.UUID,
118
+ defaultValue: DataTypes.UUIDV4,
119
+ allowNull: false,
120
+ unique: true,
121
+ },
122
+ first_name: {
123
+ type: DataTypes.STRING(100),
124
+ allowNull: false,
125
+ },
126
+ last_name: {
127
+ type: DataTypes.STRING(100),
128
+ allowNull: false,
129
+ },
130
+ middle_name: {
131
+ type: DataTypes.STRING(100),
132
+ allowNull: true,
133
+ },
134
+ email: {
135
+ type: DataTypes.STRING(255),
136
+ allowNull: false,
137
+ unique: true,
138
+ validate: { isEmail: true },
139
+ },
140
+ phone: {
141
+ type: DataTypes.STRING(10),
142
+ allowNull: false,
143
+ unique: true,
144
+ },
145
+ country_calling_code: {
146
+ type: DataTypes.STRING(3),
147
+ allowNull: false,
148
+ },
149
+ password_hash: {
150
+ type: DataTypes.STRING(255),
151
+ allowNull: false,
152
+ },
153
+ transaction_pin_hash: {
154
+ type: DataTypes.STRING(255),
155
+ allowNull: true,
156
+ },
157
+ status: {
158
+ type: DataTypes.ENUM(...Object.values(USER_STATUS)),
159
+ allowNull: false,
160
+ defaultValue: USER_STATUS.ACTIVE,
161
+ },
162
+ email_verified: {
163
+ type: DataTypes.BOOLEAN,
164
+ defaultValue: false,
165
+ },
166
+ phone_verified: {
167
+ type: DataTypes.BOOLEAN,
168
+ defaultValue: false,
169
+ },
170
+ nin_verified: {
171
+ type: DataTypes.BOOLEAN,
172
+ defaultValue: false,
173
+ comment: "Sellers only",
174
+ },
175
+ bank_verified: {
176
+ type: DataTypes.BOOLEAN,
177
+ defaultValue: false,
178
+ },
179
+ two_factor_enabled: {
180
+ type: DataTypes.BOOLEAN,
181
+ defaultValue: false,
182
+ },
183
+ },
184
+ {
185
+ sequelize,
186
+ modelName: "User",
187
+ tableName: "users",
188
+ timestamps: true,
189
+ paranoid: true,
190
+ createdAt: "created_at",
191
+ updatedAt: "updated_at",
192
+ deletedAt: "deleted_at",
193
+ },
194
+ );
195
+
196
+ User.removeAttribute("id");
197
+ return User;
198
+ };
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+
3
+ const { Model, DataTypes } = require("sequelize");
4
+
5
+ module.exports = (sequelize) => {
6
+ class UserOTP extends Model {
7
+ static associate(models) {}
8
+ }
9
+
10
+ UserOTP.init(
11
+ {
12
+ user_otp_id: {
13
+ type: DataTypes.INTEGER,
14
+ primaryKey: true,
15
+ autoIncrement: true,
16
+ allowNull: false,
17
+ },
18
+ email: {
19
+ type: DataTypes.STRING,
20
+ allowNull: false,
21
+ },
22
+ first_name: {
23
+ type: DataTypes.STRING,
24
+ allowNull: false,
25
+ },
26
+ last_name: {
27
+ type: DataTypes.STRING,
28
+ allowNull: false,
29
+ },
30
+ middle_name: {
31
+ type: DataTypes.STRING,
32
+ allowNull: true,
33
+ },
34
+ phone: {
35
+ type: DataTypes.STRING(10),
36
+ allowNull: false,
37
+ },
38
+ country_calling_code: {
39
+ type: DataTypes.STRING(3),
40
+ allowNull: false,
41
+ },
42
+ password_hash: {
43
+ type: DataTypes.STRING,
44
+ allowNull: false,
45
+ },
46
+ user_type: {
47
+ type: DataTypes.STRING,
48
+ allowNull: false,
49
+ },
50
+ otp: {
51
+ type: DataTypes.STRING,
52
+ allowNull: false,
53
+ },
54
+ purpose: {
55
+ type: DataTypes.STRING,
56
+ allowNull: false,
57
+ },
58
+ expires_at: {
59
+ type: DataTypes.DATE,
60
+ allowNull: false,
61
+ },
62
+ },
63
+ {
64
+ sequelize,
65
+ modelName: "UserOTP",
66
+ tableName: "user_otps",
67
+ timestamps: true,
68
+ paranoid: false,
69
+ expiresAt: "expires_at",
70
+ createdAt: "created_at",
71
+ updatedAt: "updated_at",
72
+ indexes: [
73
+ {
74
+ unique: true,
75
+ fields: ["email", "purpose"],
76
+ name: "user_otps_email_purpose_unique",
77
+ },
78
+ ],
79
+ },
80
+ );
81
+
82
+ UserOTP.removeAttribute("id");
83
+ return UserOTP;
84
+ };
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ const { Model, DataTypes } = require("sequelize");
4
+
5
+ module.exports = (sequelize) => {
6
+ class UserRole extends Model {
7
+ static associate(models) {}
8
+ }
9
+
10
+ UserRole.init(
11
+ {
12
+ user_role_id: {
13
+ type: DataTypes.INTEGER,
14
+ primaryKey: true,
15
+ autoIncrement: true,
16
+ },
17
+ user_id: {
18
+ type: DataTypes.INTEGER,
19
+ allowNull: false,
20
+ references: { model: "users", key: "user_id" },
21
+ onDelete: "CASCADE",
22
+ },
23
+ role_id: {
24
+ type: DataTypes.INTEGER,
25
+ allowNull: false,
26
+ references: { model: "roles", key: "role_id" },
27
+ onDelete: "CASCADE",
28
+ },
29
+ },
30
+ {
31
+ sequelize,
32
+ modelName: "UserRole",
33
+ tableName: "user_roles",
34
+ timestamps: true,
35
+ createdAt: "created_at",
36
+ updatedAt: "updated_at",
37
+ },
38
+ );
39
+
40
+ UserRole.removeAttribute("id");
41
+ return UserRole;
42
+ };
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+
3
+ const { Model, DataTypes } = require("sequelize");
4
+
5
+ module.exports = (sequelize) => {
6
+ class Wallet extends Model {
7
+ static associate(models) {
8
+ Wallet.belongsTo(models.User, {
9
+ foreignKey: "user_id",
10
+ as: "user",
11
+ });
12
+
13
+ Wallet.hasMany(models.WalletTransaction, {
14
+ foreignKey: "wallet_id",
15
+ as: "transactions",
16
+ });
17
+ }
18
+ }
19
+
20
+ Wallet.init(
21
+ {
22
+ wallet_id: {
23
+ type: DataTypes.INTEGER,
24
+ primaryKey: true,
25
+ autoIncrement: true,
26
+ },
27
+ user_id: {
28
+ type: DataTypes.INTEGER,
29
+ allowNull: false,
30
+ unique: true,
31
+ references: { model: "users", key: "user_id" },
32
+ onDelete: "CASCADE",
33
+ comment: "One wallet per user",
34
+ },
35
+ available_balance: {
36
+ type: DataTypes.DECIMAL(15, 2),
37
+ allowNull: false,
38
+ defaultValue: 0.0,
39
+ comment: "Spendable funds",
40
+ },
41
+ locked_balance: {
42
+ type: DataTypes.DECIMAL(15, 2),
43
+ allowNull: false,
44
+ defaultValue: 0.0,
45
+ comment: "Funds currently held in active escrows",
46
+ },
47
+ currency: {
48
+ type: DataTypes.STRING(3),
49
+ allowNull: false,
50
+ defaultValue: "NGN",
51
+ },
52
+ },
53
+ {
54
+ sequelize,
55
+ modelName: "Wallet",
56
+ tableName: "wallets",
57
+ timestamps: false,
58
+ },
59
+ );
60
+
61
+ Wallet.removeAttribute("id");
62
+ return Wallet;
63
+ };
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+
3
+ const { Model, DataTypes } = require("sequelize");
4
+ const {
5
+ WALLET_TRANSACTION_TYPE,
6
+ WALLET_TRANSACTION_STATUS,
7
+ } = require("../services/constants");
8
+
9
+ module.exports = (sequelize) => {
10
+ class WalletTransaction extends Model {
11
+ static associate(models) {
12
+ WalletTransaction.belongsTo(models.Wallet, {
13
+ foreignKey: "wallet_id",
14
+ as: "wallet",
15
+ });
16
+
17
+ WalletTransaction.belongsTo(models.User, {
18
+ foreignKey: "user_id",
19
+ as: "user",
20
+ });
21
+ }
22
+ }
23
+
24
+ WalletTransaction.init(
25
+ {
26
+ wallet_transaction_id: {
27
+ type: DataTypes.INTEGER,
28
+ primaryKey: true,
29
+ autoIncrement: true,
30
+ },
31
+ wallet_id: {
32
+ type: DataTypes.INTEGER,
33
+ allowNull: false,
34
+ references: { model: "wallets", key: "wallet_id" },
35
+ onDelete: "CASCADE",
36
+ },
37
+ user_id: {
38
+ type: DataTypes.INTEGER,
39
+ allowNull: false,
40
+ references: { model: "users", key: "user_id" },
41
+ onDelete: "CASCADE",
42
+ comment: "Denormalized for quick queries",
43
+ },
44
+ type: {
45
+ type: DataTypes.ENUM(...Object.values(WALLET_TRANSACTION_TYPE)),
46
+ allowNull: false,
47
+ },
48
+ amount: {
49
+ type: DataTypes.DECIMAL(15, 2),
50
+ allowNull: false,
51
+ },
52
+ currency: {
53
+ type: DataTypes.STRING(3),
54
+ allowNull: false,
55
+ defaultValue: "NGN",
56
+ },
57
+ reference: {
58
+ type: DataTypes.STRING(100),
59
+ allowNull: false,
60
+ unique: true,
61
+ comment: "Flutterwave ref or internal reference",
62
+ },
63
+ status: {
64
+ type: DataTypes.ENUM(...Object.values(WALLET_TRANSACTION_STATUS)),
65
+ allowNull: false,
66
+ defaultValue: WALLET_TRANSACTION_STATUS.PENDING,
67
+ },
68
+ },
69
+ {
70
+ sequelize,
71
+ modelName: "WalletTransaction",
72
+ tableName: "wallet_transactions",
73
+ timestamps: true,
74
+ createdAt: "created_at",
75
+ updatedAt: false,
76
+ },
77
+ );
78
+
79
+ WalletTransaction.removeAttribute("id");
80
+ return WalletTransaction;
81
+ };
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+
3
+ const { Model, DataTypes } = require("sequelize");
4
+ const { WEBHOOK_STATUS } = require("../services/constants");
5
+
6
+ module.exports = (sequelize) => {
7
+ class WebhookLog extends Model {
8
+ static associate(models) {
9
+ // No associations — standalone security log
10
+ }
11
+ }
12
+
13
+ WebhookLog.init(
14
+ {
15
+ webhook_log_id: {
16
+ type: DataTypes.INTEGER,
17
+ primaryKey: true,
18
+ autoIncrement: true,
19
+ },
20
+ source: {
21
+ type: DataTypes.STRING(100),
22
+ allowNull: false,
23
+ comment: "flutterwave | twilio | onesignal",
24
+ },
25
+ event_type: {
26
+ type: DataTypes.STRING(100),
27
+ allowNull: true,
28
+ comment: "e.g. charge.completed | delivery.update",
29
+ },
30
+ payload_json: {
31
+ type: DataTypes.JSON,
32
+ allowNull: false,
33
+ comment: "Raw webhook body",
34
+ },
35
+ status: {
36
+ type: DataTypes.ENUM(...Object.values(WEBHOOK_STATUS)),
37
+ allowNull: false,
38
+ },
39
+ },
40
+ {
41
+ sequelize,
42
+ modelName: "WebhookLog",
43
+ tableName: "webhook_logs",
44
+ timestamps: true,
45
+ createdAt: "created_at",
46
+ updatedAt: false,
47
+ },
48
+ );
49
+
50
+ WebhookLog.removeAttribute("id");
51
+ return WebhookLog;
52
+ };
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const basename = path.basename(__filename);
6
+
7
+ module.exports = (sequelize) => {
8
+ const db = {};
9
+
10
+ fs.readdirSync(__dirname)
11
+ .filter((file) => file !== basename && file.endsWith(".js"))
12
+ .forEach((file) => {
13
+ const model = require(path.join(__dirname, file))(sequelize);
14
+ db[model.name] = model;
15
+ });
16
+
17
+ // Setup associations
18
+ Object.keys(db).forEach((name) => {
19
+ if (db[name].associate) db[name].associate(db);
20
+ });
21
+
22
+ return db;
23
+ };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "ojamoto_models",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "migrate:up": "npx sequelize-cli db:migrate",
7
+ "migrate:undo": "npx sequelize-cli db:migrate:undo",
8
+ "migrate:undo:all": "npx sequelize-cli db:migrate:undo:all",
9
+ "seed:all": "npx sequelize-cli db:seed:all",
10
+ "seed:undo": "npx sequelize-cli db:seed:undo"
11
+ },
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/ojamoto/ojamoto_models.git"
15
+ },
16
+ "keywords": [],
17
+ "author": "",
18
+ "license": "ISC",
19
+ "bugs": {
20
+ "url": "https://github.com/ojamoto/ojamoto_models/issues"
21
+ },
22
+ "homepage": "https://github.com/ojamoto/ojamoto_models#readme",
23
+ "description": "",
24
+ "dependencies": {
25
+ "dotenv": "^17.4.2",
26
+ "mysql2": "^3.23.2",
27
+ "sequelize": "^6.37.8",
28
+ "sequelize-cli": "^6.6.5"
29
+ }
30
+ }