@contractspec/example.saas-boilerplate 3.7.6 → 3.7.7
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/.turbo/turbo-build.log +8 -8
- package/AGENTS.md +50 -27
- package/README.md +64 -144
- package/dist/billing/billing.event.js +1 -1
- package/dist/billing/index.d.ts +6 -6
- package/dist/billing/index.js +1 -1
- package/dist/browser/billing/billing.event.js +1 -1
- package/dist/browser/billing/index.js +1 -1
- package/dist/browser/index.js +931 -932
- package/dist/browser/project/index.js +209 -209
- package/dist/browser/project/project.event.js +1 -1
- package/dist/browser/ui/SaasDashboard.js +45 -45
- package/dist/browser/ui/SaasProjectList.js +7 -7
- package/dist/browser/ui/SaasSettingsPanel.js +12 -12
- package/dist/browser/ui/hooks/index.js +2 -2
- package/dist/browser/ui/hooks/useProjectList.js +1 -1
- package/dist/browser/ui/hooks/useProjectMutations.js +1 -1
- package/dist/browser/ui/index.js +483 -484
- package/dist/browser/ui/modals/CreateProjectModal.js +10 -10
- package/dist/browser/ui/modals/ProjectActionsModal.js +13 -13
- package/dist/browser/ui/modals/index.js +23 -23
- package/dist/browser/ui/renderers/index.js +112 -112
- package/dist/browser/ui/renderers/project-list.renderer.js +7 -7
- package/dist/handlers/index.d.ts +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +931 -932
- package/dist/node/billing/billing.event.js +1 -1
- package/dist/node/billing/index.js +1 -1
- package/dist/node/index.js +931 -932
- package/dist/node/project/index.js +209 -209
- package/dist/node/project/project.event.js +1 -1
- package/dist/node/ui/SaasDashboard.js +45 -45
- package/dist/node/ui/SaasProjectList.js +7 -7
- package/dist/node/ui/SaasSettingsPanel.js +12 -12
- package/dist/node/ui/hooks/index.js +2 -2
- package/dist/node/ui/hooks/useProjectList.js +1 -1
- package/dist/node/ui/hooks/useProjectMutations.js +1 -1
- package/dist/node/ui/index.js +483 -484
- package/dist/node/ui/modals/CreateProjectModal.js +10 -10
- package/dist/node/ui/modals/ProjectActionsModal.js +13 -13
- package/dist/node/ui/modals/index.js +23 -23
- package/dist/node/ui/renderers/index.js +112 -112
- package/dist/node/ui/renderers/project-list.renderer.js +7 -7
- package/dist/presentations/index.d.ts +1 -1
- package/dist/project/index.d.ts +7 -7
- package/dist/project/index.js +209 -209
- package/dist/project/project.event.js +1 -1
- package/dist/settings/index.d.ts +1 -1
- package/dist/ui/SaasDashboard.js +45 -45
- package/dist/ui/SaasProjectList.js +7 -7
- package/dist/ui/SaasSettingsPanel.js +12 -12
- package/dist/ui/hooks/index.d.ts +2 -2
- package/dist/ui/hooks/index.js +2 -2
- package/dist/ui/hooks/useProjectList.d.ts +5 -0
- package/dist/ui/hooks/useProjectList.js +1 -1
- package/dist/ui/hooks/useProjectMutations.d.ts +8 -0
- package/dist/ui/hooks/useProjectMutations.js +1 -1
- package/dist/ui/index.d.ts +4 -4
- package/dist/ui/index.js +483 -484
- package/dist/ui/modals/CreateProjectModal.js +10 -10
- package/dist/ui/modals/ProjectActionsModal.js +13 -13
- package/dist/ui/modals/index.js +23 -23
- package/dist/ui/renderers/index.d.ts +1 -1
- package/dist/ui/renderers/index.js +112 -112
- package/dist/ui/renderers/project-list.renderer.d.ts +1 -1
- package/dist/ui/renderers/project-list.renderer.js +7 -7
- package/package.json +10 -10
- package/src/billing/billing.entity.ts +132 -132
- package/src/billing/billing.enum.ts +9 -9
- package/src/billing/billing.event.ts +71 -71
- package/src/billing/billing.handler.ts +87 -87
- package/src/billing/billing.operations.ts +158 -158
- package/src/billing/billing.presentation.ts +45 -45
- package/src/billing/billing.schema.ts +76 -76
- package/src/billing/index.ts +43 -48
- package/src/dashboard/dashboard.presentation.ts +45 -45
- package/src/dashboard/index.ts +2 -2
- package/src/docs/saas-boilerplate.docblock.ts +43 -43
- package/src/example.ts +32 -32
- package/src/handlers/index.ts +9 -9
- package/src/handlers/saas.handlers.ts +250 -249
- package/src/index.ts +40 -41
- package/src/presentations/index.ts +18 -20
- package/src/project/index.ts +45 -50
- package/src/project/project.entity.ts +68 -68
- package/src/project/project.enum.ts +8 -8
- package/src/project/project.event.ts +79 -79
- package/src/project/project.handler.ts +103 -103
- package/src/project/project.operations.ts +236 -236
- package/src/project/project.presentation.ts +46 -46
- package/src/project/project.schema.ts +90 -90
- package/src/saas-boilerplate.feature.ts +100 -100
- package/src/seeders/index.ts +20 -20
- package/src/settings/index.ts +2 -3
- package/src/settings/settings.entity.ts +65 -65
- package/src/settings/settings.enum.ts +4 -4
- package/src/shared/mock-data.ts +92 -92
- package/src/shared/overlay-types.ts +23 -23
- package/src/tests/operations.test-spec.ts +96 -96
- package/src/ui/SaasDashboard.tsx +270 -270
- package/src/ui/SaasProjectList.tsx +90 -90
- package/src/ui/SaasSettingsPanel.tsx +84 -84
- package/src/ui/hooks/index.ts +3 -3
- package/src/ui/hooks/useProjectList.ts +69 -68
- package/src/ui/hooks/useProjectMutations.ts +144 -143
- package/src/ui/index.ts +8 -12
- package/src/ui/modals/CreateProjectModal.tsx +154 -154
- package/src/ui/modals/ProjectActionsModal.tsx +321 -321
- package/src/ui/overlays/demo-overlays.ts +49 -49
- package/src/ui/renderers/index.ts +5 -4
- package/src/ui/renderers/project-list.markdown.ts +204 -204
- package/src/ui/renderers/project-list.renderer.tsx +14 -13
- package/tsconfig.json +7 -8
- package/tsdown.config.js +7 -3
|
@@ -6,295 +6,296 @@
|
|
|
6
6
|
import type { DatabasePort } from '@contractspec/lib.runtime-sandbox';
|
|
7
7
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
8
8
|
import { web } from '@contractspec/lib.runtime-sandbox';
|
|
9
|
+
|
|
9
10
|
const { generateId } = web;
|
|
10
11
|
|
|
11
12
|
// ============ Types ============
|
|
12
13
|
|
|
13
14
|
export interface Project {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
id: string;
|
|
16
|
+
projectId: string;
|
|
17
|
+
organizationId: string;
|
|
18
|
+
name: string;
|
|
19
|
+
description?: string;
|
|
20
|
+
status: 'DRAFT' | 'ACTIVE' | 'ARCHIVED';
|
|
21
|
+
tier: 'FREE' | 'PRO' | 'ENTERPRISE';
|
|
22
|
+
createdAt: Date;
|
|
23
|
+
updatedAt: Date;
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export interface Subscription {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
id: string;
|
|
28
|
+
projectId: string;
|
|
29
|
+
organizationId: string;
|
|
30
|
+
plan: 'FREE' | 'PRO' | 'ENTERPRISE';
|
|
31
|
+
status: 'ACTIVE' | 'PAST_DUE' | 'CANCELED';
|
|
32
|
+
billingCycle: 'MONTHLY' | 'YEARLY';
|
|
33
|
+
currentPeriodStart: Date;
|
|
34
|
+
currentPeriodEnd: Date;
|
|
35
|
+
cancelAtPeriodEnd: boolean;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
export interface ListProjectsInput {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
projectId: string;
|
|
40
|
+
organizationId?: string;
|
|
41
|
+
status?: Project['status'] | 'all';
|
|
42
|
+
search?: string;
|
|
43
|
+
limit?: number;
|
|
44
|
+
offset?: number;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
export interface ListProjectsOutput {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
items: Project[];
|
|
49
|
+
total: number;
|
|
50
|
+
hasMore: boolean;
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
export interface CreateProjectInput {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
name: string;
|
|
55
|
+
description?: string;
|
|
56
|
+
tier?: Project['tier'];
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export interface UpdateProjectInput {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
id: string;
|
|
61
|
+
name?: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
status?: Project['status'];
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
// ============ Row Types ============
|
|
66
67
|
|
|
67
68
|
interface ProjectRow extends Record<string, unknown> {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
id: string;
|
|
70
|
+
projectId: string;
|
|
71
|
+
organizationId: string;
|
|
72
|
+
name: string;
|
|
73
|
+
description: string | null;
|
|
74
|
+
status: string;
|
|
75
|
+
tier: string;
|
|
76
|
+
createdAt: string;
|
|
77
|
+
updatedAt: string;
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
interface SubscriptionRow extends Record<string, unknown> {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
81
|
+
id: string;
|
|
82
|
+
projectId: string;
|
|
83
|
+
organizationId: string;
|
|
84
|
+
plan: string;
|
|
85
|
+
status: string;
|
|
86
|
+
billingCycle: string;
|
|
87
|
+
currentPeriodStart: string;
|
|
88
|
+
currentPeriodEnd: string;
|
|
89
|
+
cancelAtPeriodEnd: number;
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
function rowToProject(row: ProjectRow): Project {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
93
|
+
return {
|
|
94
|
+
id: row.id,
|
|
95
|
+
projectId: row.projectId,
|
|
96
|
+
organizationId: row.organizationId,
|
|
97
|
+
name: row.name,
|
|
98
|
+
description: row.description ?? undefined,
|
|
99
|
+
status: row.status as Project['status'],
|
|
100
|
+
tier: row.tier as Project['tier'],
|
|
101
|
+
createdAt: new Date(row.createdAt),
|
|
102
|
+
updatedAt: new Date(row.updatedAt),
|
|
103
|
+
};
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
function rowToSubscription(row: SubscriptionRow): Subscription {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
107
|
+
return {
|
|
108
|
+
id: row.id,
|
|
109
|
+
projectId: row.projectId,
|
|
110
|
+
organizationId: row.organizationId,
|
|
111
|
+
plan: row.plan as Subscription['plan'],
|
|
112
|
+
status: row.status as Subscription['status'],
|
|
113
|
+
billingCycle: row.billingCycle as Subscription['billingCycle'],
|
|
114
|
+
currentPeriodStart: new Date(row.currentPeriodStart),
|
|
115
|
+
currentPeriodEnd: new Date(row.currentPeriodEnd),
|
|
116
|
+
cancelAtPeriodEnd: Boolean(row.cancelAtPeriodEnd),
|
|
117
|
+
};
|
|
117
118
|
}
|
|
118
119
|
|
|
119
120
|
// ============ Handler Factory ============
|
|
120
121
|
|
|
121
122
|
export function createSaasHandlers(db: DatabasePort) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
123
|
+
/**
|
|
124
|
+
* List projects
|
|
125
|
+
*/
|
|
126
|
+
async function listProjects(
|
|
127
|
+
input: ListProjectsInput
|
|
128
|
+
): Promise<ListProjectsOutput> {
|
|
129
|
+
const {
|
|
130
|
+
projectId,
|
|
131
|
+
organizationId,
|
|
132
|
+
status,
|
|
133
|
+
search,
|
|
134
|
+
limit = 20,
|
|
135
|
+
offset = 0,
|
|
136
|
+
} = input;
|
|
137
|
+
|
|
138
|
+
let whereClause = 'WHERE projectId = ?';
|
|
139
|
+
const params: (string | number)[] = [projectId];
|
|
140
|
+
|
|
141
|
+
if (organizationId) {
|
|
142
|
+
whereClause += ' AND organizationId = ?';
|
|
143
|
+
params.push(organizationId);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (status && status !== 'all') {
|
|
147
|
+
whereClause += ' AND status = ?';
|
|
148
|
+
params.push(status);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (search) {
|
|
152
|
+
whereClause += ' AND (name LIKE ? OR description LIKE ?)';
|
|
153
|
+
params.push(`%${search}%`, `%${search}%`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const countResult = (
|
|
157
|
+
await db.query(
|
|
158
|
+
`SELECT COUNT(*) as count FROM saas_project ${whereClause}`,
|
|
159
|
+
params
|
|
160
|
+
)
|
|
161
|
+
).rows as unknown as { count: number }[];
|
|
162
|
+
const total = countResult[0]?.count ?? 0;
|
|
163
|
+
|
|
164
|
+
const rows = (
|
|
165
|
+
await db.query(
|
|
166
|
+
`SELECT * FROM saas_project ${whereClause} ORDER BY createdAt DESC LIMIT ? OFFSET ?`,
|
|
167
|
+
[...params, limit, offset]
|
|
168
|
+
)
|
|
169
|
+
).rows as unknown as ProjectRow[];
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
items: rows.map(rowToProject),
|
|
173
|
+
total,
|
|
174
|
+
hasMore: offset + rows.length < total,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Get a single project
|
|
180
|
+
*/
|
|
181
|
+
async function getProject(id: string): Promise<Project | null> {
|
|
182
|
+
const rows = (
|
|
183
|
+
await db.query(`SELECT * FROM saas_project WHERE id = ?`, [id])
|
|
184
|
+
).rows as unknown as ProjectRow[];
|
|
185
|
+
return rows[0] ? rowToProject(rows[0]) : null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Create a project
|
|
190
|
+
*/
|
|
191
|
+
async function createProject(
|
|
192
|
+
input: CreateProjectInput,
|
|
193
|
+
context: { projectId: string; organizationId: string }
|
|
194
|
+
): Promise<Project> {
|
|
195
|
+
const id = generateId('proj');
|
|
196
|
+
const now = new Date().toISOString();
|
|
197
|
+
|
|
198
|
+
await db.execute(
|
|
199
|
+
`INSERT INTO saas_project (id, projectId, organizationId, name, description, status, tier, createdAt, updatedAt)
|
|
199
200
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
201
|
+
[
|
|
202
|
+
id,
|
|
203
|
+
context.projectId,
|
|
204
|
+
context.organizationId,
|
|
205
|
+
input.name,
|
|
206
|
+
input.description ?? null,
|
|
207
|
+
'DRAFT',
|
|
208
|
+
input.tier ?? 'FREE',
|
|
209
|
+
now,
|
|
210
|
+
now,
|
|
211
|
+
]
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
const rows = (
|
|
215
|
+
await db.query(`SELECT * FROM saas_project WHERE id = ?`, [id])
|
|
216
|
+
).rows as unknown as ProjectRow[];
|
|
217
|
+
|
|
218
|
+
return rowToProject(rows[0]!);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Update a project
|
|
223
|
+
*/
|
|
224
|
+
async function updateProject(input: UpdateProjectInput): Promise<Project> {
|
|
225
|
+
const now = new Date().toISOString();
|
|
226
|
+
const updates: string[] = ['updatedAt = ?'];
|
|
227
|
+
const params: (string | null)[] = [now];
|
|
228
|
+
|
|
229
|
+
if (input.name !== undefined) {
|
|
230
|
+
updates.push('name = ?');
|
|
231
|
+
params.push(input.name);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (input.description !== undefined) {
|
|
235
|
+
updates.push('description = ?');
|
|
236
|
+
params.push(input.description);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (input.status !== undefined) {
|
|
240
|
+
updates.push('status = ?');
|
|
241
|
+
params.push(input.status);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
params.push(input.id);
|
|
245
|
+
|
|
246
|
+
await db.execute(
|
|
247
|
+
`UPDATE saas_project SET ${updates.join(', ')} WHERE id = ?`,
|
|
248
|
+
params
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const rows = (
|
|
252
|
+
await db.query(`SELECT * FROM saas_project WHERE id = ?`, [input.id])
|
|
253
|
+
).rows as unknown as ProjectRow[];
|
|
254
|
+
|
|
255
|
+
if (!rows[0]) {
|
|
256
|
+
throw new Error('NOT_FOUND');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return rowToProject(rows[0]);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Delete a project
|
|
264
|
+
*/
|
|
265
|
+
async function deleteProject(id: string): Promise<void> {
|
|
266
|
+
await db.execute(`DELETE FROM saas_project WHERE id = ?`, [id]);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get subscription for an organization
|
|
271
|
+
*/
|
|
272
|
+
async function getSubscription(input: {
|
|
273
|
+
projectId: string;
|
|
274
|
+
organizationId?: string;
|
|
275
|
+
}): Promise<Subscription | null> {
|
|
276
|
+
let query = `SELECT * FROM saas_subscription WHERE projectId = ?`;
|
|
277
|
+
const params: string[] = [input.projectId];
|
|
278
|
+
|
|
279
|
+
if (input.organizationId) {
|
|
280
|
+
query += ' AND organizationId = ?';
|
|
281
|
+
params.push(input.organizationId);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
query += ' LIMIT 1';
|
|
285
|
+
|
|
286
|
+
const rows = (await db.query(query, params))
|
|
287
|
+
.rows as unknown as SubscriptionRow[];
|
|
288
|
+
return rows[0] ? rowToSubscription(rows[0]) : null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
listProjects,
|
|
293
|
+
getProject,
|
|
294
|
+
createProject,
|
|
295
|
+
updateProject,
|
|
296
|
+
deleteProject,
|
|
297
|
+
getSubscription,
|
|
298
|
+
};
|
|
298
299
|
}
|
|
299
300
|
|
|
300
301
|
export type SaasHandlers = ReturnType<typeof createSaasHandlers>;
|
package/src/index.ts
CHANGED
|
@@ -3,18 +3,17 @@
|
|
|
3
3
|
|
|
4
4
|
// Export all domain modules
|
|
5
5
|
export * from './billing';
|
|
6
|
-
export * from './project';
|
|
7
|
-
export * from './settings';
|
|
8
6
|
export * from './dashboard';
|
|
9
|
-
|
|
7
|
+
export { default as example } from './example';
|
|
8
|
+
export {
|
|
9
|
+
createSaasHandlers,
|
|
10
|
+
type SaasHandlers,
|
|
11
|
+
} from './handlers/saas.handlers';
|
|
12
|
+
export * from './project';
|
|
10
13
|
// Export feature and example metadata
|
|
11
14
|
export * from './saas-boilerplate.feature';
|
|
15
|
+
export * from './settings';
|
|
12
16
|
export * from './ui';
|
|
13
|
-
export {
|
|
14
|
-
createSaasHandlers,
|
|
15
|
-
type SaasHandlers,
|
|
16
|
-
} from './handlers/saas.handlers';
|
|
17
|
-
export { default as example } from './example';
|
|
18
17
|
|
|
19
18
|
// Import docs for registration
|
|
20
19
|
import './docs';
|
|
@@ -22,41 +21,41 @@ import './docs';
|
|
|
22
21
|
// Schema composition configuration
|
|
23
22
|
import { identityRbacSchemaContribution } from '@contractspec/lib.identity-rbac';
|
|
24
23
|
import { jobsSchemaContribution } from '@contractspec/lib.jobs';
|
|
24
|
+
import type { ModuleSchemaContribution } from '@contractspec/lib.schema';
|
|
25
25
|
import { auditTrailSchemaContribution } from '@contractspec/module.audit-trail';
|
|
26
26
|
import { notificationsSchemaContribution } from '@contractspec/module.notifications';
|
|
27
|
-
import type { ModuleSchemaContribution } from '@contractspec/lib.schema';
|
|
28
27
|
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
BillingUsageEntity,
|
|
29
|
+
SubscriptionEntity,
|
|
30
|
+
SubscriptionStatusEnum,
|
|
31
|
+
UsageLimitEntity,
|
|
32
|
+
} from './billing/billing.entity';
|
|
33
|
+
import {
|
|
34
|
+
ProjectEntity,
|
|
35
|
+
ProjectMemberEntity,
|
|
36
|
+
ProjectStatusEnum,
|
|
32
37
|
} from './project/project.entity';
|
|
33
38
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
FeatureFlagEntity,
|
|
40
|
+
SettingsEntity,
|
|
41
|
+
SettingsScopeEnum,
|
|
37
42
|
} from './settings';
|
|
38
|
-
import {
|
|
39
|
-
SubscriptionEntity,
|
|
40
|
-
BillingUsageEntity,
|
|
41
|
-
UsageLimitEntity,
|
|
42
|
-
SubscriptionStatusEnum,
|
|
43
|
-
} from './billing/billing.entity';
|
|
44
43
|
|
|
45
44
|
/**
|
|
46
45
|
* SaaS boilerplate schema contribution.
|
|
47
46
|
*/
|
|
48
47
|
export const saasBoilerplateSchemaContribution: ModuleSchemaContribution = {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
moduleId: '@contractspec/example.saas-boilerplate',
|
|
49
|
+
entities: [
|
|
50
|
+
ProjectEntity,
|
|
51
|
+
ProjectMemberEntity,
|
|
52
|
+
SettingsEntity,
|
|
53
|
+
FeatureFlagEntity,
|
|
54
|
+
SubscriptionEntity,
|
|
55
|
+
BillingUsageEntity,
|
|
56
|
+
UsageLimitEntity,
|
|
57
|
+
],
|
|
58
|
+
enums: [ProjectStatusEnum, SettingsScopeEnum, SubscriptionStatusEnum],
|
|
60
59
|
};
|
|
61
60
|
|
|
62
61
|
/**
|
|
@@ -64,13 +63,13 @@ export const saasBoilerplateSchemaContribution: ModuleSchemaContribution = {
|
|
|
64
63
|
* Use with `database schema:compose` to generate Prisma schema.
|
|
65
64
|
*/
|
|
66
65
|
export const schemaComposition = {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
modules: [
|
|
67
|
+
identityRbacSchemaContribution,
|
|
68
|
+
jobsSchemaContribution,
|
|
69
|
+
auditTrailSchemaContribution,
|
|
70
|
+
notificationsSchemaContribution,
|
|
71
|
+
saasBoilerplateSchemaContribution,
|
|
72
|
+
],
|
|
73
|
+
provider: 'postgresql' as const,
|
|
74
|
+
outputPath: './prisma/schema/generated.prisma',
|
|
76
75
|
};
|