customer-registration 0.0.111 → 0.0.113
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.
- package/.medusa/server/src/admin/index.js +3249 -2
- package/.medusa/server/src/admin/index.mjs +3232 -2
- package/.medusa/server/src/api/admin/account-deletion-requests/route.js +30 -0
- package/.medusa/server/src/api/admin/account-deletion-requests/validators.js +12 -0
- package/.medusa/server/src/api/auth/customer/emailpass/reset-password/route.js +1 -26
- package/.medusa/server/src/api/auth/customer/emailpass/route.js +24 -97
- package/.medusa/server/src/api/auth/customer/phonepass/register/route.js +50 -0
- package/.medusa/server/src/api/auth/customer/phonepass/route.js +105 -0
- package/.medusa/server/src/api/middlewares/guard-account-deletion.js +29 -0
- package/.medusa/server/src/api/middlewares/ip-rate-limit.js +48 -0
- package/.medusa/server/src/api/middlewares/validate-customer-registration.js +60 -0
- package/.medusa/server/src/api/middlewares.js +30 -0
- package/.medusa/server/src/api/store/customers/account-deletion/cancel-confirm/route.js +36 -0
- package/.medusa/server/src/api/store/customers/account-deletion/cancel-request/route.js +55 -0
- package/.medusa/server/src/api/store/customers/account-deletion/confirm/route.js +43 -0
- package/.medusa/server/src/api/store/customers/account-deletion/request/route.js +40 -0
- package/.medusa/server/src/api/store/customers/account-deletion/validators.js +27 -0
- package/.medusa/server/src/api/store/customers/me/contact/route.js +95 -0
- package/.medusa/server/src/api/store/customers/me/contact/verify/route.js +83 -0
- package/.medusa/server/src/api/store/customers/me/route.js +53 -0
- package/.medusa/server/src/api/store/customers/otp/send/route.js +1 -6
- package/.medusa/server/src/api/store/customers/otp/verify/route.js +95 -3
- package/.medusa/server/src/api/store/customers/route.js +89 -0
- package/.medusa/server/src/config.js +44 -13
- package/.medusa/server/src/jobs/process-account-deletions.js +69 -0
- package/.medusa/server/src/modules/account-deletion-request/index.js +17 -0
- package/.medusa/server/src/modules/account-deletion-request/migrations/Migration20250221000000CreateAccountDeletionRequestTable.js +42 -0
- package/.medusa/server/src/modules/account-deletion-request/migrations/Migration20250221100000AddCompletedStatusToAccountDeletionRequest.js +30 -0
- package/.medusa/server/src/modules/account-deletion-request/models/account-deletion-request.js +26 -0
- package/.medusa/server/src/modules/account-deletion-request/service.js +90 -0
- package/.medusa/server/src/modules/otp-verification/migrations/Migration20250221000000AddAccountDeletionOtpPurposes.js +35 -0
- package/.medusa/server/src/modules/otp-verification/models/otp-verification.js +9 -2
- package/.medusa/server/src/modules/otp-verification/service.js +79 -1
- package/.medusa/server/src/providers/phonepass/index.js +9 -0
- package/.medusa/server/src/providers/phonepass/service.js +133 -0
- package/.medusa/server/src/subscribers/password-reset.js +1 -42
- package/.medusa/server/src/workflows/change-password.js +40 -64
- package/.medusa/server/src/workflows/send-contact-change-otp-workflow.js +41 -0
- package/.medusa/server/src/workflows/steps/determine-contact-method-step.js +8 -2
- package/.medusa/server/src/workflows/steps/generate-contact-change-otp-step.js +24 -0
- package/.medusa/server/src/workflows/steps/index.js +6 -2
- package/.medusa/server/src/workflows/steps/resolve-channel-config-step.js +10 -1
- package/.medusa/server/src/workflows/steps/send-notification-step.js +1 -11
- package/.medusa/server/src/workflows/steps/sync-phonepass-entity-id-step.js +63 -0
- package/.medusa/server/src/workflows/steps/update-password-step.js +21 -29
- package/.medusa/server/src/workflows/update-contact-workflow.js +100 -0
- package/.medusa/server/src/workflows/verify-phone.js +11 -4
- package/README.md +389 -147
- package/package.json +12 -3
- package/.medusa/server/src/subscribers/customer-updated.js +0 -100
|
@@ -5,10 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.POST = void 0;
|
|
7
7
|
const utils_1 = require("@medusajs/framework/utils");
|
|
8
|
+
const generate_jwt_token_1 = require("@medusajs/medusa/api/auth/utils/generate-jwt-token");
|
|
8
9
|
const otp_verification_1 = require("../../../../../modules/otp-verification");
|
|
9
10
|
const otp_verification_2 = require("../../../../../modules/otp-verification/models/otp-verification");
|
|
10
11
|
const verify_email_1 = __importDefault(require("../../../../../workflows/verify-email"));
|
|
11
12
|
const verify_phone_1 = __importDefault(require("../../../../../workflows/verify-phone"));
|
|
13
|
+
const config_1 = require("../../../../../config");
|
|
12
14
|
const POST = async (req, res) => {
|
|
13
15
|
const { token, code } = req.body;
|
|
14
16
|
if (!token) {
|
|
@@ -21,12 +23,14 @@ const POST = async (req, res) => {
|
|
|
21
23
|
const otpService = req.scope.resolve(otp_verification_1.OTP_VERIFICATION_MODULE);
|
|
22
24
|
// Get JWT secret from config
|
|
23
25
|
const config = req.scope.resolve(utils_1.ContainerRegistrationKeys.CONFIG_MODULE);
|
|
24
|
-
const
|
|
26
|
+
const { http } = config.projectConfig;
|
|
27
|
+
const jwtSecret = http?.jwtSecret || "supersecret";
|
|
25
28
|
// Verify OTP
|
|
26
29
|
const verifyResult = await otpService.verifyOtp({
|
|
27
30
|
token,
|
|
28
31
|
code,
|
|
29
32
|
}, jwtSecret);
|
|
33
|
+
const loginOptions = (0, config_1.resolveCustomerRegistrationOptions)(config);
|
|
30
34
|
// Execute workflow based on type
|
|
31
35
|
let result;
|
|
32
36
|
if (verifyResult.type === otp_verification_2.OtpPurpose.EMAIL_VERIFICATION) {
|
|
@@ -45,6 +49,7 @@ const POST = async (req, res) => {
|
|
|
45
49
|
const { result: workflowResult } = await (0, verify_phone_1.default)(req.scope).run({
|
|
46
50
|
input: {
|
|
47
51
|
customer_id: verifyResult.customer_id,
|
|
52
|
+
login_identifier: loginOptions.login.identifier,
|
|
48
53
|
},
|
|
49
54
|
});
|
|
50
55
|
result = {
|
|
@@ -56,7 +61,94 @@ const POST = async (req, res) => {
|
|
|
56
61
|
else {
|
|
57
62
|
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Invalid verification type");
|
|
58
63
|
}
|
|
59
|
-
|
|
64
|
+
// When login.identifier is "both", automatically create the complementary
|
|
65
|
+
// auth identity (phonepass <-> emailpass) so the customer can log in with
|
|
66
|
+
// either provider without a separate registration step.
|
|
67
|
+
await ensureComplementaryAuthIdentity(result.customer?.id ?? "", result.customer ?? {}, verifyResult.type, loginOptions.login.identifier, req.scope).catch(() => {
|
|
68
|
+
// Non-fatal — customer can still log in with their original provider.
|
|
69
|
+
// The next successful OTP verification will retry.
|
|
70
|
+
});
|
|
71
|
+
let loginToken;
|
|
72
|
+
try {
|
|
73
|
+
const knex = req.scope.resolve(utils_1.ContainerRegistrationKeys.PG_CONNECTION);
|
|
74
|
+
const authRow = await knex.raw(`SELECT id FROM auth_identity WHERE app_metadata->>'customer_id' = ? LIMIT 1`, [result.customer?.id ?? ""]);
|
|
75
|
+
const authIdentityId = (authRow.rows?.[0] ?? authRow[0]?.[0])?.id;
|
|
76
|
+
if (authIdentityId && jwtSecret) {
|
|
77
|
+
const authService = req.scope.resolve(utils_1.Modules.AUTH);
|
|
78
|
+
const authIdentity = await authService.retrieveAuthIdentity(authIdentityId);
|
|
79
|
+
loginToken = await (0, generate_jwt_token_1.generateJwtTokenForAuthIdentity)({ authIdentity, actorType: "customer" }, { secret: jwtSecret, expiresIn: http.jwtExpiresIn || "7d" });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// token generation is best-effort; verification result is still returned
|
|
84
|
+
}
|
|
85
|
+
return res.status(200).json({
|
|
86
|
+
...result,
|
|
87
|
+
token: loginToken ?? null,
|
|
88
|
+
needs_login: !loginToken,
|
|
89
|
+
});
|
|
60
90
|
};
|
|
61
91
|
exports.POST = POST;
|
|
62
|
-
|
|
92
|
+
/**
|
|
93
|
+
* When `login.identifier` is "both", a customer who registered with one
|
|
94
|
+
* provider (emailpass or phonepass) needs the complementary provider identity
|
|
95
|
+
* created so both login methods work.
|
|
96
|
+
*
|
|
97
|
+
* Strategy: attach a new provider_identity to the EXISTING auth identity
|
|
98
|
+
* (rather than creating a second auth identity) and copy the scrypt password
|
|
99
|
+
* hash. Both emailpass and phonepass use the same scrypt-kdf format, so the
|
|
100
|
+
* hash is portable between them as long as this plugin controls both providers.
|
|
101
|
+
*/
|
|
102
|
+
async function ensureComplementaryAuthIdentity(customerId, customer, otpType, loginIdentifier, scope) {
|
|
103
|
+
if (loginIdentifier !== "both")
|
|
104
|
+
return;
|
|
105
|
+
if (!customerId)
|
|
106
|
+
return;
|
|
107
|
+
const knex = scope.resolve(utils_1.ContainerRegistrationKeys.PG_CONNECTION);
|
|
108
|
+
const authService = scope.resolve(utils_1.Modules.AUTH);
|
|
109
|
+
// Find the customer's primary auth identity ID (used as the anchor for the
|
|
110
|
+
// new provider identity).
|
|
111
|
+
const authRow = await knex.raw(`SELECT id FROM auth_identity WHERE app_metadata->>'customer_id' = ? LIMIT 1`, [customerId]);
|
|
112
|
+
const authIdentityId = (authRow.rows?.[0] ?? authRow[0]?.[0])?.id;
|
|
113
|
+
if (!authIdentityId)
|
|
114
|
+
return;
|
|
115
|
+
if (otpType === otp_verification_2.OtpPurpose.PHONE_VERIFICATION && customer.phone) {
|
|
116
|
+
// Check if phonepass provider identity already exists for this customer.
|
|
117
|
+
const existing = await knex.raw(`SELECT pi.id FROM provider_identity pi
|
|
118
|
+
WHERE pi.auth_identity_id = ? AND pi.provider = 'phonepass' LIMIT 1`, [authIdentityId]);
|
|
119
|
+
if (existing.rows?.[0] ?? existing[0]?.[0])
|
|
120
|
+
return;
|
|
121
|
+
// Copy password hash from emailpass identity (same scrypt format).
|
|
122
|
+
const hashRow = await knex.raw(`SELECT pi.provider_metadata FROM provider_identity pi
|
|
123
|
+
WHERE pi.auth_identity_id = ? AND pi.provider = 'emailpass' LIMIT 1`, [authIdentityId]);
|
|
124
|
+
const hash = (hashRow.rows?.[0] ?? hashRow[0]?.[0])?.provider_metadata?.password;
|
|
125
|
+
if (!hash)
|
|
126
|
+
return;
|
|
127
|
+
await authService.createProviderIdentities({
|
|
128
|
+
auth_identity_id: authIdentityId,
|
|
129
|
+
provider: "phonepass",
|
|
130
|
+
entity_id: customer.phone,
|
|
131
|
+
provider_metadata: { password: hash },
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (otpType === otp_verification_2.OtpPurpose.EMAIL_VERIFICATION && customer.email) {
|
|
135
|
+
// Check if emailpass provider identity already exists.
|
|
136
|
+
const existing = await knex.raw(`SELECT pi.id FROM provider_identity pi
|
|
137
|
+
WHERE pi.auth_identity_id = ? AND pi.provider = 'emailpass' LIMIT 1`, [authIdentityId]);
|
|
138
|
+
if (existing.rows?.[0] ?? existing[0]?.[0])
|
|
139
|
+
return;
|
|
140
|
+
// Copy password hash from phonepass identity.
|
|
141
|
+
const hashRow = await knex.raw(`SELECT pi.provider_metadata FROM provider_identity pi
|
|
142
|
+
WHERE pi.auth_identity_id = ? AND pi.provider = 'phonepass' LIMIT 1`, [authIdentityId]);
|
|
143
|
+
const hash = (hashRow.rows?.[0] ?? hashRow[0]?.[0])?.provider_metadata?.password;
|
|
144
|
+
if (!hash)
|
|
145
|
+
return;
|
|
146
|
+
await authService.createProviderIdentities({
|
|
147
|
+
auth_identity_id: authIdentityId,
|
|
148
|
+
provider: "emailpass",
|
|
149
|
+
entity_id: customer.email.toLowerCase(),
|
|
150
|
+
provider_metadata: { password: hash },
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL3N0b3JlL2N1c3RvbWVycy9vdHAvdmVyaWZ5L3JvdXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUVBLHFEQUlrQztBQUNsQywyRkFBb0c7QUFDcEcsOEVBQWlGO0FBRWpGLHNHQUE0RjtBQUM1Rix5RkFBdUU7QUFDdkUseUZBQXVFO0FBQ3ZFLGtEQUc4QjtBQUV2QixNQUFNLElBQUksR0FBRyxLQUFLLEVBQUUsR0FBa0IsRUFBRSxHQUFtQixFQUFFLEVBQUU7SUFDcEUsTUFBTSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsR0FBRyxHQUFHLENBQUMsSUFHM0IsQ0FBQTtJQUVELElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNYLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLG1CQUFtQixDQUNwQixDQUFBO0lBQ0gsQ0FBQztJQUVELElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUNWLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLHNCQUFzQixDQUN2QixDQUFBO0lBQ0gsQ0FBQztJQUVELDhCQUE4QjtJQUM5QixNQUFNLFVBQVUsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FDbEMsMENBQXVCLENBQ0UsQ0FBQTtJQUUzQiw2QkFBNkI7SUFDN0IsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsaUNBQXlCLENBQUMsYUFBYSxDQUFDLENBQUE7SUFDekUsTUFBTSxFQUFFLElBQUksRUFBRSxHQUFHLE1BQU0sQ0FBQyxhQUFhLENBQUE7SUFDckMsTUFBTSxTQUFTLEdBQUksSUFBSSxFQUFFLFNBQWdDLElBQUksYUFBYSxDQUFBO0lBRTFFLGFBQWE7SUFDYixNQUFNLFlBQVksR0FBRyxNQUFNLFVBQVUsQ0FBQyxTQUFTLENBQzdDO1FBQ0UsS0FBSztRQUNMLElBQUk7S0FDTCxFQUNELFNBQVMsQ0FDVixDQUFBO0lBRUQsTUFBTSxZQUFZLEdBQUcsSUFBQSwyQ0FBa0MsRUFBQyxNQUEyQixDQUFDLENBQUE7SUFFcEYsaUNBQWlDO0lBQ2pDLElBQUksTUFBVyxDQUFBO0lBRWYsSUFBSSxZQUFZLENBQUMsSUFBSSxLQUFLLDZCQUFVLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUN4RCxNQUFNLEVBQUUsTUFBTSxFQUFFLGNBQWMsRUFBRSxHQUFHLE1BQU0sSUFBQSxzQkFBbUIsRUFBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxDQUFDO1lBQzFFLEtBQUssRUFBRTtnQkFDTCxXQUFXLEVBQUUsWUFBWSxDQUFDLFdBQVc7YUFDdEM7U0FDRixDQUFDLENBQUE7UUFDRixNQUFNLEdBQUc7WUFDUCxRQUFRLEVBQUUsSUFBSTtZQUNkLFFBQVEsRUFBRSxjQUFjLENBQUMsUUFBUTtZQUNqQyxjQUFjLEVBQUUsY0FBYyxDQUFDLGNBQWM7U0FDOUMsQ0FBQTtJQUNILENBQUM7U0FBTSxJQUFJLFlBQVksQ0FBQyxJQUFJLEtBQUssNkJBQVUsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO1FBQy9ELE1BQU0sRUFBRSxNQUFNLEVBQUUsY0FBYyxFQUFFLEdBQUcsTUFBTSxJQUFBLHNCQUFtQixFQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQyxHQUFHLENBQUM7WUFDMUUsS0FBSyxFQUFFO2dCQUNMLFdBQVcsRUFBRSxZQUFZLENBQUMsV0FBVztnQkFDckMsZ0JBQWdCLEVBQUUsWUFBWSxDQUFDLEtBQUssQ0FBQyxVQUFVO2FBQ2hEO1NBQ0YsQ0FBQyxDQUFBO1FBQ0YsTUFBTSxHQUFHO1lBQ1AsUUFBUSxFQUFFLElBQUk7WUFDZCxRQUFRLEVBQUUsY0FBYyxDQUFDLFFBQVE7WUFDakMsY0FBYyxFQUFFLGNBQWMsQ0FBQyxjQUFjO1NBQzlDLENBQUE7SUFDSCxDQUFDO1NBQU0sQ0FBQztRQUNOLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLDJCQUEyQixDQUM1QixDQUFBO0lBQ0gsQ0FBQztJQUVELDBFQUEwRTtJQUMxRSwwRUFBMEU7SUFDMUUsd0RBQXdEO0lBQ3hELE1BQU0sK0JBQStCLENBQ25DLE1BQU0sQ0FBQyxRQUFRLEVBQUUsRUFBRSxJQUFJLEVBQUUsRUFDekIsTUFBTSxDQUFDLFFBQVEsSUFBSSxFQUFFLEVBQ3JCLFlBQVksQ0FBQyxJQUFJLEVBQ2pCLFlBQVksQ0FBQyxLQUFLLENBQUMsVUFBVSxFQUM3QixHQUFHLENBQUMsS0FBSyxDQUNWLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRTtRQUNYLHNFQUFzRTtRQUN0RSxtREFBbUQ7SUFDckQsQ0FBQyxDQUFDLENBQUE7SUFFRixJQUFJLFVBQThCLENBQUE7SUFDbEMsSUFBSSxDQUFDO1FBQ0gsTUFBTSxJQUFJLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsaUNBQXlCLENBQUMsYUFBYSxDQUFDLENBQUE7UUFDdkUsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsR0FBRyxDQUM1Qiw2RUFBNkUsRUFDN0UsQ0FBQyxNQUFNLENBQUMsUUFBUSxFQUFFLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FDNUIsQ0FBQTtRQUNELE1BQU0sY0FBYyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLE9BQU8sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFBO1FBRWpFLElBQUksY0FBYyxJQUFJLFNBQVMsRUFBRSxDQUFDO1lBQ2hDLE1BQU0sV0FBVyxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLGVBQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQTtZQUNuRCxNQUFNLFlBQVksR0FBRyxNQUFNLFdBQVcsQ0FBQyxvQkFBb0IsQ0FBQyxjQUFjLENBQUMsQ0FBQTtZQUMzRSxVQUFVLEdBQUcsTUFBTSxJQUFBLG9EQUErQixFQUNoRCxFQUFFLFlBQVksRUFBRSxTQUFTLEVBQUUsVUFBVSxFQUFFLEVBQ3ZDLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxTQUFTLEVBQUUsSUFBSSxDQUFDLFlBQVksSUFBSSxJQUFJLEVBQUUsQ0FDNUQsQ0FBQTtRQUNILENBQUM7SUFDSCxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AseUVBQXlFO0lBQzNFLENBQUM7SUFFRCxPQUFPLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDO1FBQzFCLEdBQUcsTUFBTTtRQUNULEtBQUssRUFBRSxVQUFVLElBQUksSUFBSTtRQUN6QixXQUFXLEVBQUUsQ0FBQyxVQUFVO0tBQ3pCLENBQUMsQ0FBQTtBQUNKLENBQUMsQ0FBQTtBQWxIWSxRQUFBLElBQUksUUFrSGhCO0FBRUQ7Ozs7Ozs7OztHQVNHO0FBQ0gsS0FBSyxVQUFVLCtCQUErQixDQUM1QyxVQUFrQixFQUNsQixRQUE0QyxFQUM1QyxPQUFtQixFQUNuQixlQUF1QixFQUN2QixLQUFzQjtJQUV0QixJQUFJLGVBQWUsS0FBSyxNQUFNO1FBQUUsT0FBTTtJQUN0QyxJQUFJLENBQUMsVUFBVTtRQUFFLE9BQU07SUFFdkIsTUFBTSxJQUFJLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxpQ0FBeUIsQ0FBQyxhQUFhLENBQUMsQ0FBQTtJQUNuRSxNQUFNLFdBQVcsR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLGVBQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQTtJQUUvQywyRUFBMkU7SUFDM0UsMEJBQTBCO0lBQzFCLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLEdBQUcsQ0FDNUIsNkVBQTZFLEVBQzdFLENBQUMsVUFBVSxDQUFDLENBQ2IsQ0FBQTtJQUNELE1BQU0sY0FBYyxHQUFHLENBQUMsT0FBTyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLE9BQU8sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFBO0lBQ2pFLElBQUksQ0FBQyxjQUFjO1FBQUUsT0FBTTtJQUUzQixJQUFJLE9BQU8sS0FBSyw2QkFBVSxDQUFDLGtCQUFrQixJQUFJLFFBQVEsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNoRSx5RUFBeUU7UUFDekUsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsR0FBRyxDQUM3QjsyRUFDcUUsRUFDckUsQ0FBQyxjQUFjLENBQUMsQ0FDakIsQ0FBQTtRQUNELElBQUksUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLFFBQVEsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztZQUFFLE9BQU07UUFFbEQsbUVBQW1FO1FBQ25FLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLEdBQUcsQ0FDNUI7MkVBQ3FFLEVBQ3JFLENBQUMsY0FBYyxDQUFDLENBQ2pCLENBQUE7UUFDRCxNQUFNLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLGlCQUFpQixFQUFFLFFBQVEsQ0FBQTtRQUNoRixJQUFJLENBQUMsSUFBSTtZQUFFLE9BQU07UUFFakIsTUFBTSxXQUFXLENBQUMsd0JBQXdCLENBQUM7WUFDekMsZ0JBQWdCLEVBQUUsY0FBYztZQUNoQyxRQUFRLEVBQUUsV0FBVztZQUNyQixTQUFTLEVBQUUsUUFBUSxDQUFDLEtBQUs7WUFDekIsaUJBQWlCLEVBQUUsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFO1NBQ3RDLENBQUMsQ0FBQTtJQUNKLENBQUM7SUFFRCxJQUFJLE9BQU8sS0FBSyw2QkFBVSxDQUFDLGtCQUFrQixJQUFJLFFBQVEsQ0FBQyxLQUFLLEVBQUUsQ0FBQztRQUNoRSx1REFBdUQ7UUFDdkQsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsR0FBRyxDQUM3QjsyRUFDcUUsRUFDckUsQ0FBQyxjQUFjLENBQUMsQ0FDakIsQ0FBQTtRQUNELElBQUksUUFBUSxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLFFBQVEsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztZQUFFLE9BQU07UUFFbEQsOENBQThDO1FBQzlDLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxDQUFDLEdBQUcsQ0FDNUI7MkVBQ3FFLEVBQ3JFLENBQUMsY0FBYyxDQUFDLENBQ2pCLENBQUE7UUFDRCxNQUFNLElBQUksR0FBRyxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxPQUFPLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLGlCQUFpQixFQUFFLFFBQVEsQ0FBQTtRQUNoRixJQUFJLENBQUMsSUFBSTtZQUFFLE9BQU07UUFFakIsTUFBTSxXQUFXLENBQUMsd0JBQXdCLENBQUM7WUFDekMsZ0JBQWdCLEVBQUUsY0FBYztZQUNoQyxRQUFRLEVBQUUsV0FBVztZQUNyQixTQUFTLEVBQUUsUUFBUSxDQUFDLEtBQUssQ0FBQyxXQUFXLEVBQUU7WUFDdkMsaUJBQWlCLEVBQUUsRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFO1NBQ3RDLENBQUMsQ0FBQTtJQUNKLENBQUM7QUFDSCxDQUFDIn0=
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.POST = void 0;
|
|
4
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
+
const core_flows_1 = require("@medusajs/core-flows");
|
|
6
|
+
const config_1 = require("../../../config");
|
|
7
|
+
/**
|
|
8
|
+
* Override of Medusa's built-in POST /store/customers.
|
|
9
|
+
*
|
|
10
|
+
* Medusa's default implementation calls validateCustomerAccountCreation which
|
|
11
|
+
* throws when email is missing. When registration.identifier is "phone" we
|
|
12
|
+
* must bypass that step and create the customer with phone only.
|
|
13
|
+
*
|
|
14
|
+
* For "email" and "both" we delegate to the built-in workflow so that all of
|
|
15
|
+
* Medusa's existing validations (duplicate email check, etc.) are preserved.
|
|
16
|
+
*/
|
|
17
|
+
const POST = async (req, res) => {
|
|
18
|
+
const authContext = req.auth_context;
|
|
19
|
+
if (authContext?.actor_id) {
|
|
20
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Request already authenticated as a customer.");
|
|
21
|
+
}
|
|
22
|
+
const configModule = req.scope.resolve(utils_1.ContainerRegistrationKeys.CONFIG_MODULE);
|
|
23
|
+
const options = (0, config_1.resolveCustomerRegistrationOptions)(configModule);
|
|
24
|
+
const { identifier } = options.registration;
|
|
25
|
+
const authIdentityId = authContext?.auth_identity_id;
|
|
26
|
+
const customerData = (req.body ?? {});
|
|
27
|
+
if (identifier !== "phone") {
|
|
28
|
+
// "email" or "both": delegate to Medusa's built-in workflow which enforces
|
|
29
|
+
// the email requirement and handles duplicate detection.
|
|
30
|
+
const { result } = await (0, core_flows_1.createCustomerAccountWorkflow)(req.scope).run({
|
|
31
|
+
input: {
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
33
|
+
customerData: customerData,
|
|
34
|
+
authIdentityId: authIdentityId ?? "",
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
const customer = await refetchCustomer(result.id, req.scope);
|
|
38
|
+
return res.status(200).json({ customer });
|
|
39
|
+
}
|
|
40
|
+
// "phone": create customer without email validation.
|
|
41
|
+
if (!customerData.phone) {
|
|
42
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "Phone is required for registration");
|
|
43
|
+
}
|
|
44
|
+
const customerService = req.scope.resolve(utils_1.Modules.CUSTOMER);
|
|
45
|
+
// Check for duplicate phone
|
|
46
|
+
const knex = req.scope.resolve(utils_1.ContainerRegistrationKeys.PG_CONNECTION);
|
|
47
|
+
const existing = await knex.raw(`SELECT id FROM customer WHERE phone = ? AND has_account = true LIMIT 1`, [customerData.phone]);
|
|
48
|
+
if (existing.rows?.[0] ?? existing[0]?.[0]) {
|
|
49
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.DUPLICATE_ERROR, "A customer with this phone number already has an account");
|
|
50
|
+
}
|
|
51
|
+
const [customer] = await customerService.createCustomers([
|
|
52
|
+
{
|
|
53
|
+
...customerData,
|
|
54
|
+
has_account: !!authIdentityId,
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
// Link the auth identity to the new customer (same as setAuthAppMetadataStep)
|
|
58
|
+
if (authIdentityId) {
|
|
59
|
+
const authService = req.scope.resolve(utils_1.Modules.AUTH);
|
|
60
|
+
await authService.updateAuthIdentities({
|
|
61
|
+
id: authIdentityId,
|
|
62
|
+
app_metadata: { customer_id: customer.id },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const fullCustomer = await refetchCustomer(customer.id, req.scope);
|
|
66
|
+
return res.status(200).json({ customer: fullCustomer });
|
|
67
|
+
};
|
|
68
|
+
exports.POST = POST;
|
|
69
|
+
async function refetchCustomer(customerId, scope) {
|
|
70
|
+
const remoteQuery = scope.resolve(utils_1.ContainerRegistrationKeys.REMOTE_QUERY);
|
|
71
|
+
const queryObject = (0, utils_1.remoteQueryObjectFromString)({
|
|
72
|
+
entryPoint: "customer",
|
|
73
|
+
variables: { filters: { id: customerId } },
|
|
74
|
+
fields: [
|
|
75
|
+
"id",
|
|
76
|
+
"email",
|
|
77
|
+
"first_name",
|
|
78
|
+
"last_name",
|
|
79
|
+
"phone",
|
|
80
|
+
"has_account",
|
|
81
|
+
"metadata",
|
|
82
|
+
"created_at",
|
|
83
|
+
"updated_at",
|
|
84
|
+
],
|
|
85
|
+
});
|
|
86
|
+
const customers = await remoteQuery(queryObject);
|
|
87
|
+
return customers[0];
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL3N0b3JlL2N1c3RvbWVycy9yb3V0ZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFFQSxxREFLa0M7QUFDbEMscURBQW9FO0FBQ3BFLDRDQUd3QjtBQUV4Qjs7Ozs7Ozs7O0dBU0c7QUFDSSxNQUFNLElBQUksR0FBRyxLQUFLLEVBQUUsR0FBa0IsRUFBRSxHQUFtQixFQUFFLEVBQUU7SUFDcEUsTUFBTSxXQUFXLEdBQ2YsR0FHRCxDQUFDLFlBQVksQ0FBQTtJQUVkLElBQUksV0FBVyxFQUFFLFFBQVEsRUFBRSxDQUFDO1FBQzFCLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLDhDQUE4QyxDQUMvQyxDQUFBO0lBQ0gsQ0FBQztJQUVELE1BQU0sWUFBWSxHQUFHLEdBQUcsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLGlDQUF5QixDQUFDLGFBQWEsQ0FBQyxDQUFBO0lBQy9FLE1BQU0sT0FBTyxHQUFHLElBQUEsMkNBQWtDLEVBQUMsWUFBaUMsQ0FBQyxDQUFBO0lBQ3JGLE1BQU0sRUFBRSxVQUFVLEVBQUUsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFBO0lBRTNDLE1BQU0sY0FBYyxHQUFHLFdBQVcsRUFBRSxnQkFBZ0IsQ0FBQTtJQUNwRCxNQUFNLFlBQVksR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLElBQUksRUFBRSxDQUE0QixDQUFBO0lBRWhFLElBQUksVUFBVSxLQUFLLE9BQU8sRUFBRSxDQUFDO1FBQzNCLDJFQUEyRTtRQUMzRSx5REFBeUQ7UUFDekQsTUFBTSxFQUFFLE1BQU0sRUFBRSxHQUFHLE1BQU0sSUFBQSwwQ0FBNkIsRUFBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsR0FBRyxDQUFDO1lBQ3BFLEtBQUssRUFBRTtnQkFDTCw4REFBOEQ7Z0JBQzlELFlBQVksRUFBRSxZ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
|
|
@@ -9,19 +9,50 @@ exports.resolveCustomerRegistrationOptions = resolveCustomerRegistrationOptions;
|
|
|
9
9
|
const utils_1 = require("@medusajs/framework/utils");
|
|
10
10
|
const PLUGIN_NAME = "customer-registration";
|
|
11
11
|
function normalizeCustomerRegistrationOptions(input) {
|
|
12
|
-
|
|
13
|
-
return null;
|
|
14
|
-
}
|
|
12
|
+
const base = input ?? {};
|
|
15
13
|
// Password reset template is required if password_reset is configured
|
|
16
|
-
if (
|
|
14
|
+
if (base.password_reset && !base.password_reset.template) {
|
|
17
15
|
throw new Error("customer-registration: password_reset.template is required when password_reset is configured");
|
|
18
16
|
}
|
|
17
|
+
if (base.password_reset && !base.storefrontUrl) {
|
|
18
|
+
throw new Error("[customer-registration] storefrontUrl is required when password_reset is configured — " +
|
|
19
|
+
"the reset email link will be malformed without it");
|
|
20
|
+
}
|
|
21
|
+
if (base.account_deletion_request && !base.account_deletion_request.template) {
|
|
22
|
+
throw new Error("customer-registration: account_deletion_request.template is required when account_deletion_request is configured");
|
|
23
|
+
}
|
|
24
|
+
if (base.account_deletion_cancel && !base.account_deletion_cancel.template) {
|
|
25
|
+
throw new Error("customer-registration: account_deletion_cancel.template is required when account_deletion_cancel is configured");
|
|
26
|
+
}
|
|
27
|
+
const registrationIdentifier = base.registration?.identifier ?? "email";
|
|
19
28
|
return {
|
|
20
|
-
storefrontUrl:
|
|
21
|
-
|
|
29
|
+
storefrontUrl: base.storefrontUrl,
|
|
30
|
+
registration: {
|
|
31
|
+
identifier: registrationIdentifier,
|
|
32
|
+
require_verification: base.registration?.require_verification ?? true,
|
|
33
|
+
},
|
|
34
|
+
login: {
|
|
35
|
+
identifier: base.login?.identifier ?? registrationIdentifier,
|
|
36
|
+
},
|
|
37
|
+
password_reset: base.password_reset
|
|
38
|
+
? {
|
|
39
|
+
template: base.password_reset.template,
|
|
40
|
+
subject: base.password_reset.subject || "Reset Your Password",
|
|
41
|
+
}
|
|
42
|
+
: undefined,
|
|
43
|
+
account_deletion_request: base.account_deletion_request
|
|
44
|
+
? {
|
|
45
|
+
template: base.account_deletion_request.template,
|
|
46
|
+
subject: base.account_deletion_request.subject ||
|
|
47
|
+
"Confirm your account deletion request",
|
|
48
|
+
scheduled_days: base.account_deletion_request.scheduled_days ?? 7,
|
|
49
|
+
}
|
|
50
|
+
: undefined,
|
|
51
|
+
account_deletion_cancel: base.account_deletion_cancel
|
|
22
52
|
? {
|
|
23
|
-
template:
|
|
24
|
-
subject:
|
|
53
|
+
template: base.account_deletion_cancel.template,
|
|
54
|
+
subject: base.account_deletion_cancel.subject ||
|
|
55
|
+
"Confirm cancellation of account deletion",
|
|
25
56
|
}
|
|
26
57
|
: undefined,
|
|
27
58
|
};
|
|
@@ -35,8 +66,8 @@ function resolveCustomerRegistrationOptions(configModule) {
|
|
|
35
66
|
for (const plugin of plugins) {
|
|
36
67
|
if ((0, utils_1.isString)(plugin)) {
|
|
37
68
|
if (plugin === PLUGIN_NAME) {
|
|
38
|
-
// String plugin without options
|
|
39
|
-
return null;
|
|
69
|
+
// String plugin without options — resolve with all defaults
|
|
70
|
+
return normalizeCustomerRegistrationOptions(null);
|
|
40
71
|
}
|
|
41
72
|
continue;
|
|
42
73
|
}
|
|
@@ -44,7 +75,7 @@ function resolveCustomerRegistrationOptions(configModule) {
|
|
|
44
75
|
return normalizeCustomerRegistrationOptions(plugin.options);
|
|
45
76
|
}
|
|
46
77
|
}
|
|
47
|
-
// Plugin not found
|
|
48
|
-
return null;
|
|
78
|
+
// Plugin not found in config — return all defaults so callers never get null
|
|
79
|
+
return normalizeCustomerRegistrationOptions(null);
|
|
49
80
|
}
|
|
50
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
81
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2NvbmZpZy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7OztHQUdHOztBQStESCxvRkFrRUM7QUFFRCxnRkF5QkM7QUExSkQscURBQW9EO0FBS3BELE1BQU0sV0FBVyxHQUFHLHVCQUF1QixDQUFBO0FBd0QzQyxTQUFnQixvQ0FBb0MsQ0FDbEQsS0FBZ0Q7SUFFaEQsTUFBTSxJQUFJLEdBQUcsS0FBSyxJQUFJLEVBQUUsQ0FBQTtJQUV4QixzRUFBc0U7SUFDdEUsSUFBSSxJQUFJLENBQUMsY0FBYyxJQUFJLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUN6RCxNQUFNLElBQUksS0FBSyxDQUNiLDhGQUE4RixDQUMvRixDQUFBO0lBQ0gsQ0FBQztJQUVELElBQUksSUFBSSxDQUFDLGNBQWMsSUFBSSxDQUFDLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztRQUMvQyxNQUFNLElBQUksS0FBSyxDQUNiLHdGQUF3RjtZQUN0RixtREFBbUQsQ0FDdEQsQ0FBQTtJQUNILENBQUM7SUFFRCxJQUFJLElBQUksQ0FBQyx3QkFBd0IsSUFBSSxDQUFDLElBQUksQ0FBQyx3QkFBd0IsQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUM3RSxNQUFNLElBQUksS0FBSyxDQUNiLGtIQUFrSCxDQUNuSCxDQUFBO0lBQ0gsQ0FBQztJQUNELElBQUksSUFBSSxDQUFDLHVCQUF1QixJQUFJLENBQUMsSUFBSSxDQUFDLHVCQUF1QixDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQzNFLE1BQU0sSUFBSSxLQUFLLENBQ2IsZ0hBQWdILENBQ2pILENBQUE7SUFDSCxDQUFDO0lBRUQsTUFBTSxzQkFBc0IsR0FBb0IsSUFBSSxDQUFDLFlBQVksRUFBRSxVQUFVLElBQUksT0FBTyxDQUFBO0lBRXhGLE9BQU87UUFDTCxhQUFhLEVBQUUsSUFBSSxDQUFDLGFBQWE7UUFDakMsWUFBWSxFQUFFO1lBQ1osVUFBVSxFQUFFLHNCQUFzQjtZQUNsQyxvQkFBb0IsRUFBRSxJQUFJLENBQUMsWUFBWSxFQUFFLG9CQUFvQixJQUFJLElBQUk7U0FDdEU7UUFDRCxLQUFLLEVBQUU7WUFDTCxVQUFVLEVBQUUsSUFBSSxDQUFDLEtBQUssRUFBRSxVQUFVLElBQUksc0JBQXNCO1NBQzdEO1FBQ0QsY0FBYyxFQUFFLElBQUksQ0FBQyxjQUFjO1lBQ2pDLENBQUMsQ0FBQztnQkFDRSxRQUFRLEVBQUUsSUFBSSxDQUFDLGNBQWMsQ0FBQyxRQUFRO2dCQUN0QyxPQUFPLEVBQUUsSUFBSSxDQUFDLGNBQWMsQ0FBQyxPQUFPLElBQUkscUJBQXFCO2FBQzlEO1lBQ0gsQ0FBQyxDQUFDLFNBQVM7UUFDYix3QkFBd0IsRUFBRSxJQUFJLENBQUMsd0JBQXdCO1lBQ3JELENBQUMsQ0FBQztnQkFDRSxRQUFRLEVBQUUsSUFBSSxDQUFDLHdCQUF3QixDQUFDLFFBQVE7Z0JBQ2hELE9BQU8sRUFDTCxJQUFJLENBQUMsd0JBQXdCLENBQUMsT0FBTztvQkFDckMsdUNBQXVDO2dCQUN6QyxjQUFjLEVBQ1osSUFBSSxDQUFDLHdCQUF3QixDQUFDLGNBQWMsSUFBSSxDQUFDO2FBQ3BEO1lBQ0gsQ0FBQyxDQUFDLFNBQVM7UUFDYix1QkFBdUIsRUFBRSxJQUFJLENBQUMsdUJBQXVCO1lBQ25ELENBQUMsQ0FBQztnQkFDRSxRQUFRLEVBQUUsSUFBSSxDQUFDLHVCQUF1QixDQUFDLFFBQVE7Z0JBQy9DLE9BQU8sRUFDTCxJQUFJLENBQUMsdUJBQXVCLENBQUMsT0FBTztvQkFDcEMsMENBQTBDO2FBQzdDO1lBQ0gsQ0FBQyxDQUFDLFNBQVM7S0FDZCxDQUFBO0FBQ0gsQ0FBQztBQUVELFNBQWdCLGtDQUFrQyxDQUNoRCxZQUFnQztJQUVoQyw2REFBNkQ7SUFDN0QsTUFBTSxjQUFjLEdBQUcsWUFBWSxFQUFFLGFBQWEsRUFBRSxPQUFPLElBQUksRUFBRSxDQUFBO0lBQ2pFLHNDQUFzQztJQUN0QyxNQUFNLGFBQWEsR0FBRyxZQUFZLEVBQUUsT0FBTyxJQUFJLEVBQUUsQ0FBQTtJQUNqRCxNQUFNLE9BQU8sR0FBRyxDQUFDLEdBQUcsY0FBYyxFQUFFLEdBQUcsYUFBYSxDQUFDLENBQUE7SUFFckQsS0FBSyxNQUFNLE1BQU0sSUFBSSxPQUFPLEVBQUUsQ0FBQztRQUM3QixJQUFJLElBQUEsZ0JBQVEsRUFBQyxNQUFNLENBQUMsRUFBRSxDQUFDO1lBQ3JCLElBQUksTUFBTSxLQUFLLFdBQVcsRUFBRSxDQUFDO2dCQUMzQiw0REFBNEQ7Z0JBQzVELE9BQU8sb0NBQW9DLENBQUMsSUFBSSxDQUFDLENBQUE7WUFDbkQsQ0FBQztZQUNELFNBQVE7UUFDVixDQUFDO1FBRUQsSUFBSSxNQUFNLEVBQUUsT0FBTyxLQUFLLFdBQVcsRUFBRSxDQUFDO1lBQ3BDLE9BQU8sb0NBQW9DLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFBO1FBQzdELENBQUM7SUFDSCxDQUFDO0lBRUQsNkVBQTZFO0lBQzdFLE9BQU8sb0NBQW9DLENBQUMsSUFBSSxDQUFDLENBQUE7QUFDbkQsQ0FBQyJ9
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.config = void 0;
|
|
4
|
+
exports.default = processAccountDeletions;
|
|
5
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
6
|
+
const account_deletion_request_1 = require("../modules/account-deletion-request");
|
|
7
|
+
const BATCH_SIZE = 50;
|
|
8
|
+
async function processAccountDeletions(container) {
|
|
9
|
+
const logger = (container.resolve(utils_1.ContainerRegistrationKeys.LOGGER) ?? {
|
|
10
|
+
info: console.log,
|
|
11
|
+
warn: console.warn,
|
|
12
|
+
error: console.error,
|
|
13
|
+
debug: console.debug,
|
|
14
|
+
});
|
|
15
|
+
const accountDeletionService = container.resolve(account_deletion_request_1.ACCOUNT_DELETION_REQUEST_MODULE);
|
|
16
|
+
const authService = container.resolve(utils_1.Modules.AUTH);
|
|
17
|
+
const customerService = container.resolve(utils_1.Modules.CUSTOMER);
|
|
18
|
+
const dueRequests = await accountDeletionService.listDueForDeletion(BATCH_SIZE);
|
|
19
|
+
if (dueRequests.length === 0) {
|
|
20
|
+
logger.debug("[process-account-deletions] No requests due for deletion");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
logger.info("[process-account-deletions] Processing requests", {
|
|
24
|
+
count: dueRequests.length,
|
|
25
|
+
});
|
|
26
|
+
let processed = 0;
|
|
27
|
+
const failed = [];
|
|
28
|
+
for (const request of dueRequests) {
|
|
29
|
+
const { id: requestId, customer_id: customerId } = request;
|
|
30
|
+
try {
|
|
31
|
+
// 1. List auth identities for this customer
|
|
32
|
+
const authIdentities = await authService.listAuthIdentities({ app_metadata: { customer_id: customerId } }, { take: 10 });
|
|
33
|
+
const authIds = Array.isArray(authIdentities)
|
|
34
|
+
? authIdentities.map((a) => a.id)
|
|
35
|
+
: [];
|
|
36
|
+
if (authIds.length > 0) {
|
|
37
|
+
await authService.deleteAuthIdentities(authIds);
|
|
38
|
+
}
|
|
39
|
+
// 2. Delete customer
|
|
40
|
+
await customerService.deleteCustomers([customerId]);
|
|
41
|
+
// 3. Mark request as completed
|
|
42
|
+
await accountDeletionService.markCompleted(requestId);
|
|
43
|
+
processed++;
|
|
44
|
+
logger.debug("[process-account-deletions] Deleted customer", {
|
|
45
|
+
requestId,
|
|
46
|
+
customerId,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
51
|
+
failed.push({ requestId, customerId, error: message });
|
|
52
|
+
logger.error("[process-account-deletions] Failed to process request", {
|
|
53
|
+
requestId,
|
|
54
|
+
customerId,
|
|
55
|
+
error: message,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
logger.info("[process-account-deletions] Job finished", {
|
|
60
|
+
processed,
|
|
61
|
+
failed: failed.length,
|
|
62
|
+
failedCustomerIds: failed.map((f) => f.customerId),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
exports.config = {
|
|
66
|
+
name: "process-account-deletions",
|
|
67
|
+
schedule: "0 * * * *", // Every hour at minute 0
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvY2Vzcy1hY2NvdW50LWRlbGV0aW9ucy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9qb2JzL3Byb2Nlc3MtYWNjb3VudC1kZWxldGlvbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBY0EsMENBZ0ZDO0FBN0ZELHFEQUE4RTtBQUM5RSxrRkFBcUY7QUFHckYsTUFBTSxVQUFVLEdBQUcsRUFBRSxDQUFBO0FBU04sS0FBSyxVQUFVLHVCQUF1QixDQUNuRCxTQUEwQjtJQUUxQixNQUFNLE1BQU0sR0FBRyxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsaUNBQXlCLENBQUMsTUFBTSxDQUFDLElBQUk7UUFDckUsSUFBSSxFQUFFLE9BQU8sQ0FBQyxHQUFHO1FBQ2pCLElBQUksRUFBRSxPQUFPLENBQUMsSUFBSTtRQUNsQixLQUFLLEVBQUUsT0FBTyxDQUFDLEtBQUs7UUFDcEIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxLQUFLO0tBQ3JCLENBQVcsQ0FBQTtJQUVaLE1BQU0sc0JBQXNCLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FDOUMsMERBQStCLENBQ2hDLENBQUE7SUFDRCxNQUFNLFdBQVcsR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLGVBQU8sQ0FBQyxJQUFJLENBTWpELENBQUE7SUFDRCxNQUFNLGVBQWUsR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFDLGVBQU8sQ0FBQyxRQUFRLENBRXpELENBQUE7SUFFRCxNQUFNLFdBQVcsR0FBRyxNQUFNLHNCQUFzQixDQUFDLGtCQUFrQixDQUFDLFVBQVUsQ0FBQyxDQUFBO0lBRS9FLElBQUksV0FBVyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUM3QixNQUFNLENBQUMsS0FBSyxDQUFDLDBEQUEwRCxDQUFDLENBQUE7UUFDeEUsT0FBTTtJQUNSLENBQUM7SUFFRCxNQUFNLENBQUMsSUFBSSxDQUFDLGlEQUFpRCxFQUFFO1FBQzdELEtBQUssRUFBRSxXQUFXLENBQUMsTUFBTTtLQUMxQixDQUFDLENBQUE7SUFFRixJQUFJLFNBQVMsR0FBRyxDQUFDLENBQUE7SUFDakIsTUFBTSxNQUFNLEdBQStELEVBQUUsQ0FBQTtJQUU3RSxLQUFLLE1BQU0sT0FBTyxJQUFJLFdBQVcsRUFBRSxDQUFDO1FBQ2xDLE1BQU0sRUFBRSxFQUFFLEVBQUUsU0FBUyxFQUFFLFdBQVcsRUFBRSxVQUFVLEVBQUUsR0FBRyxPQUFPLENBQUE7UUFDMUQsSUFBSSxDQUFDO1lBQ0gsNENBQTRDO1lBQzVDLE1BQU0sY0FBYyxHQUFHLE1BQU0sV0FBVyxDQUFDLGtCQUFrQixDQUN6RCxFQUFFLFlBQVksRUFBRSxFQUFFLFdBQVcsRUFBRSxVQUFVLEVBQUUsRUFBRSxFQUM3QyxFQUFFLElBQUksRUFBRSxFQUFFLEVBQUUsQ0FDYixDQUFBO1lBQ0QsTUFBTSxPQUFPLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxjQUFjLENBQUM7Z0JBQzNDLENBQUMsQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO2dCQUNqQyxDQUFDLENBQUMsRUFBRSxDQUFBO1lBRU4sSUFBSSxPQUFPLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUN2QixNQUFNLFdBQVcsQ0FBQyxvQkFBb0IsQ0FBQyxPQUFPLENBQUMsQ0FBQTtZQUNqRCxDQUFDO1lBRUQscUJBQXFCO1lBQ3JCLE1BQU0sZUFBZSxDQUFDLGVBQWUsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUE7WUFFbkQsK0JBQStCO1lBQy9CLE1BQU0sc0JBQXNCLENBQUMsYUFBYSxDQUFDLFNBQVMsQ0FBQyxDQUFBO1lBQ3JELFNBQVMsRUFBRSxDQUFBO1lBQ1gsTUFBTSxDQUFDLEtBQUssQ0FBQyw4Q0FBOEMsRUFBRTtnQkFDM0QsU0FBUztnQkFDVCxVQUFVO2FBQ1gsQ0FBQyxDQUFBO1FBQ0osQ0FBQztRQUFDLE9BQU8sR0FBRyxFQUFFLENBQUM7WUFDYixNQUFNLE9BQU8sR0FBRyxHQUFHLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUE7WUFDaEUsTUFBTSxDQUFDLElBQUksQ0FBQyxFQUFFLFNBQVMsRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUE7WUFDdEQsTUFBTSxDQUFDLEtBQUssQ0FBQyx1REFBdUQsRUFBRTtnQkFDcEUsU0FBUztnQkFDVCxVQUFVO2dCQUNWLEtBQUssRUFBRSxPQUFPO2FBQ2YsQ0FBQyxDQUFBO1FBQ0osQ0FBQztJQUNILENBQUM7SUFFRCxNQUFNLENBQUMsSUFBSSxDQUFDLDBDQUEwQyxFQUFFO1FBQ3RELFNBQVM7UUFDVCxNQUFNLEVBQUUsTUFBTSxDQUFDLE1BQU07UUFDckIsaUJBQWlCLEVBQUUsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQztLQUNuRCxDQUFDLENBQUE7QUFDSixDQUFDO0FBRVksUUFBQSxNQUFNLEdBQUc7SUFDcEIsSUFBSSxFQUFFLDJCQUEyQjtJQUNqQyxRQUFRLEVBQUUsV0FBVyxFQUFFLHlCQUF5QjtDQUNqRCxDQUFBIn0=
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AccountDeletionRequestStatus = exports.AccountDeletionRequest = exports.AccountDeletionRequestService = exports.ACCOUNT_DELETION_REQUEST_MODULE = void 0;
|
|
7
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
8
|
+
const service_1 = __importDefault(require("./service"));
|
|
9
|
+
exports.AccountDeletionRequestService = service_1.default;
|
|
10
|
+
exports.ACCOUNT_DELETION_REQUEST_MODULE = "account_deletion_request";
|
|
11
|
+
exports.default = (0, utils_1.Module)(exports.ACCOUNT_DELETION_REQUEST_MODULE, {
|
|
12
|
+
service: service_1.default,
|
|
13
|
+
});
|
|
14
|
+
var account_deletion_request_1 = require("./models/account-deletion-request");
|
|
15
|
+
Object.defineProperty(exports, "AccountDeletionRequest", { enumerable: true, get: function () { return account_deletion_request_1.AccountDeletionRequest; } });
|
|
16
|
+
Object.defineProperty(exports, "AccountDeletionRequestStatus", { enumerable: true, get: function () { return account_deletion_request_1.AccountDeletionRequestStatus; } });
|
|
17
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9hY2NvdW50LWRlbGV0aW9uLXJlcXVlc3QvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7O0FBQUEscURBQWtEO0FBQ2xELHdEQUFxRDtBQVE1Qyx3Q0FSRixpQkFBNkIsQ0FRRTtBQU56QixRQUFBLCtCQUErQixHQUFHLDBCQUEwQixDQUFBO0FBRXpFLGtCQUFlLElBQUEsY0FBTSxFQUFDLHVDQUErQixFQUFFO0lBQ3JELE9BQU8sRUFBRSxpQkFBNkI7Q0FDdkMsQ0FBQyxDQUFBO0FBR0YsOEVBSTBDO0FBSHhDLGtJQUFBLHNCQUFzQixPQUFBO0FBQ3RCLHdJQUFBLDRCQUE0QixPQUFBIn0=
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Migration20250221000000CreateAccountDeletionRequestTable = void 0;
|
|
4
|
+
const migrations_1 = require("@mikro-orm/migrations");
|
|
5
|
+
class Migration20250221000000CreateAccountDeletionRequestTable extends migrations_1.Migration {
|
|
6
|
+
async up() {
|
|
7
|
+
this.addSql(`
|
|
8
|
+
CREATE TABLE "account_deletion_request" (
|
|
9
|
+
"id" TEXT NOT NULL,
|
|
10
|
+
"customer_id" TEXT NOT NULL,
|
|
11
|
+
"reason" TEXT NULL,
|
|
12
|
+
"deletion_scheduled_at" TIMESTAMP WITH TIME ZONE NULL,
|
|
13
|
+
"status" TEXT NOT NULL DEFAULT 'pending' CHECK ("status" IN ('pending', 'confirmed', 'cancelled')),
|
|
14
|
+
"cancelled_at" TIMESTAMP WITH TIME ZONE NULL,
|
|
15
|
+
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
|
16
|
+
"updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
|
17
|
+
"deleted_at" TIMESTAMP WITH TIME ZONE NULL,
|
|
18
|
+
CONSTRAINT "account_deletion_request_pkey" PRIMARY KEY ("id")
|
|
19
|
+
);
|
|
20
|
+
`);
|
|
21
|
+
this.addSql(`
|
|
22
|
+
CREATE INDEX "IDX_account_deletion_request_customer_id"
|
|
23
|
+
ON "account_deletion_request" ("customer_id");
|
|
24
|
+
`);
|
|
25
|
+
this.addSql(`
|
|
26
|
+
CREATE INDEX "IDX_account_deletion_request_status"
|
|
27
|
+
ON "account_deletion_request" ("status");
|
|
28
|
+
`);
|
|
29
|
+
this.addSql(`
|
|
30
|
+
CREATE INDEX "IDX_account_deletion_request_created_at"
|
|
31
|
+
ON "account_deletion_request" ("created_at");
|
|
32
|
+
`);
|
|
33
|
+
}
|
|
34
|
+
async down() {
|
|
35
|
+
this.addSql(`DROP INDEX IF EXISTS "IDX_account_deletion_request_created_at";`);
|
|
36
|
+
this.addSql(`DROP INDEX IF EXISTS "IDX_account_deletion_request_status";`);
|
|
37
|
+
this.addSql(`DROP INDEX IF EXISTS "IDX_account_deletion_request_customer_id";`);
|
|
38
|
+
this.addSql(`DROP TABLE IF EXISTS "account_deletion_request";`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.Migration20250221000000CreateAccountDeletionRequestTable = Migration20250221000000CreateAccountDeletionRequestTable;
|
|
42
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uMjAyNTAyMjEwMDAwMDBDcmVhdGVBY2NvdW50RGVsZXRpb25SZXF1ZXN0VGFibGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9hY2NvdW50LWRlbGV0aW9uLXJlcXVlc3QvbWlncmF0aW9ucy9NaWdyYXRpb24yMDI1MDIyMTAwMDAwMENyZWF0ZUFjY291bnREZWxldGlvblJlcXVlc3RUYWJsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxzREFBaUQ7QUFFakQsTUFBYSx3REFBeUQsU0FBUSxzQkFBUztJQUNyRixLQUFLLENBQUMsRUFBRTtRQUNOLElBQUksQ0FBQyxNQUFNLENBQUM7Ozs7Ozs7Ozs7Ozs7S0FhWCxDQUFDLENBQUE7UUFDRixJQUFJLENBQUMsTUFBTSxDQUFDOzs7S0FHWCxDQUFDLENBQUE7UUFDRixJQUFJLENBQUMsTUFBTSxDQUFDOzs7S0FHWCxDQUFDLENBQUE7UUFDRixJQUFJLENBQUMsTUFBTSxDQUFDOzs7S0FHWCxDQUFDLENBQUE7SUFDSixDQUFDO0lBRUQsS0FBSyxDQUFDLElBQUk7UUFDUixJQUFJLENBQUMsTUFBTSxDQUFDLGlFQUFpRSxDQUFDLENBQUE7UUFDOUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyw2REFBNkQsQ0FBQyxDQUFBO1FBQzFFLElBQUksQ0FBQyxNQUFNLENBQUMsa0VBQWtFLENBQUMsQ0FBQTtRQUMvRSxJQUFJLENBQUMsTUFBTSxDQUFDLGtEQUFrRCxDQUFDLENBQUE7SUFDakUsQ0FBQztDQUNGO0FBcENELDRIQW9DQyJ9
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Migration20250221100000AddCompletedStatusToAccountDeletionRequest = void 0;
|
|
4
|
+
const migrations_1 = require("@mikro-orm/migrations");
|
|
5
|
+
class Migration20250221100000AddCompletedStatusToAccountDeletionRequest extends migrations_1.Migration {
|
|
6
|
+
async up() {
|
|
7
|
+
this.addSql(`
|
|
8
|
+
ALTER TABLE "account_deletion_request"
|
|
9
|
+
DROP CONSTRAINT IF EXISTS "account_deletion_request_status_check";
|
|
10
|
+
`);
|
|
11
|
+
this.addSql(`
|
|
12
|
+
ALTER TABLE "account_deletion_request"
|
|
13
|
+
ADD CONSTRAINT "account_deletion_request_status_check"
|
|
14
|
+
CHECK ("status" IN ('pending', 'confirmed', 'cancelled', 'completed'));
|
|
15
|
+
`);
|
|
16
|
+
}
|
|
17
|
+
async down() {
|
|
18
|
+
this.addSql(`
|
|
19
|
+
ALTER TABLE "account_deletion_request"
|
|
20
|
+
DROP CONSTRAINT IF EXISTS "account_deletion_request_status_check";
|
|
21
|
+
`);
|
|
22
|
+
this.addSql(`
|
|
23
|
+
ALTER TABLE "account_deletion_request"
|
|
24
|
+
ADD CONSTRAINT "account_deletion_request_status_check"
|
|
25
|
+
CHECK ("status" IN ('pending', 'confirmed', 'cancelled'));
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.Migration20250221100000AddCompletedStatusToAccountDeletionRequest = Migration20250221100000AddCompletedStatusToAccountDeletionRequest;
|
|
30
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlncmF0aW9uMjAyNTAyMjExMDAwMDBBZGRDb21wbGV0ZWRTdGF0dXNUb0FjY291bnREZWxldGlvblJlcXVlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi9zcmMvbW9kdWxlcy9hY2NvdW50LWRlbGV0aW9uLXJlcXVlc3QvbWlncmF0aW9ucy9NaWdyYXRpb24yMDI1MDIyMTEwMDAwMEFkZENvbXBsZXRlZFN0YXR1c1RvQWNjb3VudERlbGV0aW9uUmVxdWVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxzREFBaUQ7QUFFakQsTUFBYSxpRUFBa0UsU0FBUSxzQkFBUztJQUM5RixLQUFLLENBQUMsRUFBRTtRQUNOLElBQUksQ0FBQyxNQUFNLENBQUM7OztLQUdYLENBQUMsQ0FBQTtRQUNGLElBQUksQ0FBQyxNQUFNLENBQUM7Ozs7S0FJWCxDQUFDLENBQUE7SUFDSixDQUFDO0lBRUQsS0FBSyxDQUFDLElBQUk7UUFDUixJQUFJLENBQUMsTUFBTSxDQUFDOzs7S0FHWCxDQUFDLENBQUE7UUFDRixJQUFJLENBQUMsTUFBTSxDQUFDOzs7O0tBSVgsQ0FBQyxDQUFBO0lBQ0osQ0FBQztDQUNGO0FBeEJELDhJQXdCQyJ9
|
package/.medusa/server/src/modules/account-deletion-request/models/account-deletion-request.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AccountDeletionRequest = exports.AccountDeletionRequestStatus = void 0;
|
|
4
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
5
|
+
exports.AccountDeletionRequestStatus = {
|
|
6
|
+
PENDING: "pending",
|
|
7
|
+
CONFIRMED: "confirmed",
|
|
8
|
+
CANCELLED: "cancelled",
|
|
9
|
+
COMPLETED: "completed",
|
|
10
|
+
};
|
|
11
|
+
exports.AccountDeletionRequest = utils_1.model
|
|
12
|
+
.define("account_deletion_request", {
|
|
13
|
+
id: utils_1.model.id().primaryKey(),
|
|
14
|
+
customer_id: utils_1.model.text().searchable(),
|
|
15
|
+
reason: utils_1.model.text().nullable(),
|
|
16
|
+
deletion_scheduled_at: utils_1.model.dateTime().nullable(),
|
|
17
|
+
status: utils_1.model
|
|
18
|
+
.enum(["pending", "confirmed", "cancelled", "completed"])
|
|
19
|
+
.default("pending"),
|
|
20
|
+
cancelled_at: utils_1.model.dateTime().nullable(),
|
|
21
|
+
})
|
|
22
|
+
.indexes([
|
|
23
|
+
{ name: "IDX_account_deletion_request_customer_id", on: ["customer_id"] },
|
|
24
|
+
{ name: "IDX_account_deletion_request_status", on: ["status"] },
|
|
25
|
+
]);
|
|
26
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYWNjb3VudC1kZWxldGlvbi1yZXF1ZXN0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vLi4vc3JjL21vZHVsZXMvYWNjb3VudC1kZWxldGlvbi1yZXF1ZXN0L21vZGVscy9hY2NvdW50LWRlbGV0aW9uLXJlcXVlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUEscURBQWlEO0FBRXBDLFFBQUEsNEJBQTRCLEdBQUc7SUFDMUMsT0FBTyxFQUFFLFNBQVM7SUFDbEIsU0FBUyxFQUFFLFdBQVc7SUFDdEIsU0FBUyxFQUFFLFdBQVc7SUFDdEIsU0FBUyxFQUFFLFdBQVc7Q0FDZCxDQUFBO0FBS0csUUFBQSxzQkFBc0IsR0FBRyxhQUFLO0tBQ3hDLE1BQU0sQ0FBQywwQkFBMEIsRUFBRTtJQUNsQyxFQUFFLEVBQUUsYUFBSyxDQUFDLEVBQUUsRUFBRSxDQUFDLFVBQVUsRUFBRTtJQUMzQixXQUFXLEVBQUUsYUFBSyxDQUFDLElBQUksRUFBRSxDQUFDLFVBQVUsRUFBRTtJQUN0QyxNQUFNLEVBQUUsYUFBSyxDQUFDLElBQUksRUFBRSxDQUFDLFFBQVEsRUFBRTtJQUMvQixxQkFBcUIsRUFBRSxhQUFLLENBQUMsUUFBUSxFQUFFLENBQUMsUUFBUSxFQUFFO0lBQ2xELE1BQU0sRUFBRSxhQUFLO1NBQ1YsSUFBSSxDQUFDLENBQUMsU0FBUyxFQUFFLFdBQVcsRUFBRSxXQUFXLEVBQUUsV0FBVyxDQUFDLENBQUM7U0FDeEQsT0FBTyxDQUFDLFNBQVMsQ0FBQztJQUNyQixZQUFZLEVBQUUsYUFBSyxDQUFDLFFBQVEsRUFBRSxDQUFDLFFBQVEsRUFBRTtDQUMxQyxDQUFDO0tBQ0QsT0FBTyxDQUFDO0lBQ1AsRUFBRSxJQUFJLEVBQUUsMENBQTBDLEVBQUUsRUFBRSxFQUFFLENBQUMsYUFBYSxDQUFDLEVBQUU7SUFDekUsRUFBRSxJQUFJLEVBQUUscUNBQXFDLEVBQUUsRUFBRSxFQUFFLENBQUMsUUFBUSxDQUFDLEVBQUU7Q0FDaEUsQ0FBQyxDQUFBIn0=
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const utils_1 = require("@medusajs/framework/utils");
|
|
4
|
+
const account_deletion_request_1 = require("./models/account-deletion-request");
|
|
5
|
+
class AccountDeletionRequestService extends (0, utils_1.MedusaService)({
|
|
6
|
+
AccountDeletionRequest: account_deletion_request_1.AccountDeletionRequest,
|
|
7
|
+
}) {
|
|
8
|
+
constructor(container, moduleOptions, moduleDeclaration) {
|
|
9
|
+
super(container, moduleOptions, moduleDeclaration);
|
|
10
|
+
this.manager_ = container.manager;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Create the account_deletion_request row only when the customer confirms (OTP verified).
|
|
14
|
+
* Throws if the customer already has an active (pending or confirmed) request.
|
|
15
|
+
*/
|
|
16
|
+
async createConfirmed(customer_id, deletion_scheduled_at) {
|
|
17
|
+
const active = await this.getActiveByCustomerId(customer_id);
|
|
18
|
+
if (active) {
|
|
19
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.INVALID_DATA, "An active deletion request already exists for this customer.");
|
|
20
|
+
}
|
|
21
|
+
const result = await this.createAccountDeletionRequests({
|
|
22
|
+
customer_id,
|
|
23
|
+
reason: null,
|
|
24
|
+
deletion_scheduled_at,
|
|
25
|
+
status: "confirmed",
|
|
26
|
+
cancelled_at: null,
|
|
27
|
+
});
|
|
28
|
+
return Array.isArray(result) ? result[0] : result;
|
|
29
|
+
}
|
|
30
|
+
async cancelRequest(customer_id) {
|
|
31
|
+
const active = await this.getActiveByCustomerId(customer_id);
|
|
32
|
+
if (!active) {
|
|
33
|
+
throw new utils_1.MedusaError(utils_1.MedusaError.Types.NOT_FOUND, "No active deletion request found for this customer.");
|
|
34
|
+
}
|
|
35
|
+
const now = new Date();
|
|
36
|
+
await this.updateAccountDeletionRequests({
|
|
37
|
+
selector: { id: active.id },
|
|
38
|
+
data: {
|
|
39
|
+
status: "cancelled",
|
|
40
|
+
cancelled_at: now,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
const [updated] = await this.listAccountDeletionRequests({ id: active.id }, { take: 1 });
|
|
44
|
+
return updated;
|
|
45
|
+
}
|
|
46
|
+
async getActiveByCustomerId(customer_id) {
|
|
47
|
+
const requests = await this.listAccountDeletionRequests({ customer_id }, { take: 10, order: { created_at: "DESC" } });
|
|
48
|
+
return (requests.find((r) => r.status === "pending" || r.status === "confirmed") ?? null);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns true if the customer has an account_deletion_request with status "pending".
|
|
52
|
+
* Used by login guard to block sign-in for pending accounts.
|
|
53
|
+
*/
|
|
54
|
+
async hasPendingRequest(customerId) {
|
|
55
|
+
const rows = await this.listAccountDeletionRequests({ customer_id: customerId, status: "pending" }, { take: 1 });
|
|
56
|
+
return rows.length > 0;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Returns true if the customer has an active deletion request (pending or confirmed).
|
|
60
|
+
* Used by middleware to block store API access until deletion is completed or cancelled.
|
|
61
|
+
*/
|
|
62
|
+
async hasActiveRequest(customerId) {
|
|
63
|
+
const active = await this.getActiveByCustomerId(customerId);
|
|
64
|
+
return active != null;
|
|
65
|
+
}
|
|
66
|
+
async listForAdmin(selector = {}, listConfig = {}) {
|
|
67
|
+
const { take = 20, skip = 0, order = { created_at: "DESC" } } = listConfig;
|
|
68
|
+
const [requests, count] = await this.listAndCountAccountDeletionRequests(selector, { take, skip, order });
|
|
69
|
+
return { requests, count };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* List requests due for deletion: status = confirmed and deletion_scheduled_at <= now.
|
|
73
|
+
*/
|
|
74
|
+
async listDueForDeletion(limit = 50) {
|
|
75
|
+
const now = new Date();
|
|
76
|
+
const requests = await this.listAccountDeletionRequests({ status: "confirmed" }, { take: limit, order: { deletion_scheduled_at: "ASC" } });
|
|
77
|
+
return requests.filter((r) => r.deletion_scheduled_at != null && r.deletion_scheduled_at <= now);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Mark a request as completed (after customer and auth identity have been deleted).
|
|
81
|
+
*/
|
|
82
|
+
async markCompleted(id) {
|
|
83
|
+
await this.updateAccountDeletionRequests({
|
|
84
|
+
selector: { id },
|
|
85
|
+
data: { status: "completed" },
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.default = AccountDeletionRequestService;
|
|
90
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9tb2R1bGVzL2FjY291bnQtZGVsZXRpb24tcmVxdWVzdC9zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEscURBQXNFO0FBRXRFLGdGQUEwRTtBQWlCMUUsTUFBcUIsNkJBQThCLFNBQVEsSUFBQSxxQkFBYSxFQUFDO0lBQ3ZFLHNCQUFzQixFQUF0QixpREFBc0I7Q0FDdkIsQ0FBQztJQUdBLFlBQ0UsU0FBK0IsRUFDL0IsYUFBdUMsRUFDdkMsaUJBQXlEO1FBRXpELEtBQUssQ0FBQyxTQUFTLEVBQUUsYUFBYSxFQUFFLGlCQUFpQixDQUFDLENBQUE7UUFDbEQsSUFBSSxDQUFDLFFBQVEsR0FBRyxTQUFTLENBQUMsT0FBTyxDQUFBO0lBQ25DLENBQUM7SUFFRDs7O09BR0c7SUFDSCxLQUFLLENBQUMsZUFBZSxDQUNuQixXQUFtQixFQUNuQixxQkFBMkI7UUFFM0IsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMscUJBQXFCLENBQUMsV0FBVyxDQUFDLENBQUE7UUFDNUQsSUFBSSxNQUFNLEVBQUUsQ0FBQztZQUNYLE1BQU0sSUFBSSxtQkFBVyxDQUNuQixtQkFBVyxDQUFDLEtBQUssQ0FBQyxZQUFZLEVBQzlCLDhEQUE4RCxDQUMvRCxDQUFBO1FBQ0gsQ0FBQztRQUNELE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLDZCQUE2QixDQUFDO1lBQ3RELFdBQVc7WUFDWCxNQUFNLEVBQUUsSUFBSTtZQUNaLHFCQUFxQjtZQUNyQixNQUFNLEVBQUUsV0FBK0M7WUFDdkQsWUFBWSxFQUFFLElBQUk7U0FDbkIsQ0FBQyxDQUFBO1FBQ0YsT0FBTyxLQUFLLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFFLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQTtJQUNwRCxDQUFDO0lBRUQsS0FBSyxDQUFDLGFBQWEsQ0FBQyxXQUFtQjtRQUNyQyxNQUFNLE1BQU0sR0FBRyxNQUFNLElBQUksQ0FBQyxxQkFBcUIsQ0FBQyxXQUFXLENBQUMsQ0FBQTtRQUM1RCxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDWixNQUFNLElBQUksbUJBQVcsQ0FDbkIsbUJBQVcsQ0FBQyxLQUFLLENBQUMsU0FBUyxFQUMzQixxREFBcUQsQ0FDdEQsQ0FBQTtRQUNILENBQUM7UUFDRCxNQUFNLEdBQUcsR0FBRyxJQUFJLElBQUksRUFBRSxDQUFBO1FBQ3RCLE1BQU0sSUFBSSxDQUFDLDZCQUE2QixDQUFDO1lBQ3ZDLFFBQVEsRUFBRSxFQUFFLEVBQUUsRUFBRSxNQUFNLENBQUMsRUFBRSxFQUFFO1lBQzNCLElBQUksRUFBRTtnQkFDSixNQUFNLEVBQUUsV0FBK0M7Z0JBQ3ZELFlBQVksRUFBRSxHQUFHO2FBQ2xCO1NBQ0YsQ0FBQyxDQUFBO1FBQ0YsTUFBTSxDQUFDLE9BQU8sQ0FBQyxHQUFHLE1BQU0sSUFBSSxDQUFDLDJCQUEyQixDQUN0RCxFQUFFLEVBQUUsRUFBRSxNQUFNLENBQUMsRUFBRSxFQUFFLEVBQ2pCLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRSxDQUNaLENBQUE7UUFDRCxPQUFPLE9BQU8sQ0FBQTtJQUNoQixDQUFDO0lBRUQsS0FBSyxDQUFDLHFCQUFxQixDQUFDLFdBQW1CO1FBQzdDLE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLDJCQUEyQixDQUNyRCxFQUFFLFdBQVcsRUFBRSxFQUNmLEVBQUUsSUFBSSxFQUFFLEVBQUUsRUFBRSxLQUFLLEVBQUUsRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLEVBQUUsQ0FDNUMsQ0FBQTtRQUNELE9BQU8sQ0FDTCxRQUFRLENBQUMsSUFBSSxDQUNYLENBQUMsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsTUFBTSxLQUFLLFNBQVMsSUFBSSxDQUFDLENBQUMsTUFBTSxLQUFLLFdBQVcsQ0FDMUQsSUFBSSxJQUFJLENBQ1YsQ0FBQTtJQUNILENBQUM7SUFFRDs7O09BR0c7SUFDSCxLQUFLLENBQUMsaUJBQWlCLENBQUMsVUFBa0I7UUFDeEMsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsMkJBQTJCLENBQ2pELEVBQUUsV0FBVyxFQUFFLFVBQVUsRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLEVBQzlDLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRSxDQUNaLENBQUE7UUFDRCxPQUFPLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFBO0lBQ3hCLENBQUM7SUFFRDs7O09BR0c7SUFDSCxLQUFLLENBQUMsZ0JBQWdCLENBQUMsVUFBa0I7UUFDdkMsTUFBTSxNQUFNLEdBQUcsTUFBTSxJQUFJLENBQUMscUJBQXFCLENBQUMsVUFBVSxDQUFDLENBQUE7UUFDM0QsT0FBTyxNQUFNLElBQUksSUFBSSxDQUFBO0lBQ3ZCLENBQUM7SUFFRCxLQUFLLENBQUMsWUFBWSxDQUNoQixXQUFvQyxFQUFFLEVBQ3RDLGFBQXlCLEVBQUU7UUFFM0IsTUFBTSxFQUFFLElBQUksR0FBRyxFQUFFLEVBQUUsSUFBSSxHQUFHLENBQUMsRUFBRSxLQUFLLEdBQUcsRUFBRSxVQUFVLEVBQUUsTUFBTSxFQUFFLEVBQUUsR0FBRyxVQUFVLENBQUE7UUFDMUUsTUFBTSxDQUFDLFFBQVEsRUFBRSxLQUFLLENBQUMsR0FBRyxNQUFNLElBQUksQ0FBQyxtQ0FBbUMsQ0FDdEUsUUFBUSxFQUNSLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsQ0FDdEIsQ0FBQTtRQUNELE9BQU8sRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLENBQUE7SUFDNUIsQ0FBQztJQUVEOztPQUVHO0lBQ0gsS0FBSyxDQUFDLGtCQUFrQixDQUFDLEtBQUssR0FBRyxFQUFFO1FBQ2pDLE1BQU0sR0FBRyxHQUFHLElBQUksSUFBSSxFQUFFLENBQUE7UUFDdEIsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsMkJBQTJCLENBQ3JELEVBQUUsTUFBTSxFQUFFLFdBQVcsRUFBRSxFQUN2QixFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLEVBQUUscUJBQXFCLEVBQUUsS0FBSyxFQUFFLEVBQUUsQ0FDekQsQ0FBQTtRQUNELE9BQU8sUUFBUSxDQUFDLE1BQU0sQ0FDcEIsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUNKLENBQUMsQ0FBQyxxQkFBcUIsSUFBSSxJQUFJLElBQUksQ0FBQyxDQUFDLHFCQUFxQixJQUFJLEdBQUcsQ0FDcEUsQ0FBQTtJQUNILENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUssQ0FBQyxhQUFhLENBQUMsRUFBVTtRQUM1QixNQUFNLElBQUksQ0FBQyw2QkFBNkIsQ0FBQztZQUN2QyxRQUFRLEVBQUUsRUFBRSxFQUFFLEVBQUU7WUFDaEIsSUFBSSxFQUFFLEVBQUUsTUFBTSxFQUFFLFdBQStDLEVBQUU7U0FDbEUsQ0FBQyxDQUFBO0lBQ0osQ0FBQztDQUNGO0FBbklELGdEQW1JQyJ9
|