@odvi/create-dtt-framework 0.1.5 → 0.1.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.
Files changed (29) hide show
  1. package/dist/index.js +0 -0
  2. package/dist/utils/template.js +1 -0
  3. package/dist/utils/template.js.map +1 -1
  4. package/package.json +50 -51
  5. package/template/.env.example +106 -103
  6. package/template/README.md +157 -7
  7. package/template/docs/framework/deployment/environment-variables.md +3 -4
  8. package/template/docs/framework/environment-variables.md +13 -25
  9. package/template/docs/framework/health-check-system.md +1 -0
  10. package/template/docs/framework/snowflake-integration.md +19 -16
  11. package/template/docs/framework/supabase-integration.md +1 -0
  12. package/template/drizzle.config.ts +0 -1
  13. package/template/src/config/__tests__/env.test.ts +6 -8
  14. package/template/src/config/env.ts +8 -8
  15. package/template/src/features/health-check/components/health-dashboard.tsx +12 -1
  16. package/template/src/features/health-check/config.ts +0 -1
  17. package/template/src/lib/nextbank/client.ts +53 -23
  18. package/template/src/lib/snowflake/client.ts +64 -15
  19. package/template/src/server/api/routes/health/database.ts +49 -25
  20. package/template/src/server/api/routes/health/edge-functions.ts +58 -26
  21. package/template/src/server/api/routes/health/framework.ts +10 -7
  22. package/template/src/server/api/routes/health/nextbank.ts +12 -33
  23. package/template/src/server/api/routes/health/snowflake.ts +2 -2
  24. package/template/src/server/api/routes/health/storage.ts +14 -0
  25. package/template/src/server/api/routes/users.ts +14 -30
  26. package/template/src/server/db/schema.ts +1 -26
  27. package/template/supabase/.temp/cli-latest +1 -0
  28. package/template/supabase/functions/health-check/index.ts +84 -0
  29. package/template/tsconfig.json +1 -1
@@ -7,7 +7,7 @@ export const nextbankHealthRoutes = new Hono()
7
7
  nextbankHealthRoutes.get('/ping', async (c) => {
8
8
  const start = performance.now()
9
9
 
10
- if (!env.NEXTBANK_API_URL || env.NEXTBANK_API_URL === 'placeholder') {
10
+ if (!env.NEXTBANK_API || env.NEXTBANK_API === 'placeholder') {
11
11
  return c.json({
12
12
  status: 'unconfigured',
13
13
  responseTimeMs: Math.round(performance.now() - start),
@@ -15,43 +15,22 @@ nextbankHealthRoutes.get('/ping', async (c) => {
15
15
  })
16
16
  }
17
17
 
18
- try {
19
- const result = await nextbankClient.ping()
20
- return c.json({
21
- status: 'healthy',
22
- responseTimeMs: Math.round(performance.now() - start),
23
- message: 'Successfully pinged NextBank API',
24
- data: result,
25
- })
26
- } catch (error) {
27
- return c.json(
28
- {
29
- status: 'error',
30
- responseTimeMs: Math.round(performance.now() - start),
31
- error: error instanceof Error ? error.message : 'Ping failed',
32
- },
33
- 500
34
- )
35
- }
36
- })
18
+ // Generate fingerprint based on request headers
19
+ const userAgent = c.req.header('user-agent') || 'unknown'
20
+ const ipAddress = c.req.header('x-forwarded-for')?.split(',')[0] ||
21
+ c.req.header('x-real-ip') ||
22
+ '127.0.0.1'
23
+ const acceptLanguage = c.req.header('accept-language') || ''
37
24
 
38
- nextbankHealthRoutes.get('/auth', async (c) => {
39
- const start = performance.now()
40
-
41
- if (!env.NEXTBANK_CLIENT_ID || env.NEXTBANK_CLIENT_ID === 'placeholder') {
42
- return c.json({
43
- status: 'unconfigured',
44
- responseTimeMs: Math.round(performance.now() - start),
45
- message: 'NextBank OAuth not configured',
46
- })
47
- }
25
+ const fingerprintString = `${userAgent}-${ipAddress}-${acceptLanguage}`
26
+ const fingerprint = Buffer.from(fingerprintString).toString('base64')
48
27
 
49
28
  try {
50
- const result = await nextbankClient.authenticate()
29
+ const result = await nextbankClient.ping(fingerprint)
51
30
  return c.json({
52
31
  status: 'healthy',
53
32
  responseTimeMs: Math.round(performance.now() - start),
54
- message: 'Successfully authenticated with NextBank',
33
+ message: 'Successfully pinged NextBank API',
55
34
  data: result,
56
35
  })
57
36
  } catch (error) {
@@ -59,7 +38,7 @@ nextbankHealthRoutes.get('/auth', async (c) => {
59
38
  {
60
39
  status: 'error',
61
40
  responseTimeMs: Math.round(performance.now() - start),
62
- error: error instanceof Error ? error.message : 'Auth failed',
41
+ error: error instanceof Error ? error.message : 'Ping failed',
63
42
  },
64
43
  500
65
44
  )
@@ -26,8 +26,8 @@ snowflakeHealthRoutes.get('/connect', async (c) => {
26
26
  data: {
27
27
  account: env.SNOWFLAKE_ACCOUNT,
28
28
  warehouse: env.SNOWFLAKE_WAREHOUSE,
29
- database: env.SNOWFLAKE_DATABASE,
30
- schema: env.SNOWFLAKE_SCHEMA,
29
+ authenticator: env.SNOWFLAKE_AUTHENTICATOR,
30
+ role: env.SNOWFLAKE_ROLE,
31
31
  },
32
32
  })
33
33
  } catch (error) {
@@ -86,6 +86,20 @@ storageHealthRoutes.get('/download', async (c) => {
86
86
  .from(BUCKET_NAME)
87
87
  .createSignedUrl(TEST_FILE_PATH, 60)
88
88
 
89
+ // Handle "Object not found" as a success state for health check
90
+ // since connectivity is working, but the file just isn't there
91
+ if (error && error.message.includes('Object not found')) {
92
+ return c.json({
93
+ status: 'healthy',
94
+ responseTimeMs: Math.round(performance.now() - start),
95
+ message: 'Storage connection successful, but no test file found to download',
96
+ data: {
97
+ found: false,
98
+ path: TEST_FILE_PATH,
99
+ },
100
+ })
101
+ }
102
+
89
103
  if (error) throw error
90
104
 
91
105
  return c.json({
@@ -1,7 +1,5 @@
1
1
  import { Hono } from 'hono'
2
- import { db } from '@/server/db'
3
- import { users } from '@/server/db/schema/users'
4
- import { eq, desc } from 'drizzle-orm'
2
+ import { supabaseAdmin } from '@/lib/supabase/admin'
5
3
 
6
4
  export const usersRoutes = new Hono()
7
5
 
@@ -9,21 +7,14 @@ usersRoutes.get('/', async (c) => {
9
7
  const start = performance.now()
10
8
 
11
9
  try {
12
- const allUsers = await db
13
- .select({
14
- id: users.id,
15
- email: users.email,
16
- firstName: users.firstName,
17
- lastName: users.lastName,
18
- imageUrl: users.imageUrl,
19
- clerkOrgId: users.clerkOrgId,
20
- createdAt: users.createdAt,
21
- updatedAt: users.updatedAt,
22
- })
23
- .from(users)
24
- .orderBy(desc(users.createdAt))
10
+ const { data: allUsers, error } = await supabaseAdmin
11
+ .from('users')
12
+ .select('id, email, first_name, last_name, image_url, clerk_org_id, created_at, updated_at')
13
+ .order('created_at', { ascending: false })
25
14
  .limit(100)
26
15
 
16
+ if (error) throw error
17
+
27
18
  return c.json({
28
19
  status: 'healthy',
29
20
  responseTimeMs: Math.round(performance.now() - start),
@@ -50,22 +41,15 @@ usersRoutes.get('/:id', async (c) => {
50
41
  const id = c.req.param('id')
51
42
 
52
43
  try {
53
- const user = await db
54
- .select({
55
- id: users.id,
56
- email: users.email,
57
- firstName: users.firstName,
58
- lastName: users.lastName,
59
- imageUrl: users.imageUrl,
60
- clerkOrgId: users.clerkOrgId,
61
- createdAt: users.createdAt,
62
- updatedAt: users.updatedAt,
63
- })
64
- .from(users)
65
- .where(eq(users.id, id))
44
+ const { data: user, error } = await supabaseAdmin
45
+ .from('users')
46
+ .select('id, email, first_name, last_name, image_url, clerk_org_id, created_at, updated_at')
47
+ .eq('id', id)
66
48
  .limit(1)
67
49
 
68
- if (user.length === 0) {
50
+ if (error) throw error
51
+
52
+ if (!user || user.length === 0) {
69
53
  return c.json(
70
54
  {
71
55
  status: 'error',
@@ -1,26 +1 @@
1
- // Example model schema from the Drizzle docs
2
- // https://orm.drizzle.team/docs/sql-schema-declaration
3
-
4
- import { index, pgTableCreator } from "drizzle-orm/pg-core";
5
-
6
- /**
7
- * This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
8
- * database instance for multiple projects.
9
- *
10
- * @see https://orm.drizzle.team/docs/goodies#multi-project-schema
11
- */
12
- export const createTable = pgTableCreator((name) => `dtt-framework_${name}`);
13
-
14
- export const posts = createTable(
15
- "post",
16
- (d) => ({
17
- id: d.integer().primaryKey().generatedByDefaultAsIdentity(),
18
- name: d.varchar({ length: 256 }),
19
- createdAt: d
20
- .timestamp({ withTimezone: true })
21
- .$defaultFn(() => /* @__PURE__ */ new Date())
22
- .notNull(),
23
- updatedAt: d.timestamp({ withTimezone: true }).$onUpdate(() => new Date()),
24
- }),
25
- (t) => [index("name_idx").on(t.name)],
26
- );
1
+ export * from "./schema/index";
@@ -0,0 +1 @@
1
+ v2.67.1
@@ -0,0 +1,84 @@
1
+ // Follow this setup guide to deploy the function:
2
+ // https://supabase.com/docs/guides/functions/deploy
3
+
4
+ import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
5
+
6
+ const corsHeaders = {
7
+ 'Access-Control-Allow-Origin': '*',
8
+ 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
9
+ }
10
+
11
+ serve(async (req) => {
12
+ if (req.method === 'OPTIONS') {
13
+ return new Response('ok', { headers: corsHeaders })
14
+ }
15
+
16
+ try {
17
+ const { message } = await req.json()
18
+ let authHeader = req.headers.get('Authorization')
19
+ const testAuthHeader = req.headers.get('x-test-auth')
20
+
21
+ // Handle ping check
22
+ if (message === 'ping') {
23
+ return new Response(
24
+ JSON.stringify({
25
+ message: 'pong',
26
+ timestamp: new Date().toISOString(),
27
+ }),
28
+ {
29
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
30
+ status: 200,
31
+ },
32
+ )
33
+ }
34
+
35
+ // Handle auth check
36
+ if (message === 'auth-check') {
37
+ // Prioritize test header if present (allows testing without bypassing Supabase Gateway auth)
38
+ if (testAuthHeader) {
39
+ authHeader = testAuthHeader
40
+ }
41
+
42
+ if (!authHeader) {
43
+ return new Response(
44
+ JSON.stringify({ error: 'No authorization header provided' }),
45
+ {
46
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
47
+ status: 401,
48
+ },
49
+ )
50
+ }
51
+
52
+ return new Response(
53
+ JSON.stringify({
54
+ message: 'Auth header received',
55
+ hasAuth: true,
56
+ // Don't log/return the full token in production for security
57
+ tokenPrefix: authHeader.substring(0, 10) + '...',
58
+ source: testAuthHeader ? 'x-test-auth' : 'Authorization'
59
+ }),
60
+ {
61
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
62
+ status: 200,
63
+ },
64
+ )
65
+ }
66
+
67
+ return new Response(
68
+ JSON.stringify({ error: 'Unknown message type' }),
69
+ {
70
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
71
+ status: 400,
72
+ },
73
+ )
74
+ } catch (error) {
75
+ return new Response(
76
+ JSON.stringify({ error: error.message }),
77
+ {
78
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
79
+ status: 400,
80
+ },
81
+ )
82
+ }
83
+ })
84
+
@@ -39,5 +39,5 @@
39
39
  "**/*.js",
40
40
  ".next/types/**/*.ts"
41
41
  ],
42
- "exclude": ["node_modules", "generated", "cli", "src/test", "**/*.test.ts", "**/*.test.tsx", "vitest.config.ts", "drizzle.config.ts"]
42
+ "exclude": ["node_modules", "generated", "cli", "src/test", "**/*.test.ts", "**/*.test.tsx", "vitest.config.ts", "drizzle.config.ts", "supabase"]
43
43
  }