@odvi/create-dtt-framework 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@odvi/create-dtt-framework",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "CLI tool to scaffold new projects with DTT Framework",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,103 +1,106 @@
1
- # Since the ".env" file is gitignored, you can use the ".env.example" file to
2
- # build a new ".env" file when you clone the repo. Keep this file up-to-date
3
- # when you add new variables to `.env`.
4
-
5
- # This file will be committed to version control, so make sure not to have any
6
- # secrets in it. If you are cloning this repo, create a copy of this file named
7
- # ".env" and populate it with your secrets.
8
-
9
- # When adding additional environment variables, the schema in "/src/env.js"
10
- # should be updated accordingly.
11
-
12
- # ========================================
13
- # App Configuration
14
- # ========================================
15
- # The base URL of your application. Used for redirects, webhooks, and callbacks.
16
- # In development: http://localhost:3000
17
- # In production: https://your-domain.com
18
- NEXT_PUBLIC_APP_URL=http://localhost:3000
19
-
20
- # ========================================
21
- # Clerk Authentication
22
- # ========================================
23
- # Clerk publishable key - used on the client side for authentication
24
- # Get this from your Clerk Dashboard > API Keys > Publishable Key
25
- NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx
26
-
27
- # Clerk secret key - used on the server side for authentication
28
- # Get this from your Clerk Dashboard > API Keys > Secret Key
29
- CLERK_SECRET_KEY=sk_test_xxx
30
-
31
- # Clerk webhook secret - used to verify webhook events from Clerk
32
- # Get this from your Clerk Dashboard > Webhooks > Add Endpoint > Signing Secret
33
- CLERK_WEBHOOK_SECRET=whsec_xxx
34
-
35
- # URL path for the sign-in page
36
- NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
37
-
38
- # URL path for the sign-up page
39
- NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
40
-
41
- # Where to redirect users after successful sign-in
42
- NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/health
43
-
44
- # Where to redirect users after successful sign-up
45
- NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/health
46
-
47
- # ========================================
48
- # Supabase
49
- # ========================================
50
- # Supabase project URL - found in your Supabase project settings
51
- NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
52
-
53
- # Supabase anonymous/public key - safe to expose on client side
54
- # Found in your Supabase project settings under API keys
55
- NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJxxx
56
-
57
- # Supabase service role key - full admin access, keep secret!
58
- # Found in your Supabase project settings under API keys
59
- SUPABASE_SERVICE_ROLE_KEY=eyJxxx
60
-
61
- # PostgreSQL connection string for Drizzle ORM
62
- # Use Supabase's Transaction mode pooler for better performance
63
- # Format: postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres
64
- DATABASE_URL=postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres
65
-
66
- # ========================================
67
- # Snowflake Data Warehouse
68
- # ========================================
69
- # Snowflake account identifier (e.g., xxx.us-east-1)
70
- SNOWFLAKE_ACCOUNT=xxx.us-east-1
71
-
72
- # Snowflake username for authentication
73
- SNOWFLAKE_USERNAME=xxx
74
-
75
- # Snowflake password for authentication
76
- SNOWFLAKE_PASSWORD=xxx
77
-
78
- # Snowflake virtual warehouse to use for queries
79
- SNOWFLAKE_WAREHOUSE=COMPUTE_WH
80
-
81
- # Snowflake database name
82
- SNOWFLAKE_DATABASE=ANALYTICS
83
-
84
- # Snowflake schema name within the database
85
- SNOWFLAKE_SCHEMA=PUBLIC
86
-
87
- # Snowflake role with appropriate permissions
88
- SNOWFLAKE_ROLE=ANALYST
89
-
90
- # ========================================
91
- # NextBank API (Placeholder)
92
- # ========================================
93
- # Base URL for the NextBank API endpoint
94
- NEXTBANK_API_URL=https://api.nextbank.com
95
-
96
- # API key for NextBank authentication
97
- NEXTBANK_API_KEY=xxx
98
-
99
- # OAuth client ID for NextBank integration
100
- NEXTBANK_CLIENT_ID=xxx
101
-
102
- # OAuth client secret for NextBank integration
103
- NEXTBANK_CLIENT_SECRET=xxx
1
+ # Since the ".env" file is gitignored, you can use the ".env.example" file to
2
+ # build a new ".env" file when you clone the repo. Keep this file up-to-date
3
+ # when you add new variables to `.env`.
4
+
5
+ # This file will be committed to version control, so make sure not to have any
6
+ # secrets in it. If you are cloning this repo, create a copy of this file named
7
+ # ".env" and populate it with your secrets.
8
+
9
+ # When adding additional environment variables, the schema in "/src/env.js"
10
+ # should be updated accordingly.
11
+
12
+ # ========================================
13
+ # App Configuration
14
+ # ========================================
15
+ # The base URL of your application. Used for redirects, webhooks, and callbacks.
16
+ # In development: http://localhost:3000
17
+ # In production: https://your-domain.com
18
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
19
+
20
+ # ========================================
21
+ # Clerk Authentication
22
+ # ========================================
23
+ # Clerk publishable key - used on the client side for authentication
24
+ # Get this from your Clerk Dashboard > API Keys > Publishable Key
25
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xxx
26
+
27
+ # Clerk secret key - used on the server side for authentication
28
+ # Get this from your Clerk Dashboard > API Keys > Secret Key
29
+ CLERK_SECRET_KEY=sk_test_xxx
30
+
31
+ # Clerk webhook secret - used to verify webhook events from Clerk
32
+ # Get this from your Clerk Dashboard > Webhooks > Add Endpoint > Signing Secret
33
+ CLERK_WEBHOOK_SECRET=whsec_xxx
34
+
35
+ # URL path for the sign-in page
36
+ NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
37
+
38
+ # URL path for the sign-up page
39
+ NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
40
+
41
+ # Where to redirect users after successful sign-in
42
+ NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/health
43
+
44
+ # Where to redirect users after successful sign-up
45
+ NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/health
46
+
47
+ # ========================================
48
+ # Supabase
49
+ # ========================================
50
+ # Supabase project URL - found in your Supabase project settings
51
+ NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
52
+
53
+ # Supabase anonymous/public key - safe to expose on client side
54
+ # Found in your Supabase project settings under API keys
55
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJxxx
56
+
57
+ # Supabase service role key - full admin access, keep secret!
58
+ # Found in your Supabase project settings under API keys
59
+ SUPABASE_SERVICE_ROLE_KEY=eyJxxx
60
+
61
+ # PostgreSQL connection string for Drizzle ORM
62
+ # Use Supabase's Transaction mode pooler for better performance
63
+ # Format: postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres
64
+ DATABASE_URL=postgresql://postgres.[ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres
65
+
66
+ # ========================================
67
+ # Snowflake Data Warehouse
68
+ # ========================================
69
+ # Snowflake account identifier (e.g., ef19411.ap-southeast-1)
70
+ SNOWFLAKE_ACCOUNT=ef19411.ap-southeast-1
71
+
72
+ # Snowflake warehouse to use
73
+ SNOWFLAKE_WAREHOUSE=COMPUTE_WH
74
+
75
+ # Snowflake role
76
+ SNOWFLAKE_ROLE=ACCOUNTADMIN
77
+
78
+ # Authentication method (SNOWFLAKE_JWT for Key Pair Auth)
79
+ SNOWFLAKE_AUTHENTICATOR=SNOWFLAKE_JWT
80
+
81
+ # Snowflake username
82
+ SNOWFLAKE_USERNAME=APP_USER_WITH_KEY_AUTH
83
+
84
+ # Private Key for Authentication (PEM format)
85
+ SNOWFLAKE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
86
+ ...
87
+ -----END PRIVATE KEY-----"
88
+
89
+ # Passphrase for the Private Key (if encrypted)
90
+ SNOWFLAKE_PRIVATE_KEY_PASSPHRASE="<password>"
91
+
92
+ # Enable logging (optional)
93
+ SNOWFLAKE_LOGGING=true
94
+
95
+ # ========================================
96
+ # NextBank API (Placeholder)
97
+ # ========================================
98
+ # Base URL for the NextBank API endpoint
99
+ NEXTBANK_API=https://api.nextbank.com
100
+
101
+ # Username for NextBank Basic Authentication
102
+ NEXTBANK_API_USERNAME=user
103
+
104
+ # Password for NextBank Basic Authentication
105
+ NEXTBANK_API_PASSWORD=pass
106
+
@@ -63,10 +63,9 @@ This document provides a comprehensive guide to managing environment variables f
63
63
 
64
64
  | Variable Name | Type | Required | Description | Example |
65
65
  |---------------|------|----------|-------------|---------|
66
- | `NEXTBANK_API_URL` | Server | No | NextBank API endpoint | `https://api.nextbank.com` |
67
- | `NEXTBANK_API_KEY` | Server | No | NextBank API key | `xxx` |
68
- | `NEXTBANK_CLIENT_ID` | Server | No | NextBank OAuth client ID | `xxx` |
69
- | `NEXTBANK_CLIENT_SECRET` | Server | No | NextBank OAuth client secret | `xxx` |
66
+ | `NEXTBANK_API` | Server | No | NextBank API endpoint | `https://api.nextbank.com` |
67
+ | `NEXTBANK_API_USERNAME` | Server | No | NextBank API username | `user` |
68
+ | `NEXTBANK_API_PASSWORD` | Server | No | NextBank API password | `pass` |
70
69
 
71
70
  ---
72
71
 
@@ -102,16 +102,13 @@ SNOWFLAKE_ROLE=ANALYST
102
102
  # ============================================
103
103
 
104
104
  # NextBank API URL
105
- NEXTBANK_API_URL=https://api.nextbank.com
105
+ NEXTBANK_API=https://api.nextbank.com
106
106
 
107
- # NextBank API key
108
- NEXTBANK_API_KEY=xxx
107
+ # NextBank API username
108
+ NEXTBANK_API_USERNAME=user
109
109
 
110
- # NextBank OAuth client ID
111
- NEXTBANK_CLIENT_ID=xxx
112
-
113
- # NextBank OAuth client secret
114
- NEXTBANK_CLIENT_SECRET=xxx
110
+ # NextBank API password
111
+ NEXTBANK_API_PASSWORD=pass
115
112
  ```
116
113
 
117
114
  ---
@@ -425,40 +422,31 @@ SNOWFLAKE_ROLE=ANALYST
425
422
 
426
423
  **Note:** NextBank is a placeholder integration. These variables are optional and not currently used.
427
424
 
428
- #### NEXTBANK_API_URL
425
+ #### NEXTBANK_API
429
426
 
430
427
  **Purpose:** NextBank API endpoint
431
428
 
432
429
  **Example:**
433
430
  ```bash
434
- NEXTBANK_API_URL=https://api.nextbank.com
435
- ```
436
-
437
- #### NEXTBANK_API_KEY
438
-
439
- **Purpose:** API key for NextBank
440
-
441
- **Example:**
442
- ```bash
443
- NEXTBANK_API_KEY=xxx
431
+ NEXTBANK_API=https://api.nextbank.com
444
432
  ```
445
433
 
446
- #### NEXTBANK_CLIENT_ID
434
+ #### NEXTBANK_API_USERNAME
447
435
 
448
- **Purpose:** OAuth client ID
436
+ **Purpose:** API username for NextBank
449
437
 
450
438
  **Example:**
451
439
  ```bash
452
- NEXTBANK_CLIENT_ID=xxx
440
+ NEXTBANK_API_USERNAME=user
453
441
  ```
454
442
 
455
- #### NEXTBANK_CLIENT_SECRET
443
+ #### NEXTBANK_API_PASSWORD
456
444
 
457
- **Purpose:** OAuth client secret
445
+ **Purpose:** API password for NextBank
458
446
 
459
447
  **Example:**
460
448
  ```bash
461
- NEXTBANK_CLIENT_SECRET=xxx
449
+ NEXTBANK_API_PASSWORD=pass
462
450
  ```
463
451
 
464
452
  ---
@@ -564,6 +564,7 @@ try {
564
564
  - Check service logs for detailed error messages
565
565
  - Verify network connectivity to the service
566
566
  - Check if service is running and accessible
567
+ - For Supabase Database: Ensure migrations are applied (`pnpm db:push` or `pnpm db:migrate`) so `health_check_tests` table exists
567
568
 
568
569
  **Issue: Health check is slow**
569
570
 
@@ -36,24 +36,27 @@ Add the following to your [`.env`](./environment-variables.md) file:
36
36
 
37
37
  ```bash
38
38
  # Snowflake
39
- SNOWFLAKE_ACCOUNT=xxx.us-east-1
40
- SNOWFLAKE_USERNAME=xxx
41
- SNOWFLAKE_PASSWORD=xxx
39
+ SNOWFLAKE_ACCOUNT=ef19411.ap-southeast-1
40
+ SNOWFLAKE_AUTHENTICATOR=SNOWFLAKE_JWT
41
+ SNOWFLAKE_USERNAME=APP_USER_WITH_KEY_AUTH
42
+ SNOWFLAKE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----
43
+ -----END PRIVATE KEY-----"
44
+ SNOWFLAKE_PRIVATE_KEY_PASSPHRASE="<password>"
42
45
  SNOWFLAKE_WAREHOUSE=COMPUTE_WH
43
- SNOWFLAKE_DATABASE=ANALYTICS
44
- SNOWFLAKE_SCHEMA=PUBLIC
45
- SNOWFLAKE_ROLE=ANALYST
46
+ SNOWFLAKE_ROLE=ACCOUNTADMIN
47
+ SNOWFLAKE_LOGGING=true
46
48
  ```
47
49
 
48
50
  **Where to find these values:**
49
51
 
50
- - **Account**: Snowflake URL (e.g., `xy12345.us-east-1` from `https://xy12345.us-east-1.snowflakecomputing.com`)
51
- - **Username**: Your Snowflake username
52
- - **Password**: Your Snowflake password
53
- - **Warehouse**: Compute warehouse name (create one if needed)
54
- - **Database**: Database name (create one if needed)
55
- - **Schema**: Schema name (usually `PUBLIC`)
56
- - **Role**: Role with appropriate permissions
52
+ - **Account**: Snowflake URL (e.g., `ef19411.ap-southeast-1` from `https://ef19411.ap-southeast-1.snowflakecomputing.com`)
53
+ - **Authenticator**: `SNOWFLAKE_JWT` for Key Pair Authentication
54
+ - **Username**: Your Snowflake username (must be configured with public key)
55
+ - **Private Key**: Your private key in PEM format
56
+ - **Passphrase**: Password for your private key (if encrypted)
57
+ - **Warehouse**: Compute warehouse name
58
+ - **Role**: Role with appropriate permissions (e.g., `ACCOUNTADMIN` or custom role)
59
+ - **Logging**: Enable verbose logging (optional)
57
60
 
58
61
  ### 4. Create Snowflake Client
59
62
 
@@ -67,10 +70,10 @@ import { env } from '@/config/env'
67
70
  const config = {
68
71
  account: env.SNOWFLAKE_ACCOUNT,
69
72
  username: env.SNOWFLAKE_USERNAME,
70
- password: env.SNOWFLAKE_PASSWORD,
73
+ authenticator: env.SNOWFLAKE_AUTHENTICATOR,
74
+ privateKey: env.SNOWFLAKE_PRIVATE_KEY,
75
+ privateKeyPass: env.SNOWFLAKE_PRIVATE_KEY_PASSPHRASE,
71
76
  warehouse: env.SNOWFLAKE_WAREHOUSE,
72
- database: env.SNOWFLAKE_DATABASE,
73
- schema: env.SNOWFLAKE_SCHEMA,
74
77
  role: env.SNOWFLAKE_ROLE,
75
78
  }
76
79
 
@@ -146,6 +146,7 @@ export * from './health-checks'
146
146
  pnpm db:generate
147
147
 
148
148
  # Push schema to database (development)
149
+ # This is required for health checks to pass (creates health_check_tests table)
149
150
  pnpm db:push
150
151
 
151
152
  # Or run migrations (production)
@@ -8,5 +8,4 @@ export default {
8
8
  dbCredentials: {
9
9
  url: env.DATABASE_URL,
10
10
  },
11
- tablesFilter: ["dtt-framework_*"],
12
11
  } satisfies Config;
@@ -33,10 +33,9 @@ describe('env configuration', () => {
33
33
  SNOWFLAKE_DATABASE: z.string().default('ANALYTICS'),
34
34
  SNOWFLAKE_SCHEMA: z.string().default('PUBLIC'),
35
35
  SNOWFLAKE_ROLE: z.string().default('ANALYST'),
36
- NEXTBANK_API_URL: z.string().url().optional(),
37
- NEXTBANK_API_KEY: z.string().optional(),
38
- NEXTBANK_CLIENT_ID: z.string().optional(),
39
- NEXTBANK_CLIENT_SECRET: z.string().optional(),
36
+ NEXTBANK_API: z.string().url().optional(),
37
+ NEXTBANK_API_USERNAME: z.string().optional(),
38
+ NEXTBANK_API_PASSWORD: z.string().optional(),
40
39
  });
41
40
 
42
41
  const result = envSchema.safeParse({});
@@ -104,10 +103,9 @@ describe('env configuration', () => {
104
103
 
105
104
  it('should allow optional NextBank fields', () => {
106
105
  const nextbankSchema = z.object({
107
- NEXTBANK_API_URL: z.string().url().optional(),
108
- NEXTBANK_API_KEY: z.string().optional(),
109
- NEXTBANK_CLIENT_ID: z.string().optional(),
110
- NEXTBANK_CLIENT_SECRET: z.string().optional(),
106
+ NEXTBANK_API: z.string().url().optional(),
107
+ NEXTBANK_API_USERNAME: z.string().optional(),
108
+ NEXTBANK_API_PASSWORD: z.string().optional(),
111
109
  });
112
110
 
113
111
  const result = nextbankSchema.safeParse({});
@@ -19,17 +19,17 @@ const envSchema = z.object({
19
19
  // Snowflake
20
20
  SNOWFLAKE_ACCOUNT: z.string().default('placeholder'),
21
21
  SNOWFLAKE_USERNAME: z.string().default('placeholder'),
22
- SNOWFLAKE_PASSWORD: z.string().default('placeholder'),
22
+ SNOWFLAKE_AUTHENTICATOR: z.literal('SNOWFLAKE_JWT').default('SNOWFLAKE_JWT'),
23
+ SNOWFLAKE_PRIVATE_KEY: z.string().default('placeholder'),
24
+ SNOWFLAKE_PRIVATE_KEY_PASSPHRASE: z.string().optional(),
23
25
  SNOWFLAKE_WAREHOUSE: z.string().default('COMPUTE_WH'),
24
- SNOWFLAKE_DATABASE: z.string().default('ANALYTICS'),
25
- SNOWFLAKE_SCHEMA: z.string().default('PUBLIC'),
26
- SNOWFLAKE_ROLE: z.string().default('ANALYST'),
26
+ SNOWFLAKE_ROLE: z.string().default('ACCOUNTADMIN'),
27
+ SNOWFLAKE_LOGGING: z.string().transform((val) => val === 'true').optional(),
27
28
 
28
29
  // NextBank (optional)
29
- NEXTBANK_API_URL: z.string().url().optional(),
30
- NEXTBANK_API_KEY: z.string().optional(),
31
- NEXTBANK_CLIENT_ID: z.string().optional(),
32
- NEXTBANK_CLIENT_SECRET: z.string().optional(),
30
+ NEXTBANK_API: z.string().url().optional(),
31
+ NEXTBANK_API_USERNAME: z.string().optional(),
32
+ NEXTBANK_API_PASSWORD: z.string().optional(),
33
33
  })
34
34
 
35
35
  export const env = envSchema.parse(process.env)
@@ -11,6 +11,7 @@ import { useState } from 'react'
11
11
  import type { HealthStatus } from '@/features/health-check/types'
12
12
  import { useHealthStore } from '../stores/health-store'
13
13
  import { useQueryClient } from '@tanstack/react-query'
14
+ import { useAuth } from '@clerk/nextjs'
14
15
 
15
16
  const statusIcons: Record<HealthStatus, React.ReactNode> = {
16
17
  healthy: <CheckCircle className="h-5 w-5 text-green-500" />,
@@ -43,6 +44,7 @@ export function HealthDashboard() {
43
44
  const [loadingChecks, setLoadingChecks] = useState<Set<string>>(new Set())
44
45
  const healthStore = useHealthStore()
45
46
  const queryClient = useQueryClient()
47
+ const { getToken } = useAuth()
46
48
 
47
49
  const totalHealthChecks = SERVICES.reduce((total, service) => total + service.checks.length, 0)
48
50
 
@@ -85,8 +87,17 @@ export function HealthDashboard() {
85
87
  throw new Error(`Unknown client endpoint: ${endpoint}`)
86
88
  }
87
89
  } else {
90
+ // Get auth token if available
91
+ const token = await getToken()
92
+
88
93
  // Handle server-side checks
89
- const response = await fetch(`/api/health${endpoint}`)
94
+ const response = await fetch(`/api/health${endpoint}`, {
95
+ method: ['/database/write', '/database/delete', '/storage/upload', '/storage/delete'].includes(endpoint) ? 'POST' : 'GET',
96
+ headers: {
97
+ ...(token ? { Authorization: `Bearer ${token}` } : {}),
98
+ },
99
+ ...(endpoint === '/database/delete' || endpoint === '/storage/delete' ? { method: 'DELETE' } : {})
100
+ })
90
101
  // Check content type to ensure JSON
91
102
  const contentType = response.headers.get('content-type')
92
103
  if (!contentType || !contentType.includes('application/json')) {
@@ -50,7 +50,6 @@ export const SERVICES = [
50
50
  icon: 'building',
51
51
  checks: [
52
52
  { name: 'Ping API', endpoint: '/nextbank/ping' },
53
- { name: 'Test Authentication', endpoint: '/nextbank/auth' },
54
53
  ],
55
54
  },
56
55
  {
@@ -2,35 +2,65 @@ import { env } from '@/config/env'
2
2
 
3
3
  class NextBankClient {
4
4
  private apiUrl: string
5
- private apiKey: string
6
- private accessToken: string | null = null
5
+ private username: string
6
+ private password: string
7
7
 
8
8
  constructor() {
9
- this.apiUrl = env.NEXTBANK_API_URL ?? ''
10
- this.apiKey = env.NEXTBANK_API_KEY ?? ''
9
+ this.apiUrl = env.NEXTBANK_API ?? ''
10
+ this.username = env.NEXTBANK_API_USERNAME ?? ''
11
+ this.password = env.NEXTBANK_API_PASSWORD ?? ''
11
12
  }
12
13
 
13
- async ping(): Promise<{ status: string; timestamp: string }> {
14
- const response = await fetch(`${this.apiUrl}/health`, {
15
- headers: { 'X-API-Key': this.apiKey },
16
- })
17
- if (!response.ok) throw new Error(`NextBank API error: ${response.status}`)
18
- return response.json()
14
+ private getAuthHeader(): Record<string, string> {
15
+ if (!this.username || !this.password) return {}
16
+ const token = btoa(`${this.username}:${this.password}`)
17
+ return { Authorization: `Basic ${token}` }
19
18
  }
20
19
 
21
- async authenticate(): Promise<{ success: boolean; expiresAt: string }> {
22
- const response = await fetch(`${this.apiUrl}/auth/token`, {
23
- method: 'POST',
24
- headers: { 'Content-Type': 'application/json' },
25
- body: JSON.stringify({
26
- clientId: env.NEXTBANK_CLIENT_ID,
27
- clientSecret: env.NEXTBANK_CLIENT_SECRET,
28
- }),
29
- })
30
- if (!response.ok) throw new Error(`NextBank auth error: ${response.status}`)
31
- const data = await response.json()
32
- this.accessToken = data.accessToken
33
- return { success: true, expiresAt: data.expiresAt }
20
+ async ping(fingerprint: string): Promise<{ status: string; timestamp: string }> {
21
+ const url = `${this.apiUrl}/management/status`
22
+ console.log(`[NextBank] Pinging ${url}`)
23
+
24
+ try {
25
+ const response = await fetch(url, {
26
+ method: 'POST',
27
+ headers: {
28
+ ...this.getAuthHeader(),
29
+ 'Content-Type': 'application/json',
30
+ 'Accept': 'application/json',
31
+ 'User-Agent': 'dtt-framework-health-check',
32
+ },
33
+ body: JSON.stringify({
34
+ fingerprint: fingerprint,
35
+ }),
36
+ })
37
+
38
+ const contentType = response.headers.get('content-type')
39
+
40
+ if (!response.ok) {
41
+ if (contentType && contentType.includes('text/html')) {
42
+ const text = await response.text()
43
+ console.error('[NextBank] Received HTML error response. Possible causes:')
44
+ console.error('1. NEXTBANK_API is pointing to the wrong server (e.g. Next.js app instead of NextBank API)')
45
+ console.error('2. The endpoint /management/status does not exist or does not support POST')
46
+ console.error(`[NextBank] Response preview: ${text.substring(0, 150)}...`)
47
+ throw new Error(`NextBank API error: ${response.status} (HTML response)`)
48
+ }
49
+ throw new Error(`NextBank API error: ${response.status} ${response.statusText}`)
50
+ }
51
+
52
+ if (contentType && contentType.includes('application/json')) {
53
+ return await response.json()
54
+ } else {
55
+ const text = await response.text()
56
+ console.error('[NextBank] Invalid response content type:', contentType)
57
+ console.error('[NextBank] Response preview:', text.substring(0, 200))
58
+ throw new Error(`Invalid response format. Expected JSON, got ${contentType}`)
59
+ }
60
+ } catch (error) {
61
+ console.error('[NextBank] Ping failed:', error)
62
+ throw error
63
+ }
34
64
  }
35
65
  }
36
66
 
@@ -1,33 +1,82 @@
1
- import snowflake from 'snowflake-sdk'
2
- import type { Bind } from 'snowflake-sdk'
1
+ import type { Connection, Bind } from 'snowflake-sdk'
3
2
  import { env } from '@/config/env'
4
3
 
4
+ // Helper to format private key correctly
5
+ function formatPrivateKey(key: string | undefined): string | undefined {
6
+ if (!key || key === 'placeholder') return undefined
7
+
8
+ let cleanKey = key
9
+
10
+ // 1. Remove surrounding quotes if they exist
11
+ if ((cleanKey.startsWith('"') && cleanKey.endsWith('"')) ||
12
+ (cleanKey.startsWith("'") && cleanKey.endsWith("'"))) {
13
+ cleanKey = cleanKey.slice(1, -1)
14
+ }
15
+
16
+ // 2. Handle literal escaped newlines (common in one-line env vars)
17
+ if (cleanKey.includes('\\n')) {
18
+ cleanKey = cleanKey.replace(/\\n/g, '\n')
19
+ }
20
+
21
+ // 3. Split by newline to handle potential indentation/whitespace issues
22
+ const lines = cleanKey.split('\n')
23
+ .map(line => line.trim()) // Remove indentation/whitespace from each line
24
+ .filter(line => line.length > 0) // Remove empty lines
25
+
26
+ // 4. Reconstruct the key
27
+ const formattedKey = lines.join('\n')
28
+
29
+ // Debug log (safe)
30
+ if (!formattedKey.includes('BEGIN PRIVATE KEY')) {
31
+ console.warn('Snowflake Private Key may be invalid or missing PKCS#8 header (expected "-----BEGIN PRIVATE KEY-----")')
32
+ }
33
+
34
+ if (formattedKey.includes('BEGIN RSA PRIVATE KEY')) {
35
+ console.warn('Snowflake Private Key appears to be PKCS#1 (RSA). Snowflake requires PKCS#8. Use "openssl pkcs8 -topk8..." to convert.')
36
+ }
37
+
38
+ return formattedKey
39
+ }
40
+
5
41
  const config = {
6
42
  account: env.SNOWFLAKE_ACCOUNT,
7
43
  username: env.SNOWFLAKE_USERNAME,
8
- password: env.SNOWFLAKE_PASSWORD,
44
+ authenticator: env.SNOWFLAKE_AUTHENTICATOR,
45
+ privateKey: formatPrivateKey(env.SNOWFLAKE_PRIVATE_KEY),
46
+ privateKeyPass: env.SNOWFLAKE_PRIVATE_KEY_PASSPHRASE,
9
47
  warehouse: env.SNOWFLAKE_WAREHOUSE,
10
- database: env.SNOWFLAKE_DATABASE,
11
- schema: env.SNOWFLAKE_SCHEMA,
12
48
  role: env.SNOWFLAKE_ROLE,
13
49
  }
14
50
 
15
- export function createSnowflakeConnection() {
51
+ async function getSnowflake() {
52
+ if (process.env.NEXT_RUNTIME === 'edge') {
53
+ throw new Error('Snowflake SDK is not supported in Edge Runtime')
54
+ }
55
+ // Dynamically import snowflake-sdk to avoid build errors in Edge Runtime
56
+ return (await import('snowflake-sdk')).default
57
+ }
58
+
59
+ export async function createSnowflakeConnection(): Promise<Connection> {
60
+ const snowflake = await getSnowflake()
16
61
  return snowflake.createConnection(config)
17
62
  }
18
63
 
19
- export async function connectSnowflake(): Promise<snowflake.Connection> {
20
- return new Promise((resolve, reject) => {
21
- const connection = createSnowflakeConnection()
22
- connection.connect((err, conn) => {
23
- if (err) reject(err)
24
- else resolve(conn)
25
- })
64
+ export async function connectSnowflake(): Promise<Connection> {
65
+ return new Promise(async (resolve, reject) => {
66
+ try {
67
+ const connection = await createSnowflakeConnection()
68
+ connection.connect((err, conn) => {
69
+ if (err) reject(err)
70
+ else resolve(conn)
71
+ })
72
+ } catch (error) {
73
+ reject(error)
74
+ }
26
75
  })
27
76
  }
28
77
 
29
78
  export async function executeQuery<T = unknown>(
30
- connection: snowflake.Connection,
79
+ connection: Connection,
31
80
  sqlText: string,
32
81
  binds?: Bind[]
33
82
  ): Promise<T[]> {
@@ -43,7 +92,7 @@ export async function executeQuery<T = unknown>(
43
92
  })
44
93
  }
45
94
 
46
- export async function destroyConnection(connection: snowflake.Connection): Promise<void> {
95
+ export async function destroyConnection(connection: Connection): Promise<void> {
47
96
  return new Promise((resolve, reject) => {
48
97
  connection.destroy((err) => {
49
98
  if (err) reject(err)
@@ -1,7 +1,5 @@
1
1
  import { Hono } from 'hono'
2
- import { db } from '@/server/db'
3
- import { healthCheckTests } from '@/server/db/schema/health-checks'
4
- import { eq } from 'drizzle-orm'
2
+ import { supabaseAdmin } from '@/lib/supabase/admin'
5
3
 
6
4
  export const databaseHealthRoutes = new Hono()
7
5
 
@@ -12,24 +10,26 @@ databaseHealthRoutes.post('/write', async (c) => {
12
10
 
13
11
  try {
14
12
  // Delete any existing test row first
15
- await db.delete(healthCheckTests).where(eq(healthCheckTests.testKey, TEST_KEY))
13
+ await supabaseAdmin.from('health_check_tests').delete().eq('test_key', TEST_KEY)
16
14
 
17
15
  // Insert a new test row
18
- const result = await db
19
- .insert(healthCheckTests)
20
- .values({
21
- testKey: TEST_KEY,
22
- testValue: `test-${Date.now()}`,
16
+ const { data: result, error } = await supabaseAdmin
17
+ .from('health_check_tests')
18
+ .insert({
19
+ test_key: TEST_KEY,
20
+ test_value: `test-${Date.now()}`,
23
21
  })
24
- .returning()
22
+ .select()
23
+
24
+ if (error) throw error
25
25
 
26
26
  return c.json({
27
27
  status: 'healthy',
28
28
  responseTimeMs: Math.round(performance.now() - start),
29
29
  message: 'Successfully wrote test row to database',
30
30
  data: {
31
- id: result[0]?.id,
32
- testKey: result[0]?.testKey,
31
+ id: result?.[0]?.id,
32
+ testKey: result?.[0]?.test_key,
33
33
  },
34
34
  })
35
35
  } catch (error) {
@@ -48,13 +48,15 @@ databaseHealthRoutes.get('/read', async (c) => {
48
48
  const start = performance.now()
49
49
 
50
50
  try {
51
- const result = await db
52
- .select()
53
- .from(healthCheckTests)
54
- .where(eq(healthCheckTests.testKey, TEST_KEY))
51
+ const { data: result, error } = await supabaseAdmin
52
+ .from('health_check_tests')
53
+ .select('*')
54
+ .eq('test_key', TEST_KEY)
55
55
  .limit(1)
56
56
 
57
- if (result.length === 0) {
57
+ if (error) throw error
58
+
59
+ if (!result || result.length === 0) {
58
60
  return c.json({
59
61
  status: 'healthy',
60
62
  responseTimeMs: Math.round(performance.now() - start),
@@ -70,8 +72,8 @@ databaseHealthRoutes.get('/read', async (c) => {
70
72
  data: {
71
73
  found: true,
72
74
  id: result[0]?.id,
73
- testKey: result[0]?.testKey,
74
- createdAt: result[0]?.createdAt,
75
+ testKey: result[0]?.test_key,
76
+ createdAt: result[0]?.created_at,
75
77
  },
76
78
  })
77
79
  } catch (error) {
@@ -90,18 +92,39 @@ databaseHealthRoutes.delete('/delete', async (c) => {
90
92
  const start = performance.now()
91
93
 
92
94
  try {
93
- const result = await db
94
- .delete(healthCheckTests)
95
- .where(eq(healthCheckTests.testKey, TEST_KEY))
96
- .returning()
95
+ // Check if row exists first
96
+ const { data: result, error: readError } = await supabaseAdmin
97
+ .from('health_check_tests')
98
+ .select('*')
99
+ .eq('test_key', TEST_KEY)
100
+ .limit(1)
101
+
102
+ if (readError) throw readError
103
+
104
+ if (!result || result.length === 0) {
105
+ return c.json({
106
+ status: 'healthy',
107
+ responseTimeMs: Math.round(performance.now() - start),
108
+ message: 'Database connection successful, but no test row to delete',
109
+ data: { deleted: false },
110
+ })
111
+ }
112
+
113
+ // Delete the row
114
+ const { error: deleteError } = await supabaseAdmin
115
+ .from('health_check_tests')
116
+ .delete()
117
+ .eq('test_key', TEST_KEY)
118
+
119
+ if (deleteError) throw deleteError
97
120
 
98
121
  return c.json({
99
122
  status: 'healthy',
100
123
  responseTimeMs: Math.round(performance.now() - start),
101
124
  message: 'Successfully deleted test row from database',
102
125
  data: {
103
- deleted: result.length > 0,
104
- count: result.length,
126
+ deleted: true,
127
+ id: result[0]?.id,
105
128
  },
106
129
  })
107
130
  } catch (error) {
@@ -115,3 +138,4 @@ databaseHealthRoutes.delete('/delete', async (c) => {
115
138
  )
116
139
  }
117
140
  })
141
+
@@ -8,27 +8,31 @@ edgeFunctionsHealthRoutes.get('/ping', async (c) => {
8
8
  const start = performance.now()
9
9
 
10
10
  try {
11
- // Note: This is a placeholder implementation
12
- // In a real setup, you would have an actual edge function deployed
13
- // and would invoke it using Supabase's invoke function
14
-
15
- // For now, we'll simulate a successful ping by checking the Supabase connection
16
- const { data, error } = await supabaseAdmin
17
- .from('_test_connection_')
18
- .select('*')
19
- .limit(1)
11
+ // Invoke the 'health-check' edge function
12
+ const { data, error } = await supabaseAdmin.functions.invoke('health-check', {
13
+ body: { message: 'ping' },
14
+ })
20
15
 
21
- // Even if the table doesn't exist, if we get a connection error, we know Supabase is reachable
22
- // If we get a different error, it means the connection worked
16
+ if (error) {
17
+ // Return detailed error information
18
+ const errorDetails = error as any
19
+ return c.json({
20
+ status: 'error',
21
+ responseTimeMs: Math.round(performance.now() - start),
22
+ message: 'Edge Function invocation failed',
23
+ data: {
24
+ error: error.message,
25
+ context: errorDetails.context || 'No context available',
26
+ hint: 'Ensure the function is deployed: "supabase functions deploy health-check"'
27
+ }
28
+ })
29
+ }
23
30
 
24
31
  return c.json({
25
32
  status: 'healthy',
26
33
  responseTimeMs: Math.round(performance.now() - start),
27
- message: 'Successfully pinged Supabase (edge functions placeholder)',
28
- data: {
29
- note: 'This is a placeholder - deploy an actual edge function to test real functionality',
30
- supabaseUrl: env.NEXT_PUBLIC_SUPABASE_URL,
31
- },
34
+ message: 'Successfully invoked edge function',
35
+ data,
32
36
  })
33
37
  } catch (error) {
34
38
  return c.json(
@@ -46,28 +50,56 @@ edgeFunctionsHealthRoutes.get('/auth', async (c) => {
46
50
  const start = performance.now()
47
51
 
48
52
  try {
49
- // Note: This is a placeholder implementation
50
- // In a real setup, you would invoke an edge function with an auth header
51
- // and verify that the function can access and validate the auth token
52
-
53
- // For now, we'll verify that we can create a valid auth header
53
+ // Verify that we can create a valid auth header and invoke the function
54
54
  const authHeader = c.req.header('authorization')
55
55
 
56
+ // If no auth header is provided to the health check endpoint, we can't test the downstream auth
57
+ if (!authHeader) {
58
+ return c.json({
59
+ status: 'warning', // Changed from error to warning/skipped if user isn't logged in
60
+ responseTimeMs: Math.round(performance.now() - start),
61
+ message: 'No auth header provided - skipping auth propagation check',
62
+ data: {
63
+ skipped: true,
64
+ reason: 'Client request did not include Authorization header',
65
+ },
66
+ })
67
+ }
68
+
69
+ const { data, error } = await supabaseAdmin.functions.invoke('health-check', {
70
+ body: { message: 'auth-check' },
71
+ headers: {
72
+ // We send the auth header in a custom header to verify it can be passed through
73
+ // We avoid overriding 'Authorization' to prevent Supabase Gateway from rejecting
74
+ // the request if the Clerk token hasn't been configured in Supabase yet.
75
+ 'x-test-auth': authHeader,
76
+ },
77
+ })
78
+
79
+ if (error) {
80
+ // If we get an error from the function, it might be due to auth validation failure
81
+ // or the function crashing. We want to expose the error message.
82
+ const errorDetails = error as any
83
+ throw new Error(`Edge function invocation failed: ${error.message || 'Unknown error'}`)
84
+ }
85
+
56
86
  return c.json({
57
87
  status: 'healthy',
58
88
  responseTimeMs: Math.round(performance.now() - start),
59
- message: 'Successfully verified auth header capability (edge functions placeholder)',
60
- data: {
61
- hasAuthHeader: !!authHeader,
62
- note: 'This is a placeholder - deploy an actual edge function to test real functionality',
63
- },
89
+ message: 'Successfully verified auth header with edge function',
90
+ data,
64
91
  })
65
92
  } catch (error) {
93
+ // Check if the error is due to a 401/403 from the function itself (which would come as an error response data if handled,
94
+ // or thrown if using invoke with certain options, but here likely a re-throw)
66
95
  return c.json(
67
96
  {
68
97
  status: 'error',
69
98
  responseTimeMs: Math.round(performance.now() - start),
70
99
  error: error instanceof Error ? error.message : 'Failed to test auth header',
100
+ data: {
101
+ details: error instanceof Error ? error.stack : undefined
102
+ }
71
103
  },
72
104
  500
73
105
  )
@@ -1,6 +1,5 @@
1
1
  import { Hono } from 'hono'
2
- import { db } from '@/server/db'
3
- import { sql } from 'drizzle-orm'
2
+ import { supabaseAdmin } from '@/lib/supabase/admin'
4
3
 
5
4
  export const frameworkHealthRoutes = new Hono()
6
5
 
@@ -20,15 +19,19 @@ frameworkHealthRoutes.get('/hono', (c) => {
20
19
  frameworkHealthRoutes.get('/drizzle', async (c) => {
21
20
  const start = performance.now()
22
21
  try {
23
- // Simple SELECT 1 to verify ORM connection
24
- await db.execute(sql`SELECT 1`)
22
+ // Note: Edge Runtime doesn't support direct Drizzle + postgres.js connection
23
+ // We check Supabase connection instead as a proxy for DB health
24
+ const { error } = await supabaseAdmin.from('health_check_tests').select('count').limit(1)
25
+
26
+ if (error) throw error
25
27
 
26
28
  return c.json({
27
29
  status: 'healthy',
28
30
  responseTimeMs: Math.round(performance.now() - start),
29
- message: 'Drizzle ORM is connected and executing queries',
31
+ message: 'Database access via Supabase Client is working (Edge compatible)',
30
32
  data: {
31
- dialect: 'postgres',
33
+ mode: 'http-client',
34
+ platform: 'supabase',
32
35
  },
33
36
  })
34
37
  } catch (error) {
@@ -36,7 +39,7 @@ frameworkHealthRoutes.get('/drizzle', async (c) => {
36
39
  {
37
40
  status: 'error',
38
41
  responseTimeMs: Math.round(performance.now() - start),
39
- error: error instanceof Error ? error.message : 'Drizzle ORM check failed',
42
+ error: error instanceof Error ? error.message : 'Database check failed',
40
43
  },
41
44
  500
42
45
  )
@@ -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";
@@ -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
  }