@nubase/create 0.1.4 → 0.1.5

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/dist/index.js CHANGED
@@ -26,7 +26,7 @@ function replaceInContent(content, options) {
26
26
  const kebabName = toKebabCase(options.name);
27
27
  const pascalName = toPascalCase(options.name);
28
28
  const camelName = toCamelCase(options.name);
29
- return content.replace(/__PROJECT_NAME__/g, kebabName).replace(/__PROJECT_NAME_PASCAL__/g, pascalName).replace(/__PROJECT_NAME_CAMEL__/g, camelName).replace(/__DB_NAME__/g, options.dbName).replace(/__DB_USER__/g, options.dbUser).replace(/__DB_PASSWORD__/g, options.dbPassword).replace(/__DEV_PORT__/g, String(options.devPort)).replace(/__TEST_PORT__/g, String(options.testPort)).replace(/__BACKEND_PORT__/g, String(options.backendPort)).replace(/__FRONTEND_PORT__/g, String(options.frontendPort));
29
+ return content.replace(/__PROJECT_NAME__/g, kebabName).replace(/__PROJECT_NAME_PASCAL__/g, pascalName).replace(/__PROJECT_NAME_CAMEL__/g, camelName).replace(/__DB_NAME__/g, options.dbName).replace(/__DB_USER__/g, options.dbUser).replace(/__DB_PASSWORD__/g, options.dbPassword).replace(/__DEV_PORT__/g, String(options.devPort)).replace(/__TEST_PORT__/g, String(options.testPort)).replace(/__BACKEND_PORT__/g, String(options.backendPort)).replace(/__FRONTEND_PORT__/g, String(options.frontendPort)).replace(/__TEST_BACKEND_PORT__/g, String(options.testBackendPort)).replace(/__TEST_FRONTEND_PORT__/g, String(options.testFrontendPort));
30
30
  }
31
31
  function copyTemplateDir(src, dest, options) {
32
32
  fse.ensureDirSync(dest);
@@ -48,7 +48,7 @@ function copyTemplateDir(src, dest, options) {
48
48
  }
49
49
  }
50
50
  async function main() {
51
- program.name("@nubase/create").description("Create a new Nubase application").argument("[project-name]", "Name of the project").option("--db-name <name>", "Database name").option("--db-user <user>", "Database user").option("--db-password <password>", "Database password").option("--dev-port <port>", "Development database port", "5434").option("--test-port <port>", "Test database port", "5435").option("--backend-port <port>", "Backend server port", "3001").option("--frontend-port <port>", "Frontend dev server port", "3002").option("--skip-install", "Skip npm install").parse();
51
+ program.name("@nubase/create").description("Create a new Nubase application").argument("[project-name]", "Name of the project").option("--db-name <name>", "Database name").option("--db-user <user>", "Database user").option("--db-password <password>", "Database password").option("--dev-port <port>", "Development database port", "5434").option("--test-port <port>", "Test database port", "5435").option("--backend-port <port>", "Backend server port", "3001").option("--frontend-port <port>", "Frontend dev server port", "3002").option("--test-backend-port <port>", "Test backend server port", "4001").option("--test-frontend-port <port>", "Test frontend dev server port", "4002").option("--skip-install", "Skip npm install").parse();
52
52
  const args = program.args;
53
53
  const opts = program.opts();
54
54
  console.log(chalk.bold.cyan("\n Welcome to Nubase!\n"));
@@ -78,7 +78,9 @@ async function main() {
78
78
  devPort: Number.parseInt(opts.devPort, 10),
79
79
  testPort: Number.parseInt(opts.testPort, 10),
80
80
  backendPort: Number.parseInt(opts.backendPort, 10),
81
- frontendPort: Number.parseInt(opts.frontendPort, 10)
81
+ frontendPort: Number.parseInt(opts.frontendPort, 10),
82
+ testBackendPort: Number.parseInt(opts.testBackendPort, 10),
83
+ testFrontendPort: Number.parseInt(opts.testFrontendPort, 10)
82
84
  };
83
85
  const targetDir = path.join(process.cwd(), projectName);
84
86
  if (fs.existsSync(targetDir)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nubase/create",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Create a new Nubase application",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,14 +4,14 @@
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "NODE_ENV=development tsx watch src/index.ts",
7
- "dev:test": "PORT=__BACKEND_PORT__ DB_PORT=__TEST_PORT__ NODE_ENV=test tsx watch src/index.ts",
7
+ "dev:test": "PORT=__TEST_BACKEND_PORT__ DB_PORT=__TEST_PORT__ NODE_ENV=test tsx watch src/index.ts",
8
8
  "build": "tsc",
9
9
  "start": "node dist/index.js",
10
10
  "db:dev:up": "docker compose -f docker/dev/docker-compose.yml up -d",
11
11
  "db:dev:down": "docker compose -f docker/dev/docker-compose.yml down",
12
12
  "db:dev:kill": "docker compose -f docker/dev/docker-compose.yml down -v && rm -rf docker/dev/postgresql-data",
13
13
  "db:dev:seed": "NODE_ENV=development tsx src/db/seed.ts",
14
- "db:dev:reset": "npm run db:dev:kill && npm run db:schema-sync && npm run db:dev:up && sleep 2 && npm run db:dev:seed",
14
+ "db:dev:reset": "npm run db:dev:kill && npm run db:schema-sync && npm run db:dev:up && sleep 5 && npm run db:dev:seed",
15
15
  "db:test:up": "docker compose -f docker/test/docker-compose.yml up -d",
16
16
  "db:test:down": "docker compose -f docker/test/docker-compose.yml down",
17
17
  "db:test:kill": "docker compose -f docker/test/docker-compose.yml down -v && rm -rf docker/test/postgresql-data",
@@ -8,7 +8,7 @@ import { and, eq, inArray } from "drizzle-orm";
8
8
  import { apiEndpoints } from "schema";
9
9
  import jwt from "jsonwebtoken";
10
10
  import type { __PROJECT_NAME_PASCAL__User } from "../../auth";
11
- import { db, adminDb } from "../../db/helpers/drizzle";
11
+ import { getAdminDb } from "../../db/helpers/drizzle";
12
12
  import { users, userWorkspaces, workspaces } from "../../db/schema";
13
13
 
14
14
  // Short-lived secret for login tokens (in production, use a proper secret)
@@ -31,7 +31,7 @@ export const authHandlers = {
31
31
  endpoint: apiEndpoints.loginStart,
32
32
  handler: async ({ body }) => {
33
33
  // Find user by username
34
- const [user] = await adminDb
34
+ const [user] = await getAdminDb()
35
35
  .select()
36
36
  .from(users)
37
37
  .where(eq(users.username, body.username));
@@ -50,7 +50,7 @@ export const authHandlers = {
50
50
  }
51
51
 
52
52
  // Get all workspaces this user belongs to
53
- const userWorkspaceRows = await adminDb
53
+ const userWorkspaceRows = await getAdminDb()
54
54
  .select()
55
55
  .from(userWorkspaces)
56
56
  .where(eq(userWorkspaces.userId, user.id));
@@ -61,7 +61,7 @@ export const authHandlers = {
61
61
 
62
62
  // Fetch workspace details
63
63
  const workspaceIds = userWorkspaceRows.map((uw) => uw.workspaceId);
64
- const workspaceList = await adminDb
64
+ const workspaceList = await getAdminDb()
65
65
  .select()
66
66
  .from(workspaces)
67
67
  .where(inArray(workspaces.id, workspaceIds));
@@ -109,7 +109,7 @@ export const authHandlers = {
109
109
  }
110
110
 
111
111
  // Look up the selected workspace
112
- const [workspace] = await adminDb
112
+ const [workspace] = await getAdminDb()
113
113
  .select()
114
114
  .from(workspaces)
115
115
  .where(eq(workspaces.slug, body.workspace));
@@ -119,7 +119,7 @@ export const authHandlers = {
119
119
  }
120
120
 
121
121
  // Verify user has access to this workspace
122
- const [access] = await adminDb
122
+ const [access] = await getAdminDb()
123
123
  .select()
124
124
  .from(userWorkspaces)
125
125
  .where(
@@ -134,7 +134,7 @@ export const authHandlers = {
134
134
  }
135
135
 
136
136
  // Fetch the user
137
- const [dbUser] = await adminDb
137
+ const [dbUser] = await getAdminDb()
138
138
  .select()
139
139
  .from(users)
140
140
  .where(eq(users.id, decoded.userId));
@@ -180,7 +180,7 @@ export const authHandlers = {
180
180
  const authController = getAuthController<__PROJECT_NAME_PASCAL__User>(ctx);
181
181
 
182
182
  // Look up workspace
183
- const [workspace] = await adminDb
183
+ const [workspace] = await getAdminDb()
184
184
  .select()
185
185
  .from(workspaces)
186
186
  .where(eq(workspaces.slug, body.workspace));
@@ -190,7 +190,7 @@ export const authHandlers = {
190
190
  }
191
191
 
192
192
  // Find user by username
193
- const [dbUser] = await adminDb
193
+ const [dbUser] = await getAdminDb()
194
194
  .select()
195
195
  .from(users)
196
196
  .where(eq(users.username, body.username));
@@ -209,7 +209,7 @@ export const authHandlers = {
209
209
  }
210
210
 
211
211
  // Verify user has access to this workspace
212
- const [access] = await adminDb
212
+ const [access] = await getAdminDb()
213
213
  .select()
214
214
  .from(userWorkspaces)
215
215
  .where(
@@ -293,7 +293,7 @@ export const authHandlers = {
293
293
  }
294
294
 
295
295
  // Check if workspace slug already exists
296
- const [existingWorkspace] = await adminDb
296
+ const [existingWorkspace] = await getAdminDb()
297
297
  .select()
298
298
  .from(workspaces)
299
299
  .where(eq(workspaces.slug, body.workspace));
@@ -303,7 +303,7 @@ export const authHandlers = {
303
303
  }
304
304
 
305
305
  // Check if username already exists
306
- const [existingUser] = await adminDb
306
+ const [existingUser] = await getAdminDb()
307
307
  .select()
308
308
  .from(users)
309
309
  .where(eq(users.username, body.username));
@@ -313,7 +313,7 @@ export const authHandlers = {
313
313
  }
314
314
 
315
315
  // Check if email already exists
316
- const [existingEmail] = await adminDb
316
+ const [existingEmail] = await getAdminDb()
317
317
  .select()
318
318
  .from(users)
319
319
  .where(eq(users.email, body.email));
@@ -328,7 +328,7 @@ export const authHandlers = {
328
328
  }
329
329
 
330
330
  // Create the workspace
331
- const [newWorkspace] = await adminDb
331
+ const [newWorkspace] = await getAdminDb()
332
332
  .insert(workspaces)
333
333
  .values({
334
334
  slug: body.workspace,
@@ -340,7 +340,7 @@ export const authHandlers = {
340
340
  const passwordHash = await bcrypt.hash(body.password, 10);
341
341
 
342
342
  // Create the admin user
343
- const [newUser] = await adminDb
343
+ const [newUser] = await getAdminDb()
344
344
  .insert(users)
345
345
  .values({
346
346
  email: body.email,
@@ -350,7 +350,7 @@ export const authHandlers = {
350
350
  .returning();
351
351
 
352
352
  // Link user to workspace
353
- await adminDb.insert(userWorkspaces).values({
353
+ await getAdminDb().insert(userWorkspaces).values({
354
354
  userId: newUser.id,
355
355
  workspaceId: newWorkspace.id,
356
356
  });
@@ -1,2 +1,3 @@
1
1
  export * from "./ticket";
2
2
  export * from "./auth";
3
+ export * from "./test-utils";
@@ -0,0 +1,390 @@
1
+ import { createHttpHandler } from "@nubase/backend";
2
+ import { emptySchema, nu } from "@nubase/core";
3
+ import bcrypt from "bcrypt";
4
+ import { eq, sql } from "drizzle-orm";
5
+ import { getAdminDb } from "../../db/helpers/drizzle";
6
+ import { tickets } from "../../db/schema/ticket";
7
+ import { users } from "../../db/schema/user";
8
+ import { userWorkspaces } from "../../db/schema/user-workspace";
9
+ import { workspaces } from "../../db/schema/workspace";
10
+
11
+ // Default test workspace slug
12
+ const DEFAULT_TEST_WORKSPACE = "tavern";
13
+
14
+ /**
15
+ * Helper to get workspace by slug using admin DB (bypasses RLS).
16
+ * Used by test utilities that don't have auth context.
17
+ */
18
+ async function getWorkspaceBySlug(slug: string) {
19
+ const db = getAdminDb();
20
+ const workspaceRows = await db
21
+ .select()
22
+ .from(workspaces)
23
+ .where(eq(workspaces.slug, slug));
24
+
25
+ if (workspaceRows.length === 0) {
26
+ throw new Error(`Workspace not found: ${slug}`);
27
+ }
28
+ return workspaceRows[0];
29
+ }
30
+
31
+ // Clear all data from the database - used between tests
32
+ export const handleClearDatabase = createHttpHandler({
33
+ endpoint: {
34
+ method: "POST" as const,
35
+ path: "/api/test/clear-database",
36
+ requestParams: emptySchema,
37
+ requestBody: nu.object({
38
+ workspace: nu.string().optional(),
39
+ }),
40
+ responseBody: nu.object({
41
+ success: nu.boolean(),
42
+ message: nu.string(),
43
+ }),
44
+ },
45
+ handler: async ({ body }) => {
46
+ // Only allow in test environment
47
+ if (process.env.NODE_ENV !== "test" && process.env.DB_PORT !== "__TEST_PORT__") {
48
+ throw new Error("Database cleanup is only allowed in test environment");
49
+ }
50
+
51
+ const workspaceSlug = body?.workspace || DEFAULT_TEST_WORKSPACE;
52
+ const workspace = await getWorkspaceBySlug(workspaceSlug);
53
+ const db = getAdminDb();
54
+
55
+ // Clear ALL tickets (not just for this workspace) to avoid sequence conflicts
56
+ await db.delete(tickets);
57
+
58
+ // Reset the ID sequence to start from 1
59
+ await db.execute(sql`ALTER SEQUENCE tickets_id_seq RESTART WITH 1`);
60
+
61
+ // Clear all user_workspaces associations (for all workspaces in test env)
62
+ await db.delete(userWorkspaces);
63
+
64
+ // Clear all users (in test env we start fresh each time)
65
+ await db.delete(users);
66
+
67
+ // Reset the users ID sequence
68
+ await db.execute(sql`ALTER SEQUENCE users_id_seq RESTART WITH 1`);
69
+
70
+ // Seed a default test user for this workspace
71
+ const passwordHash = await bcrypt.hash("password123", 12);
72
+
73
+ // Create the test user
74
+ const [newUser] = await db
75
+ .insert(users)
76
+ .values({
77
+ email: "admin@example.com",
78
+ username: "admin",
79
+ passwordHash,
80
+ })
81
+ .returning();
82
+
83
+ // Link user to workspace
84
+ await db.insert(userWorkspaces).values({
85
+ userId: newUser.id,
86
+ workspaceId: workspace.id,
87
+ });
88
+
89
+ return {
90
+ success: true,
91
+ message: `Database cleared and test user seeded for workspace ${workspace.slug}`,
92
+ };
93
+ },
94
+ });
95
+
96
+ // Seed test data - useful for specific test scenarios
97
+ export const handleSeedTestData = createHttpHandler({
98
+ endpoint: {
99
+ method: "POST" as const,
100
+ path: "/api/test/seed",
101
+ requestParams: emptySchema,
102
+ requestBody: nu.object({
103
+ workspace: nu.string().optional(),
104
+ tickets: nu
105
+ .array(
106
+ nu.object({
107
+ title: nu.string(),
108
+ description: nu.string().optional(),
109
+ }),
110
+ )
111
+ .optional(),
112
+ }),
113
+ responseBody: nu.object({
114
+ success: nu.boolean(),
115
+ message: nu.string(),
116
+ data: nu
117
+ .object({
118
+ ticketIds: nu.array(nu.number()),
119
+ })
120
+ .optional(),
121
+ }),
122
+ },
123
+ handler: async ({ body }) => {
124
+ // Only allow in test environment
125
+ if (process.env.NODE_ENV !== "test" && process.env.DB_PORT !== "__TEST_PORT__") {
126
+ throw new Error("Test seeding is only allowed in test environment");
127
+ }
128
+
129
+ const workspaceSlug = body?.workspace || DEFAULT_TEST_WORKSPACE;
130
+ const workspace = await getWorkspaceBySlug(workspaceSlug);
131
+ const db = getAdminDb();
132
+ const insertedTicketIds: number[] = [];
133
+
134
+ if (body?.tickets) {
135
+ for (const ticket of body.tickets) {
136
+ const result = await db
137
+ .insert(tickets)
138
+ .values({
139
+ workspaceId: workspace.id,
140
+ title: ticket.title,
141
+ description: ticket.description,
142
+ })
143
+ .returning();
144
+
145
+ if (result[0]) {
146
+ insertedTicketIds.push(result[0].id);
147
+ }
148
+ }
149
+ }
150
+
151
+ return {
152
+ success: true,
153
+ message: `Test data seeded for workspace ${workspace.slug}`,
154
+ data: {
155
+ ticketIds: insertedTicketIds,
156
+ },
157
+ };
158
+ },
159
+ });
160
+
161
+ // Get database statistics - useful for verifying test state
162
+ export const handleGetDatabaseStats = createHttpHandler({
163
+ endpoint: {
164
+ method: "GET" as const,
165
+ path: "/api/test/stats",
166
+ requestParams: nu.object({
167
+ workspace: nu.string().optional(),
168
+ }),
169
+ requestBody: emptySchema,
170
+ responseBody: nu.object({
171
+ tickets: nu.object({
172
+ count: nu.number(),
173
+ }),
174
+ }),
175
+ },
176
+ handler: async ({ params }) => {
177
+ // Only allow in test environment
178
+ if (process.env.NODE_ENV !== "test" && process.env.DB_PORT !== "__TEST_PORT__") {
179
+ throw new Error("Database stats are only available in test environment");
180
+ }
181
+
182
+ const workspaceSlug = params?.workspace || DEFAULT_TEST_WORKSPACE;
183
+ const workspace = await getWorkspaceBySlug(workspaceSlug);
184
+ const db = getAdminDb();
185
+ const ticketRows = await db
186
+ .select()
187
+ .from(tickets)
188
+ .where(eq(tickets.workspaceId, workspace.id));
189
+
190
+ return {
191
+ tickets: {
192
+ count: ticketRows.length,
193
+ },
194
+ };
195
+ },
196
+ });
197
+
198
+ // Ensure default workspace exists - called at the start of test runs
199
+ export const handleEnsureWorkspace = createHttpHandler({
200
+ endpoint: {
201
+ method: "POST" as const,
202
+ path: "/api/test/ensure-workspace",
203
+ requestParams: emptySchema,
204
+ requestBody: nu.object({
205
+ workspace: nu.string().optional(),
206
+ }),
207
+ responseBody: nu.object({
208
+ success: nu.boolean(),
209
+ workspace: nu.object({
210
+ id: nu.number(),
211
+ slug: nu.string(),
212
+ name: nu.string(),
213
+ }),
214
+ }),
215
+ },
216
+ handler: async ({ body }) => {
217
+ // Only allow in test environment
218
+ if (process.env.NODE_ENV !== "test" && process.env.DB_PORT !== "__TEST_PORT__") {
219
+ throw new Error(
220
+ "Workspace management is only allowed in test environment",
221
+ );
222
+ }
223
+
224
+ const workspaceSlug = body?.workspace || DEFAULT_TEST_WORKSPACE;
225
+ const db = getAdminDb();
226
+
227
+ // Check if workspace exists
228
+ const existingWorkspaces = await db
229
+ .select()
230
+ .from(workspaces)
231
+ .where(eq(workspaces.slug, workspaceSlug));
232
+
233
+ if (existingWorkspaces.length > 0) {
234
+ return {
235
+ success: true,
236
+ workspace: {
237
+ id: existingWorkspaces[0].id,
238
+ slug: existingWorkspaces[0].slug,
239
+ name: existingWorkspaces[0].name,
240
+ },
241
+ };
242
+ }
243
+
244
+ // Create the workspace
245
+ const result = await db
246
+ .insert(workspaces)
247
+ .values({
248
+ slug: workspaceSlug,
249
+ name: workspaceSlug.charAt(0).toUpperCase() + workspaceSlug.slice(1),
250
+ })
251
+ .returning();
252
+
253
+ if (result.length === 0) {
254
+ throw new Error("Failed to create workspace");
255
+ }
256
+
257
+ return {
258
+ success: true,
259
+ workspace: {
260
+ id: result[0].id,
261
+ slug: result[0].slug,
262
+ name: result[0].name,
263
+ },
264
+ };
265
+ },
266
+ });
267
+
268
+ /**
269
+ * Seed a user with multiple workspaces - for testing workspace selection flow
270
+ */
271
+ export const handleSeedMultiWorkspaceUser = createHttpHandler({
272
+ endpoint: {
273
+ method: "POST" as const,
274
+ path: "/api/test/seed-multi-workspace-user",
275
+ requestParams: emptySchema,
276
+ requestBody: nu.object({
277
+ username: nu.string(),
278
+ password: nu.string(),
279
+ email: nu.string(),
280
+ workspaces: nu.array(
281
+ nu.object({
282
+ slug: nu.string(),
283
+ name: nu.string(),
284
+ }),
285
+ ),
286
+ }),
287
+ responseBody: nu.object({
288
+ success: nu.boolean(),
289
+ message: nu.string(),
290
+ workspaces: nu.array(
291
+ nu.object({
292
+ id: nu.number(),
293
+ slug: nu.string(),
294
+ name: nu.string(),
295
+ }),
296
+ ),
297
+ }),
298
+ },
299
+ handler: async ({ body }) => {
300
+ // Only allow in test environment
301
+ if (process.env.NODE_ENV !== "test" && process.env.DB_PORT !== "__TEST_PORT__") {
302
+ throw new Error(
303
+ "Multi-workspace user seeding is only allowed in test environment",
304
+ );
305
+ }
306
+
307
+ const db = getAdminDb();
308
+ const createdWorkspaces: { id: number; slug: string; name: string }[] = [];
309
+ const passwordHash = await bcrypt.hash(body.password, 12);
310
+
311
+ // Check if user already exists
312
+ let userId: number;
313
+ const existingUsers = await db
314
+ .select()
315
+ .from(users)
316
+ .where(eq(users.username, body.username));
317
+
318
+ if (existingUsers.length > 0) {
319
+ userId = existingUsers[0].id;
320
+ } else {
321
+ // Create user (root-level, no workspace)
322
+ const [newUser] = await db
323
+ .insert(users)
324
+ .values({
325
+ email: body.email,
326
+ username: body.username,
327
+ passwordHash,
328
+ })
329
+ .returning();
330
+ userId = newUser.id;
331
+ }
332
+
333
+ for (const workspaceData of body.workspaces) {
334
+ // Check if workspace exists
335
+ let workspace = await db
336
+ .select()
337
+ .from(workspaces)
338
+ .where(eq(workspaces.slug, workspaceData.slug))
339
+ .then((rows) => rows[0]);
340
+
341
+ // Create workspace if it doesn't exist
342
+ if (!workspace) {
343
+ const result = await db
344
+ .insert(workspaces)
345
+ .values({
346
+ slug: workspaceData.slug,
347
+ name: workspaceData.name,
348
+ })
349
+ .returning();
350
+ workspace = result[0];
351
+ }
352
+
353
+ // Check if user already has access to this workspace
354
+ const existingAccess = await db
355
+ .select()
356
+ .from(userWorkspaces)
357
+ .where(eq(userWorkspaces.userId, userId))
358
+ .then((rows) => rows.find((uw) => uw.workspaceId === workspace.id));
359
+
360
+ // Link user to workspace if not already linked
361
+ if (!existingAccess) {
362
+ await db.insert(userWorkspaces).values({
363
+ userId,
364
+ workspaceId: workspace.id,
365
+ });
366
+ }
367
+
368
+ createdWorkspaces.push({
369
+ id: workspace.id,
370
+ slug: workspace.slug,
371
+ name: workspace.name,
372
+ });
373
+ }
374
+
375
+ return {
376
+ success: true,
377
+ message: `User ${body.username} seeded in ${createdWorkspaces.length} workspaces`,
378
+ workspaces: createdWorkspaces,
379
+ };
380
+ },
381
+ });
382
+
383
+ // Export test utils handlers object
384
+ export const testUtilsHandlers = {
385
+ clearDatabase: handleClearDatabase,
386
+ ensureWorkspace: handleEnsureWorkspace,
387
+ seedTestData: handleSeedTestData,
388
+ getDatabaseStats: handleGetDatabaseStats,
389
+ seedMultiWorkspaceUser: handleSeedMultiWorkspaceUser,
390
+ };
@@ -1,14 +1,14 @@
1
1
  import { createHttpHandler, HttpError } from "@nubase/backend";
2
2
  import { eq } from "drizzle-orm";
3
3
  import { apiEndpoints } from "schema";
4
- import { db } from "../../db/helpers/drizzle";
4
+ import { getDb } from "../../db/helpers/drizzle";
5
5
  import { tickets } from "../../db/schema";
6
6
 
7
7
  export const ticketHandlers = {
8
8
  getTickets: createHttpHandler({
9
9
  endpoint: apiEndpoints.getTickets,
10
10
  handler: async () => {
11
- const allTickets = await db.select().from(tickets);
11
+ const allTickets = await getDb().select().from(tickets);
12
12
  return allTickets.map((ticket) => ({
13
13
  id: ticket.id,
14
14
  title: ticket.title,
@@ -20,7 +20,7 @@ export const ticketHandlers = {
20
20
  getTicket: createHttpHandler({
21
21
  endpoint: apiEndpoints.getTicket,
22
22
  handler: async ({ params }) => {
23
- const [ticket] = await db
23
+ const [ticket] = await getDb()
24
24
  .select()
25
25
  .from(tickets)
26
26
  .where(eq(tickets.id, params.id));
@@ -40,7 +40,7 @@ export const ticketHandlers = {
40
40
  postTicket: createHttpHandler({
41
41
  endpoint: apiEndpoints.postTicket,
42
42
  handler: async ({ body }) => {
43
- const [ticket] = await db
43
+ const [ticket] = await getDb()
44
44
  .insert(tickets)
45
45
  .values({
46
46
  workspaceId: 1, // TODO: Get from context
@@ -75,7 +75,7 @@ export const ticketHandlers = {
75
75
  updateData.description = body.description;
76
76
  }
77
77
 
78
- const [ticket] = await db
78
+ const [ticket] = await getDb()
79
79
  .update(tickets)
80
80
  .set(updateData)
81
81
  .where(eq(tickets.id, params.id))
@@ -96,7 +96,7 @@ export const ticketHandlers = {
96
96
  deleteTicket: createHttpHandler({
97
97
  endpoint: apiEndpoints.deleteTicket,
98
98
  handler: async ({ params }) => {
99
- const [deleted] = await db
99
+ const [deleted] = await getDb()
100
100
  .delete(tickets)
101
101
  .where(eq(tickets.id, params.id))
102
102
  .returning();
@@ -9,7 +9,7 @@ import bcrypt from "bcrypt";
9
9
  import { and, eq } from "drizzle-orm";
10
10
  import type { Context } from "hono";
11
11
  import jwt from "jsonwebtoken";
12
- import { adminDb } from "../db/helpers/drizzle";
12
+ import { getAdminDb } from "../db/helpers/drizzle";
13
13
  import { users, userWorkspaces } from "../db/schema";
14
14
 
15
15
  /**
@@ -77,7 +77,7 @@ export class __PROJECT_NAME_PASCAL__AuthController
77
77
  ) as __PROJECT_NAME_PASCAL__TokenPayload;
78
78
 
79
79
  // Fetch user from database to ensure they still exist
80
- const [dbUser] = await adminDb
80
+ const [dbUser] = await getAdminDb()
81
81
  .select()
82
82
  .from(users)
83
83
  .where(eq(users.id, decoded.userId));
@@ -87,7 +87,7 @@ export class __PROJECT_NAME_PASCAL__AuthController
87
87
  }
88
88
 
89
89
  // Verify user still has access to the workspace in the token
90
- const [access] = await adminDb
90
+ const [access] = await getAdminDb()
91
91
  .select()
92
92
  .from(userWorkspaces)
93
93
  .where(
@@ -180,7 +180,7 @@ export class __PROJECT_NAME_PASCAL__AuthController
180
180
  }
181
181
 
182
182
  // Find user by username
183
- const [dbUser] = await adminDb
183
+ const [dbUser] = await getAdminDb()
184
184
  .select()
185
185
  .from(users)
186
186
  .where(eq(users.username, username));
@@ -196,7 +196,7 @@ export class __PROJECT_NAME_PASCAL__AuthController
196
196
  }
197
197
 
198
198
  // Check if user has access to the workspace
199
- const [access] = await adminDb
199
+ const [access] = await getAdminDb()
200
200
  .select()
201
201
  .from(userWorkspaces)
202
202
  .where(