@simple-auth-kit/cli 1.4.2 → 1.5.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 (133) hide show
  1. package/package.json +1 -1
  2. package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  3. package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
  4. package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  5. package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
  6. package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  7. package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  8. package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
  9. package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
  10. package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
  11. package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
  12. package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  13. package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
  14. package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
  15. package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
  16. package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
  17. package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
  18. package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  19. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
  20. package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
  21. package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
  22. package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
  23. package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
  24. package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
  25. package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
  26. package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  27. package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
  28. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  29. package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
  30. package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  31. package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  32. package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  33. package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
  34. package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
  35. package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +57 -22
  36. package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  37. package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +67 -19
  38. package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  39. package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
  40. package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
  41. package/registry/combos/express-prisma/variants/base/src/seed.ts +78 -20
  42. package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
  43. package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +81 -26
  44. package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  45. package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
  46. package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +73 -21
  47. package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  48. package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
  49. package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
  50. package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
  51. package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
  52. package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +109 -29
  53. package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
  54. package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  55. package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  56. package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  57. package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  58. package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
  59. package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
  60. package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  61. package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  62. package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
  63. package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
  64. package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
  65. package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
  66. package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
  67. package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  68. package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
  69. package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
  70. package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
  71. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
  72. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
  73. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  74. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  75. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
  76. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
  77. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
  78. package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
  79. package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  80. package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  81. package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  82. package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  83. package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
  84. package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
  85. package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  86. package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  87. package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  88. package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
  89. package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
  90. package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +59 -28
  91. package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
  92. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
  93. package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  94. package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +98 -27
  95. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  96. package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
  97. package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
  98. package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
  99. package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
  100. package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +78 -20
  101. package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
  102. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +55 -27
  103. package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
  104. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  105. package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  106. package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +104 -29
  107. package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  108. package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
  109. package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
  110. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
  111. package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
  112. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
  113. package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +109 -29
  114. package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
  115. package/registry.json +6 -4
  116. package/simple-auth-kit.ts +582 -124
  117. package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
  118. package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
  119. package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
  120. package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
  121. package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
  122. package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
  123. package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
  124. package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
  125. package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
  126. package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
  127. package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
  128. package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
  129. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
  130. package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
  131. package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
  132. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
  133. package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
@@ -1,7 +1,17 @@
1
- import { ConflictException, Inject, Injectable, NotFoundException } from "@nestjs/common";
2
- import { Prisma, PrismaClient } from "../generated/prisma/client.js";
3
- import { toId, toIdOrNull } from "./id.helper.js";
4
- import { buildPageMeta, normalizeLimit, normalizePage, type Paginated } from "./pagination.js";
1
+ import {
2
+ ConflictException,
3
+ Inject,
4
+ Injectable,
5
+ NotFoundException,
6
+ } from "@nestjs/common";
7
+ import { Prisma, PrismaClient } from "../../generated/prisma/client.js";
8
+ import { toId, toIdOrNull } from "../id.helper.js";
9
+ import {
10
+ buildPageMeta,
11
+ normalizeLimit,
12
+ normalizePage,
13
+ type Paginated,
14
+ } from "../pagination.js";
5
15
 
6
16
  /** A `Country` row as `findUnique`/`findMany` return it. */
7
17
  export type CountryRow = Prisma.CountryGetPayload<object>;
@@ -78,7 +88,10 @@ export class CountryRepository {
78
88
  constructor(@Inject(PrismaClient) private readonly prisma: PrismaClient) {}
79
89
 
80
90
  /** Newest-first, page-paginated; `search` matches name/code/isoCode. Returns raw rows — shaping is the caller's job. */
81
- async list(workspaceId: string, filter: CountryListFilter = {}): Promise<CountryListResult> {
91
+ async list(
92
+ workspaceId: string,
93
+ filter: CountryListFilter = {},
94
+ ): Promise<CountryListResult> {
82
95
  const page = normalizePage(filter.page);
83
96
  const limit = normalizeLimit(filter.limit);
84
97
  const where: Prisma.CountryWhereInput = {
@@ -95,64 +108,152 @@ export class CountryRepository {
95
108
  };
96
109
 
97
110
  const [rows, total] = await this.prisma.$transaction([
98
- this.prisma.country.findMany({ where, orderBy: [{ createdAt: "desc" }, { id: "desc" }], take: limit, skip: (page - 1) * limit }),
111
+ this.prisma.country.findMany({
112
+ where,
113
+ orderBy: [{ createdAt: "desc" }, { id: "desc" }],
114
+ take: limit,
115
+ skip: (page - 1) * limit,
116
+ }),
99
117
  this.prisma.country.count({ where }),
100
118
  ]);
101
119
 
102
- return { items: rows.map(toCountrySummary), meta: buildPageMeta(page, limit, total) };
120
+ return {
121
+ items: rows.map(toCountrySummary),
122
+ meta: buildPageMeta(page, limit, total),
123
+ };
103
124
  }
104
125
 
105
126
  async get(workspaceId: string, countryId: string): Promise<CountrySummary> {
106
- const row = await this.prisma.country.findUnique({ where: { id: toId(countryId), workspaceId: toId(workspaceId), isDeleted: false } });
127
+ const row = await this.prisma.country.findUnique({
128
+ where: {
129
+ id: toId(countryId),
130
+ workspaceId: toId(workspaceId),
131
+ isDeleted: false,
132
+ },
133
+ });
107
134
  if (!row) throw new NotFoundException(`country "${countryId}" not found`);
108
135
  return toCountrySummary(row);
109
136
  }
110
137
 
111
- async create(workspaceId: string, input: CountryInput, actorUserId: string | null): Promise<CountrySummary> {
138
+ async create(
139
+ workspaceId: string,
140
+ input: CountryInput,
141
+ actorUserId: string | null,
142
+ ): Promise<CountrySummary> {
112
143
  const workspaceIdBig = toId(workspaceId);
113
- const existing = await this.prisma.country.findFirst({ where: { workspaceId: workspaceIdBig, OR: [{ code: input.code }, { isoCode: input.isoCode }] } });
114
- if (existing) throw new ConflictException("a country with this code or ISO code already exists in this workspace");
144
+ const existing = await this.prisma.country.findFirst({
145
+ where: {
146
+ workspaceId: workspaceIdBig,
147
+ OR: [{ code: input.code }, { isoCode: input.isoCode }],
148
+ },
149
+ });
150
+ if (existing)
151
+ throw new ConflictException(
152
+ "a country with this code or ISO code already exists in this workspace",
153
+ );
115
154
 
116
155
  const row = await this.prisma.country.create({
117
- data: { ...input, workspaceId: workspaceIdBig, createdBy: toIdOrNull(actorUserId), updatedBy: toIdOrNull(actorUserId) },
156
+ data: {
157
+ ...input,
158
+ workspaceId: workspaceIdBig,
159
+ createdBy: toIdOrNull(actorUserId),
160
+ updatedBy: toIdOrNull(actorUserId),
161
+ },
118
162
  });
119
163
  return toCountrySummary(row);
120
164
  }
121
165
 
122
- async update(workspaceId: string, countryId: string, input: Partial<CountryInput>, actorUserId: string | null): Promise<CountrySummary> {
166
+ async update(
167
+ workspaceId: string,
168
+ countryId: string,
169
+ input: Partial<CountryInput>,
170
+ actorUserId: string | null,
171
+ ): Promise<CountrySummary> {
123
172
  const workspaceIdBig = toId(workspaceId);
124
173
  const countryIdBig = toId(countryId);
125
- const existing = await this.prisma.country.findUnique({ where: { id: countryIdBig, workspaceId: workspaceIdBig, isDeleted: false }, select: { id: true } });
126
- if (!existing) throw new NotFoundException(`country "${countryId}" not found`);
174
+ const existing = await this.prisma.country.findUnique({
175
+ where: {
176
+ id: countryIdBig,
177
+ workspaceId: workspaceIdBig,
178
+ isDeleted: false,
179
+ },
180
+ select: { id: true },
181
+ });
182
+ if (!existing)
183
+ throw new NotFoundException(`country "${countryId}" not found`);
127
184
 
128
185
  if (input.code !== undefined || input.isoCode !== undefined) {
129
186
  const clash = await this.prisma.country.findFirst({
130
- where: { workspaceId: workspaceIdBig, NOT: { id: countryIdBig }, OR: [{ code: input.code }, { isoCode: input.isoCode }] },
187
+ where: {
188
+ workspaceId: workspaceIdBig,
189
+ NOT: { id: countryIdBig },
190
+ OR: [{ code: input.code }, { isoCode: input.isoCode }],
191
+ },
131
192
  });
132
- if (clash) throw new ConflictException("a country with this code or ISO code already exists in this workspace");
193
+ if (clash)
194
+ throw new ConflictException(
195
+ "a country with this code or ISO code already exists in this workspace",
196
+ );
133
197
  }
134
198
 
135
- const row = await this.prisma.country.update({ where: { id: countryIdBig }, data: { ...input, updatedBy: toIdOrNull(actorUserId) } });
199
+ const row = await this.prisma.country.update({
200
+ where: { id: countryIdBig },
201
+ data: { ...input, updatedBy: toIdOrNull(actorUserId) },
202
+ });
136
203
  return toCountrySummary(row);
137
204
  }
138
205
 
139
206
  // Soft-delete, matching every other table's isDeleted/deletedAt/deletedBy/deletedReason pattern.
140
- async delete(workspaceId: string, countryId: string, actorUserId: string | null, reason?: string): Promise<void> {
207
+ async delete(
208
+ workspaceId: string,
209
+ countryId: string,
210
+ actorUserId: string | null,
211
+ reason?: string,
212
+ ): Promise<void> {
141
213
  const workspaceIdBig = toId(workspaceId);
142
214
  const countryIdBig = toId(countryId);
143
- const existing = await this.prisma.country.findUnique({ where: { id: countryIdBig, workspaceId: workspaceIdBig, isDeleted: false }, select: { id: true } });
144
- if (!existing) throw new NotFoundException(`country "${countryId}" not found`);
215
+ const existing = await this.prisma.country.findUnique({
216
+ where: {
217
+ id: countryIdBig,
218
+ workspaceId: workspaceIdBig,
219
+ isDeleted: false,
220
+ },
221
+ select: { id: true },
222
+ });
223
+ if (!existing)
224
+ throw new NotFoundException(`country "${countryId}" not found`);
145
225
  await this.prisma.country.update({
146
226
  where: { id: countryIdBig },
147
- data: { isDeleted: true, deletedAt: new Date(), deletedBy: toIdOrNull(actorUserId), deletedReason: reason ?? null },
227
+ data: {
228
+ isDeleted: true,
229
+ deletedAt: new Date(),
230
+ deletedBy: toIdOrNull(actorUserId),
231
+ deletedReason: reason ?? null,
232
+ },
148
233
  });
149
234
  }
150
235
 
151
- async setActive(workspaceId: string, countryId: string, isActive: boolean, actorUserId: string | null): Promise<void> {
236
+ async setActive(
237
+ workspaceId: string,
238
+ countryId: string,
239
+ isActive: boolean,
240
+ actorUserId: string | null,
241
+ ): Promise<void> {
152
242
  const workspaceIdBig = toId(workspaceId);
153
243
  const countryIdBig = toId(countryId);
154
- const existing = await this.prisma.country.findUnique({ where: { id: countryIdBig, workspaceId: workspaceIdBig, isDeleted: false }, select: { id: true } });
155
- if (!existing) throw new NotFoundException(`country "${countryId}" not found`);
156
- await this.prisma.country.update({ where: { id: countryIdBig }, data: { isActive, updatedBy: toIdOrNull(actorUserId) } });
244
+ const existing = await this.prisma.country.findUnique({
245
+ where: {
246
+ id: countryIdBig,
247
+ workspaceId: workspaceIdBig,
248
+ isDeleted: false,
249
+ },
250
+ select: { id: true },
251
+ });
252
+ if (!existing)
253
+ throw new NotFoundException(`country "${countryId}" not found`);
254
+ await this.prisma.country.update({
255
+ where: { id: countryIdBig },
256
+ data: { isActive, updatedBy: toIdOrNull(actorUserId) },
257
+ });
157
258
  }
158
259
  }
@@ -1,7 +1,17 @@
1
- import { ConflictException, Inject, Injectable, NotFoundException } from "@nestjs/common";
2
- import { Prisma, PrismaClient } from "../generated/prisma/client.js";
3
- import { toId, toIdOrNull } from "./id.helper.js";
4
- import { buildPageMeta, normalizeLimit, normalizePage, type Paginated } from "./pagination.js";
1
+ import {
2
+ ConflictException,
3
+ Inject,
4
+ Injectable,
5
+ NotFoundException,
6
+ } from "@nestjs/common";
7
+ import { Prisma, PrismaClient } from "../../generated/prisma/client.js";
8
+ import { toId, toIdOrNull } from "../id.helper.js";
9
+ import {
10
+ buildPageMeta,
11
+ normalizeLimit,
12
+ normalizePage,
13
+ type Paginated,
14
+ } from "../pagination.js";
5
15
 
6
16
  /** A `Customer` row as `findUnique`/`findMany` return it. */
7
17
  export type CustomerRow = Prisma.CustomerGetPayload<object>;
@@ -78,11 +88,22 @@ export interface CustomerInput {
78
88
  isActive?: boolean;
79
89
  }
80
90
 
81
- function toPrismaInput(input: Partial<CustomerInput>): Omit<Partial<CustomerInput>, "dob" | "joinedDate"> & { dob?: Date | null; joinedDate?: Date } {
91
+ function toPrismaInput(
92
+ input: Partial<CustomerInput>,
93
+ ): Omit<Partial<CustomerInput>, "dob" | "joinedDate"> & {
94
+ dob?: Date | null;
95
+ joinedDate?: Date;
96
+ } {
82
97
  return {
83
98
  ...input,
84
- dob: input.dob === undefined ? undefined : input.dob === null ? null : new Date(input.dob),
85
- joinedDate: input.joinedDate === undefined ? undefined : new Date(input.joinedDate),
99
+ dob:
100
+ input.dob === undefined
101
+ ? undefined
102
+ : input.dob === null
103
+ ? null
104
+ : new Date(input.dob),
105
+ joinedDate:
106
+ input.joinedDate === undefined ? undefined : new Date(input.joinedDate),
86
107
  };
87
108
  }
88
109
 
@@ -96,7 +117,10 @@ export class CustomerRepository {
96
117
  constructor(@Inject(PrismaClient) private readonly prisma: PrismaClient) {}
97
118
 
98
119
  /** Newest-first, page-paginated; `search` matches name/email/username/phone. Returns raw rows — shaping is the caller's job. */
99
- async list(workspaceId: string, filter: CustomerListFilter = {}): Promise<CustomerListResult> {
120
+ async list(
121
+ workspaceId: string,
122
+ filter: CustomerListFilter = {},
123
+ ): Promise<CustomerListResult> {
100
124
  const page = normalizePage(filter.page);
101
125
  const limit = normalizeLimit(filter.limit);
102
126
  const where: Prisma.CustomerWhereInput = {
@@ -115,39 +139,90 @@ export class CustomerRepository {
115
139
  };
116
140
 
117
141
  const [rows, total] = await this.prisma.$transaction([
118
- this.prisma.customer.findMany({ where, orderBy: [{ createdAt: "desc" }, { id: "desc" }], take: limit, skip: (page - 1) * limit }),
142
+ this.prisma.customer.findMany({
143
+ where,
144
+ orderBy: [{ createdAt: "desc" }, { id: "desc" }],
145
+ take: limit,
146
+ skip: (page - 1) * limit,
147
+ }),
119
148
  this.prisma.customer.count({ where }),
120
149
  ]);
121
150
 
122
- return { items: rows.map(toCustomerSummary), meta: buildPageMeta(page, limit, total) };
151
+ return {
152
+ items: rows.map(toCustomerSummary),
153
+ meta: buildPageMeta(page, limit, total),
154
+ };
123
155
  }
124
156
 
125
157
  async get(workspaceId: string, customerId: string): Promise<CustomerSummary> {
126
- const row = await this.prisma.customer.findUnique({ where: { id: toId(customerId), workspaceId: toId(workspaceId), isDeleted: false } });
158
+ const row = await this.prisma.customer.findUnique({
159
+ where: {
160
+ id: toId(customerId),
161
+ workspaceId: toId(workspaceId),
162
+ isDeleted: false,
163
+ },
164
+ });
127
165
  if (!row) throw new NotFoundException(`customer "${customerId}" not found`);
128
166
  return toCustomerSummary(row);
129
167
  }
130
168
 
131
- async create(workspaceId: string, input: CustomerInput, actorUserId: string | null): Promise<CustomerSummary> {
169
+ async create(
170
+ workspaceId: string,
171
+ input: CustomerInput,
172
+ actorUserId: string | null,
173
+ ): Promise<CustomerSummary> {
132
174
  const workspaceIdBig = toId(workspaceId);
133
- const existing = await this.prisma.customer.findFirst({ where: { workspaceId: workspaceIdBig, email: input.email } });
134
- if (existing) throw new ConflictException("a customer with this email already exists in this workspace");
175
+ const existing = await this.prisma.customer.findFirst({
176
+ where: { workspaceId: workspaceIdBig, email: input.email },
177
+ });
178
+ if (existing)
179
+ throw new ConflictException(
180
+ "a customer with this email already exists in this workspace",
181
+ );
135
182
 
136
183
  const row = await this.prisma.customer.create({
137
- data: { ...toPrismaInput(input), email: input.email, workspaceId: workspaceIdBig, createdBy: toIdOrNull(actorUserId), updatedBy: toIdOrNull(actorUserId) },
184
+ data: {
185
+ ...toPrismaInput(input),
186
+ email: input.email,
187
+ workspaceId: workspaceIdBig,
188
+ createdBy: toIdOrNull(actorUserId),
189
+ updatedBy: toIdOrNull(actorUserId),
190
+ },
138
191
  });
139
192
  return toCustomerSummary(row);
140
193
  }
141
194
 
142
- async update(workspaceId: string, customerId: string, input: Partial<CustomerInput>, actorUserId: string | null): Promise<CustomerSummary> {
195
+ async update(
196
+ workspaceId: string,
197
+ customerId: string,
198
+ input: Partial<CustomerInput>,
199
+ actorUserId: string | null,
200
+ ): Promise<CustomerSummary> {
143
201
  const workspaceIdBig = toId(workspaceId);
144
202
  const customerIdBig = toId(customerId);
145
- const existing = await this.prisma.customer.findUnique({ where: { id: customerIdBig, workspaceId: workspaceIdBig, isDeleted: false }, select: { id: true } });
146
- if (!existing) throw new NotFoundException(`customer "${customerId}" not found`);
203
+ const existing = await this.prisma.customer.findUnique({
204
+ where: {
205
+ id: customerIdBig,
206
+ workspaceId: workspaceIdBig,
207
+ isDeleted: false,
208
+ },
209
+ select: { id: true },
210
+ });
211
+ if (!existing)
212
+ throw new NotFoundException(`customer "${customerId}" not found`);
147
213
 
148
214
  if (input.email !== undefined) {
149
- const clash = await this.prisma.customer.findFirst({ where: { workspaceId: workspaceIdBig, NOT: { id: customerIdBig }, email: input.email } });
150
- if (clash) throw new ConflictException("a customer with this email already exists in this workspace");
215
+ const clash = await this.prisma.customer.findFirst({
216
+ where: {
217
+ workspaceId: workspaceIdBig,
218
+ NOT: { id: customerIdBig },
219
+ email: input.email,
220
+ },
221
+ });
222
+ if (clash)
223
+ throw new ConflictException(
224
+ "a customer with this email already exists in this workspace",
225
+ );
151
226
  }
152
227
 
153
228
  const row = await this.prisma.customer.update({
@@ -158,22 +233,56 @@ export class CustomerRepository {
158
233
  }
159
234
 
160
235
  // Soft-delete, matching every other table's isDeleted/deletedAt/deletedBy/deletedReason pattern.
161
- async delete(workspaceId: string, customerId: string, actorUserId: string | null, reason?: string): Promise<void> {
236
+ async delete(
237
+ workspaceId: string,
238
+ customerId: string,
239
+ actorUserId: string | null,
240
+ reason?: string,
241
+ ): Promise<void> {
162
242
  const workspaceIdBig = toId(workspaceId);
163
243
  const customerIdBig = toId(customerId);
164
- const existing = await this.prisma.customer.findUnique({ where: { id: customerIdBig, workspaceId: workspaceIdBig, isDeleted: false }, select: { id: true } });
165
- if (!existing) throw new NotFoundException(`customer "${customerId}" not found`);
244
+ const existing = await this.prisma.customer.findUnique({
245
+ where: {
246
+ id: customerIdBig,
247
+ workspaceId: workspaceIdBig,
248
+ isDeleted: false,
249
+ },
250
+ select: { id: true },
251
+ });
252
+ if (!existing)
253
+ throw new NotFoundException(`customer "${customerId}" not found`);
166
254
  await this.prisma.customer.update({
167
255
  where: { id: customerIdBig },
168
- data: { isDeleted: true, deletedAt: new Date(), deletedBy: toIdOrNull(actorUserId), deletedReason: reason ?? null },
256
+ data: {
257
+ isDeleted: true,
258
+ deletedAt: new Date(),
259
+ deletedBy: toIdOrNull(actorUserId),
260
+ deletedReason: reason ?? null,
261
+ },
169
262
  });
170
263
  }
171
264
 
172
- async setActive(workspaceId: string, customerId: string, isActive: boolean, actorUserId: string | null): Promise<void> {
265
+ async setActive(
266
+ workspaceId: string,
267
+ customerId: string,
268
+ isActive: boolean,
269
+ actorUserId: string | null,
270
+ ): Promise<void> {
173
271
  const workspaceIdBig = toId(workspaceId);
174
272
  const customerIdBig = toId(customerId);
175
- const existing = await this.prisma.customer.findUnique({ where: { id: customerIdBig, workspaceId: workspaceIdBig, isDeleted: false }, select: { id: true } });
176
- if (!existing) throw new NotFoundException(`customer "${customerId}" not found`);
177
- await this.prisma.customer.update({ where: { id: customerIdBig }, data: { isActive, updatedBy: toIdOrNull(actorUserId) } });
273
+ const existing = await this.prisma.customer.findUnique({
274
+ where: {
275
+ id: customerIdBig,
276
+ workspaceId: workspaceIdBig,
277
+ isDeleted: false,
278
+ },
279
+ select: { id: true },
280
+ });
281
+ if (!existing)
282
+ throw new NotFoundException(`customer "${customerId}" not found`);
283
+ await this.prisma.customer.update({
284
+ where: { id: customerIdBig },
285
+ data: { isActive, updatedBy: toIdOrNull(actorUserId) },
286
+ });
178
287
  }
179
288
  }
@@ -0,0 +1,243 @@
1
+ import {
2
+ ConflictException,
3
+ Inject,
4
+ Injectable,
5
+ NotFoundException,
6
+ } from "@nestjs/common";
7
+ import { Prisma, PrismaClient } from "../../generated/prisma/client.js";
8
+ import { toId, toIdOrNull } from "../id.helper.js";
9
+ import {
10
+ buildPageMeta,
11
+ normalizeLimit,
12
+ normalizePage,
13
+ type Paginated,
14
+ } from "../pagination.js";
15
+
16
+ /** A `Language` row as `findUnique`/`findMany` return it. */
17
+ export type LanguageRow = Prisma.LanguageGetPayload<object>;
18
+
19
+ /** Every column the `Language` table has — no secret to withhold, same reasoning as `toUserSummary`. */
20
+ export interface LanguageSummary {
21
+ id: string;
22
+ uuid: string;
23
+ code: string;
24
+ name: string;
25
+ nativeName: string;
26
+ direction: string;
27
+ isDefault: boolean;
28
+ isActive: boolean;
29
+ createdBy: string | null;
30
+ updatedBy: string | null;
31
+ createdAt: string;
32
+ updatedAt: string;
33
+ }
34
+
35
+ export function toLanguageSummary(row: LanguageRow): LanguageSummary {
36
+ return {
37
+ id: row.id.toString(),
38
+ uuid: row.uuid,
39
+ code: row.code,
40
+ name: row.name,
41
+ nativeName: row.nativeName,
42
+ direction: row.direction,
43
+ isDefault: row.isDefault,
44
+ isActive: row.isActive,
45
+ createdBy: row.createdBy?.toString() ?? null,
46
+ updatedBy: row.updatedBy?.toString() ?? null,
47
+ createdAt: row.createdAt.toISOString(),
48
+ updatedAt: row.updatedAt.toISOString(),
49
+ };
50
+ }
51
+
52
+ export interface LanguageListFilter {
53
+ search?: string;
54
+ page?: number;
55
+ limit?: number;
56
+ activeOnly?: boolean;
57
+ }
58
+
59
+ export type LanguageListResult = Paginated<LanguageSummary>;
60
+
61
+ export interface LanguageInput {
62
+ code: string;
63
+ name: string;
64
+ nativeName: string;
65
+ direction?: string;
66
+ isDefault?: boolean;
67
+ isActive?: boolean;
68
+ }
69
+
70
+ // Every method takes `workspaceId` first, matching `WorkspaceRepository`'s signature style, and
71
+ // every query's `where` includes it — a language lives in exactly one workspace, and `code`
72
+ // uniqueness is checked within that workspace only (see `schema.prisma`'s composite unique constraint).
73
+ @Injectable()
74
+ export class LanguageRepository {
75
+ constructor(@Inject(PrismaClient) private readonly prisma: PrismaClient) {}
76
+
77
+ /** Newest-first, page-paginated; `search` matches name/code/nativeName. Returns raw rows — shaping is the caller's job. */
78
+ async list(
79
+ workspaceId: string,
80
+ filter: LanguageListFilter = {},
81
+ ): Promise<LanguageListResult> {
82
+ const page = normalizePage(filter.page);
83
+ const limit = normalizeLimit(filter.limit);
84
+ const where: Prisma.LanguageWhereInput = {
85
+ workspaceId: toId(workspaceId),
86
+ isDeleted: false,
87
+ isActive: filter.activeOnly ? true : undefined,
88
+ OR: filter.search
89
+ ? [
90
+ { name: { contains: filter.search, mode: "insensitive" } },
91
+ { code: { contains: filter.search, mode: "insensitive" } },
92
+ { nativeName: { contains: filter.search, mode: "insensitive" } },
93
+ ]
94
+ : undefined,
95
+ };
96
+
97
+ const [rows, total] = await this.prisma.$transaction([
98
+ this.prisma.language.findMany({
99
+ where,
100
+ orderBy: [{ createdAt: "desc" }, { id: "desc" }],
101
+ take: limit,
102
+ skip: (page - 1) * limit,
103
+ }),
104
+ this.prisma.language.count({ where }),
105
+ ]);
106
+
107
+ return {
108
+ items: rows.map(toLanguageSummary),
109
+ meta: buildPageMeta(page, limit, total),
110
+ };
111
+ }
112
+
113
+ async get(workspaceId: string, languageId: string): Promise<LanguageSummary> {
114
+ const row = await this.prisma.language.findUnique({
115
+ where: {
116
+ id: toId(languageId),
117
+ workspaceId: toId(workspaceId),
118
+ isDeleted: false,
119
+ },
120
+ });
121
+ if (!row) throw new NotFoundException(`language "${languageId}" not found`);
122
+ return toLanguageSummary(row);
123
+ }
124
+
125
+ async create(
126
+ workspaceId: string,
127
+ input: LanguageInput,
128
+ actorUserId: string | null,
129
+ ): Promise<LanguageSummary> {
130
+ const workspaceIdBig = toId(workspaceId);
131
+ const existing = await this.prisma.language.findFirst({
132
+ where: { workspaceId: workspaceIdBig, code: input.code },
133
+ });
134
+ if (existing)
135
+ throw new ConflictException(
136
+ "a language with this code already exists in this workspace",
137
+ );
138
+
139
+ const row = await this.prisma.language.create({
140
+ data: {
141
+ ...input,
142
+ workspaceId: workspaceIdBig,
143
+ createdBy: toIdOrNull(actorUserId),
144
+ updatedBy: toIdOrNull(actorUserId),
145
+ },
146
+ });
147
+ return toLanguageSummary(row);
148
+ }
149
+
150
+ async update(
151
+ workspaceId: string,
152
+ languageId: string,
153
+ input: Partial<LanguageInput>,
154
+ actorUserId: string | null,
155
+ ): Promise<LanguageSummary> {
156
+ const workspaceIdBig = toId(workspaceId);
157
+ const languageIdBig = toId(languageId);
158
+ const existing = await this.prisma.language.findUnique({
159
+ where: {
160
+ id: languageIdBig,
161
+ workspaceId: workspaceIdBig,
162
+ isDeleted: false,
163
+ },
164
+ select: { id: true },
165
+ });
166
+ if (!existing)
167
+ throw new NotFoundException(`language "${languageId}" not found`);
168
+
169
+ if (input.code !== undefined) {
170
+ const clash = await this.prisma.language.findFirst({
171
+ where: {
172
+ workspaceId: workspaceIdBig,
173
+ NOT: { id: languageIdBig },
174
+ code: input.code,
175
+ },
176
+ });
177
+ if (clash)
178
+ throw new ConflictException(
179
+ "a language with this code already exists in this workspace",
180
+ );
181
+ }
182
+
183
+ const row = await this.prisma.language.update({
184
+ where: { id: languageIdBig },
185
+ data: { ...input, updatedBy: toIdOrNull(actorUserId) },
186
+ });
187
+ return toLanguageSummary(row);
188
+ }
189
+
190
+ // Soft-delete, matching every other table's isDeleted/deletedAt/deletedBy/deletedReason pattern.
191
+ async delete(
192
+ workspaceId: string,
193
+ languageId: string,
194
+ actorUserId: string | null,
195
+ reason?: string,
196
+ ): Promise<void> {
197
+ const workspaceIdBig = toId(workspaceId);
198
+ const languageIdBig = toId(languageId);
199
+ const existing = await this.prisma.language.findUnique({
200
+ where: {
201
+ id: languageIdBig,
202
+ workspaceId: workspaceIdBig,
203
+ isDeleted: false,
204
+ },
205
+ select: { id: true },
206
+ });
207
+ if (!existing)
208
+ throw new NotFoundException(`language "${languageId}" not found`);
209
+ await this.prisma.language.update({
210
+ where: { id: languageIdBig },
211
+ data: {
212
+ isDeleted: true,
213
+ deletedAt: new Date(),
214
+ deletedBy: toIdOrNull(actorUserId),
215
+ deletedReason: reason ?? null,
216
+ },
217
+ });
218
+ }
219
+
220
+ async setActive(
221
+ workspaceId: string,
222
+ languageId: string,
223
+ isActive: boolean,
224
+ actorUserId: string | null,
225
+ ): Promise<void> {
226
+ const workspaceIdBig = toId(workspaceId);
227
+ const languageIdBig = toId(languageId);
228
+ const existing = await this.prisma.language.findUnique({
229
+ where: {
230
+ id: languageIdBig,
231
+ workspaceId: workspaceIdBig,
232
+ isDeleted: false,
233
+ },
234
+ select: { id: true },
235
+ });
236
+ if (!existing)
237
+ throw new NotFoundException(`language "${languageId}" not found`);
238
+ await this.prisma.language.update({
239
+ where: { id: languageIdBig },
240
+ data: { isActive, updatedBy: toIdOrNull(actorUserId) },
241
+ });
242
+ }
243
+ }