@porulle/core 0.53.0 → 0.53.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@porulle/core",
3
- "version": "0.53.0",
3
+ "version": "0.53.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -62,8 +62,8 @@
62
62
  "eslint": "^9.39.1",
63
63
  "typescript": "5.9.2",
64
64
  "vitest": "^3.2.4",
65
- "@porulle/eslint-config": "0.1.0",
66
- "@porulle/typescript-config": "0.1.0"
65
+ "@porulle/typescript-config": "0.1.0",
66
+ "@porulle/eslint-config": "0.1.0"
67
67
  },
68
68
  "publishConfig": {
69
69
  "access": "public"
@@ -1,5 +1,6 @@
1
1
  import { eq, and, desc } from "drizzle-orm";
2
2
  import type { TxContext } from "../../../kernel/database/tx-context.js";
3
+ import { user } from "../../../auth/auth-schema.js";
3
4
  import type {
4
5
  DrizzleDatabase,
5
6
  DbOrTx,
@@ -51,6 +52,27 @@ export class CustomersRepository {
51
52
  return rows[0];
52
53
  }
53
54
 
55
+ /**
56
+ * The auth user's email, when no OTHER customer profile in this organization already holds it
57
+ * (`customers` is unique on organization + email). Undefined otherwise — the caller then leaves the
58
+ * profile's email as it is rather than fail the whole profile read on the unique index.
59
+ */
60
+ async claimableUserEmail(
61
+ orgId: string,
62
+ userId: string,
63
+ ctx?: TxContext,
64
+ ): Promise<string | undefined> {
65
+ const db = this.getDb(ctx);
66
+ const [account] = await db.select({ email: user.email }).from(user).where(eq(user.id, userId));
67
+ if (!account?.email) return undefined;
68
+ const [holder] = await db
69
+ .select({ userId: customers.userId })
70
+ .from(customers)
71
+ .where(and(eq(customers.organizationId, orgId), eq(customers.email, account.email)))
72
+ .limit(1);
73
+ return holder === undefined || holder.userId === userId ? account.email : undefined;
74
+ }
75
+
54
76
  async findByUserId(
55
77
  orgId: string,
56
78
  userId: string,
@@ -177,16 +177,24 @@ export class CustomerService {
177
177
  actor: Actor | null,
178
178
  ctx?: TxContext,
179
179
  ): Promise<Customer> {
180
+ // The profile carries the shopper's email: it is the key channel redaction uses, so a profile
181
+ // without one cannot be erased once its orders have been exported. Filled on creation, and on
182
+ // the next access of a profile created before this (never overwriting an email it has).
180
183
  const existing = await this.repo.findByUserId(orgId, userId, ctx);
181
184
  if (existing) {
182
- return existing;
185
+ if (existing.email) return existing;
186
+ const email = await this.repo.claimableUserEmail(orgId, userId, ctx);
187
+ if (!email) return existing;
188
+ return (await this.repo.update(existing.id, { email }, ctx)) ?? existing;
183
189
  }
184
190
 
191
+ const email = await this.repo.claimableUserEmail(orgId, userId, ctx);
185
192
  const customer = await this.repo.create(
186
193
  {
187
194
  organizationId: orgId,
188
195
  userId,
189
196
  metadata: {},
197
+ ...(email !== undefined ? { email } : {}),
190
198
  },
191
199
  ctx,
192
200
  );