@react-spa-scaffold/mcp 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -0
- package/dist/constants.js.map +1 -1
- package/dist/features/definitions/database.d.ts +3 -0
- package/dist/features/definitions/database.d.ts.map +1 -0
- package/dist/features/definitions/database.js +45 -0
- package/dist/features/definitions/database.js.map +1 -0
- package/dist/features/definitions/deployment.d.ts +3 -0
- package/dist/features/definitions/deployment.d.ts.map +1 -0
- package/dist/features/definitions/deployment.js +14 -0
- package/dist/features/definitions/deployment.js.map +1 -0
- package/dist/features/definitions/index.d.ts +2 -0
- package/dist/features/definitions/index.d.ts.map +1 -1
- package/dist/features/definitions/index.js +2 -0
- package/dist/features/definitions/index.js.map +1 -1
- package/dist/features/registry.d.ts.map +1 -1
- package/dist/features/registry.js +3 -1
- package/dist/features/registry.js.map +1 -1
- package/dist/features/types.test.js +4 -2
- package/dist/features/types.test.js.map +1 -1
- package/dist/resources/docs.d.ts.map +1 -1
- package/dist/resources/docs.js +5 -0
- package/dist/resources/docs.js.map +1 -1
- package/dist/tools/add-features.js +1 -1
- package/dist/tools/add-features.js.map +1 -1
- package/dist/utils/docs.d.ts.map +1 -1
- package/dist/utils/docs.js +2 -0
- package/dist/utils/docs.js.map +1 -1
- package/dist/utils/scaffold/claude-md/index.d.ts.map +1 -1
- package/dist/utils/scaffold/claude-md/index.js +3 -1
- package/dist/utils/scaffold/claude-md/index.js.map +1 -1
- package/dist/utils/scaffold/claude-md/sections.d.ts +2 -0
- package/dist/utils/scaffold/claude-md/sections.d.ts.map +1 -1
- package/dist/utils/scaffold/claude-md/sections.js +132 -2
- package/dist/utils/scaffold/claude-md/sections.js.map +1 -1
- package/dist/utils/scaffold/compute.js +1 -1
- package/dist/utils/scaffold/compute.js.map +1 -1
- package/dist/utils/scaffold/generators.d.ts +2 -2
- package/dist/utils/scaffold/generators.d.ts.map +1 -1
- package/dist/utils/scaffold/generators.js +57 -22
- package/dist/utils/scaffold/generators.js.map +1 -1
- package/package.json +1 -1
- package/templates/.env.example +40 -12
- package/templates/.github/workflows/ci.yml +4 -1
- package/templates/.github/workflows/deploy.yml +59 -0
- package/templates/CLAUDE.md +177 -1
- package/templates/docs/AUTHENTICATION.md +325 -0
- package/templates/docs/DEPLOYMENT.md +268 -0
- package/templates/docs/E2E_TESTING.md +81 -4
- package/templates/docs/SUPABASE_INTEGRATION.md +310 -0
- package/templates/docs/TESTING.md +195 -77
- package/templates/e2e/auth/auth.setup.ts +60 -0
- package/templates/e2e/fixtures/index.ts +11 -0
- package/templates/e2e/tests/profile.auth.spec.ts +103 -0
- package/templates/e2e/tests/profile.spec.ts +64 -0
- package/templates/e2e/tests/register-form.spec.ts +38 -0
- package/templates/gitignore +5 -0
- package/templates/package.json +8 -0
- package/templates/playwright.config.ts +33 -3
- package/templates/src/App.tsx +32 -19
- package/templates/src/components/layout/Header.test.tsx +17 -1
- package/templates/src/components/layout/Header.tsx +11 -0
- package/templates/src/components/shared/AccountButton/AccountButton.test.tsx +3 -3
- package/templates/src/components/shared/ProfileSync/ProfileSync.test.tsx +44 -0
- package/templates/src/components/shared/ProfileSync/ProfileSync.tsx +104 -0
- package/templates/src/components/shared/ProfileSync/index.ts +1 -0
- package/templates/src/components/shared/ProtectedRoute/ProtectedRoute.test.tsx +3 -3
- package/templates/src/components/shared/index.ts +1 -0
- package/templates/src/contexts/performanceContext.tsx +3 -3
- package/templates/src/contexts/supabaseContext.test.tsx +59 -0
- package/templates/src/contexts/supabaseContext.tsx +87 -0
- package/templates/src/hooks/index.ts +17 -0
- package/templates/src/hooks/supabase/index.ts +12 -0
- package/templates/src/hooks/supabase/useProfiles.test.tsx +207 -0
- package/templates/src/hooks/supabase/useProfiles.ts +213 -0
- package/templates/src/hooks/supabase/useSupabaseQuery.test.tsx +150 -0
- package/templates/src/hooks/supabase/useSupabaseQuery.ts +91 -0
- package/templates/src/lib/api.test.ts +30 -38
- package/templates/src/lib/api.ts +1 -7
- package/templates/src/lib/config.ts +54 -4
- package/templates/src/lib/env.ts +36 -14
- package/templates/src/lib/index.ts +4 -2
- package/templates/src/lib/routes.ts +1 -0
- package/templates/src/lib/sentry.ts +13 -10
- package/templates/src/lib/supabase/client.ts +58 -0
- package/templates/src/lib/supabase/index.ts +5 -0
- package/templates/src/main.tsx +17 -39
- package/templates/src/mocks/constants.ts +31 -0
- package/templates/src/mocks/fixtures/index.ts +3 -1
- package/templates/src/mocks/fixtures/profiles.ts +55 -0
- package/templates/src/mocks/fixtures/users.ts +91 -0
- package/templates/src/mocks/handlers/index.ts +2 -1
- package/templates/src/mocks/handlers/supabase.ts +64 -0
- package/templates/src/mocks/handlers/todos.ts +1 -1
- package/templates/src/mocks/index.ts +6 -0
- package/templates/src/pages/Profile.test.tsx +263 -0
- package/templates/src/pages/Profile.tsx +171 -0
- package/templates/src/pages/index.ts +1 -0
- package/templates/src/stores/preferencesStore.ts +2 -1
- package/templates/src/test/clerkMock.tsx +49 -9
- package/templates/src/test/fetchMock.ts +58 -0
- package/templates/src/test/index.ts +49 -3
- package/templates/src/test/mocks.ts +128 -1
- package/templates/src/test/providers.tsx +7 -4
- package/templates/src/test/supabaseMock.ts +112 -0
- package/templates/src/test-setup.ts +26 -0
- package/templates/src/types/database.ts +46 -0
- package/templates/src/types/index.ts +1 -0
- package/templates/src/types/supabase.ts +167 -0
- package/templates/src/vite-env.d.ts +6 -0
- package/templates/supabase/migrations/20260104000000_create_profiles_table.sql +67 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase mocks for testing.
|
|
3
|
+
*
|
|
4
|
+
* Provides mock implementations of the Supabase context and client,
|
|
5
|
+
* with state controls for testing different scenarios.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ReactNode } from 'react';
|
|
9
|
+
import { vi } from 'vitest';
|
|
10
|
+
|
|
11
|
+
// Re-export fixtures for test convenience
|
|
12
|
+
export { createProfile, createProfiles, mockProfiles } from '@/mocks/fixtures/profiles';
|
|
13
|
+
export type { Profile } from '@/types/database';
|
|
14
|
+
|
|
15
|
+
// =============================================================================
|
|
16
|
+
// Mock State
|
|
17
|
+
// =============================================================================
|
|
18
|
+
|
|
19
|
+
interface SupabaseMockState {
|
|
20
|
+
data: unknown[];
|
|
21
|
+
error: { message: string; code: string } | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const defaultState: SupabaseMockState = {
|
|
25
|
+
data: [],
|
|
26
|
+
error: null,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
let mockState: SupabaseMockState = { ...defaultState };
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// State Controls
|
|
33
|
+
// =============================================================================
|
|
34
|
+
|
|
35
|
+
/** Set mock data to be returned by Supabase queries */
|
|
36
|
+
export function setMockSupabaseData(data: unknown[]) {
|
|
37
|
+
mockState.data = data;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Set a mock error to be returned by Supabase queries */
|
|
41
|
+
export function setMockSupabaseError(error: { message: string; code: string } | null) {
|
|
42
|
+
mockState.error = error;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Reset all Supabase mocks to default state */
|
|
46
|
+
export function resetSupabaseMocks() {
|
|
47
|
+
mockState = { ...defaultState };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// =============================================================================
|
|
51
|
+
// Mock Query Builder
|
|
52
|
+
// =============================================================================
|
|
53
|
+
|
|
54
|
+
function createMockQueryBuilder() {
|
|
55
|
+
const resolveQuery = () => {
|
|
56
|
+
if (mockState.error) {
|
|
57
|
+
return { data: null, error: mockState.error };
|
|
58
|
+
}
|
|
59
|
+
return { data: mockState.data, error: null };
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const resolveSingle = () => {
|
|
63
|
+
if (mockState.error) {
|
|
64
|
+
return { data: null, error: mockState.error };
|
|
65
|
+
}
|
|
66
|
+
return { data: mockState.data[0] ?? null, error: null };
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
select: vi.fn().mockReturnThis(),
|
|
71
|
+
insert: vi.fn().mockReturnThis(),
|
|
72
|
+
update: vi.fn().mockReturnThis(),
|
|
73
|
+
delete: vi.fn().mockReturnThis(),
|
|
74
|
+
upsert: vi.fn().mockReturnThis(),
|
|
75
|
+
eq: vi.fn().mockReturnThis(),
|
|
76
|
+
neq: vi.fn().mockReturnThis(),
|
|
77
|
+
single: vi.fn().mockImplementation(() => Promise.resolve(resolveSingle())),
|
|
78
|
+
maybeSingle: vi.fn().mockImplementation(() => Promise.resolve(resolveSingle())),
|
|
79
|
+
then: (resolve: (value: { data: unknown[] | null; error: unknown }) => void) => {
|
|
80
|
+
resolve(resolveQuery());
|
|
81
|
+
return { catch: () => {} };
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// =============================================================================
|
|
87
|
+
// Mock Supabase Client
|
|
88
|
+
// =============================================================================
|
|
89
|
+
|
|
90
|
+
/** Create a mock Supabase client for testing */
|
|
91
|
+
export function createMockSupabaseClient() {
|
|
92
|
+
return {
|
|
93
|
+
from: vi.fn().mockReturnValue(createMockQueryBuilder()),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Singleton client instance for useSupabase hook (maintains referential equality)
|
|
98
|
+
const mockClientInstance = createMockSupabaseClient();
|
|
99
|
+
|
|
100
|
+
// =============================================================================
|
|
101
|
+
// Mock Context (for vi.mock)
|
|
102
|
+
// =============================================================================
|
|
103
|
+
|
|
104
|
+
/** Mock SupabaseProvider - passes through children */
|
|
105
|
+
export function SupabaseProvider({ children }: { children: ReactNode }) {
|
|
106
|
+
return children;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Mock useSupabase hook - returns stable client instance */
|
|
110
|
+
export function useSupabase() {
|
|
111
|
+
return mockClientInstance;
|
|
112
|
+
}
|
|
@@ -1,8 +1,30 @@
|
|
|
1
1
|
import '@testing-library/jest-dom/vitest';
|
|
2
2
|
import { afterAll, afterEach, beforeAll, vi } from 'vitest';
|
|
3
3
|
|
|
4
|
+
// =============================================================================
|
|
5
|
+
// Environment Variable Mock (must be mocked before any imports that use env.ts)
|
|
6
|
+
// =============================================================================
|
|
7
|
+
vi.mock('@/lib/env', () => ({
|
|
8
|
+
env: {
|
|
9
|
+
VITE_APP_NAME: 'My App',
|
|
10
|
+
VITE_APP_URL: 'http://localhost:5173',
|
|
11
|
+
VITE_API_URL: 'https://jsonplaceholder.typicode.com',
|
|
12
|
+
VITE_SENTRY_DSN: 'https://test@sentry.io/123',
|
|
13
|
+
VITE_SENTRY_ENABLED: false,
|
|
14
|
+
VITE_CLERK_PUBLISHABLE_KEY: 'pk_test_mock',
|
|
15
|
+
VITE_SUPABASE_DATABASE_URL: 'https://test.supabase.co',
|
|
16
|
+
VITE_SUPABASE_ANON_KEY: 'test-anon-key',
|
|
17
|
+
VITE_PERF_TEST: false,
|
|
18
|
+
MODE: 'test',
|
|
19
|
+
DEV: false,
|
|
20
|
+
PROD: false,
|
|
21
|
+
},
|
|
22
|
+
validateEnv: vi.fn(),
|
|
23
|
+
}));
|
|
24
|
+
|
|
4
25
|
import { server } from '@/mocks/node';
|
|
5
26
|
import { resetClerkMocks } from '@/test/clerkMock';
|
|
27
|
+
import { resetSupabaseMocks } from '@/test/supabaseMock';
|
|
6
28
|
|
|
7
29
|
// =============================================================================
|
|
8
30
|
// Module Mocks
|
|
@@ -16,6 +38,9 @@ vi.mock('@clerk/themes', () => ({
|
|
|
16
38
|
shadcn: { baseTheme: 'shadcn' },
|
|
17
39
|
}));
|
|
18
40
|
|
|
41
|
+
// Mock Supabase context with test client
|
|
42
|
+
vi.mock('@/contexts/supabaseContext', async () => import('@/test/supabaseMock'));
|
|
43
|
+
|
|
19
44
|
// =============================================================================
|
|
20
45
|
// MSW Server Setup
|
|
21
46
|
// =============================================================================
|
|
@@ -31,6 +56,7 @@ beforeAll(() => {
|
|
|
31
56
|
afterEach(() => {
|
|
32
57
|
server.resetHandlers();
|
|
33
58
|
resetClerkMocks();
|
|
59
|
+
resetSupabaseMocks();
|
|
34
60
|
});
|
|
35
61
|
|
|
36
62
|
// Close MSW server after all tests complete
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database type aliases and re-exports.
|
|
3
|
+
*
|
|
4
|
+
* This file provides a database-agnostic public API for type imports.
|
|
5
|
+
* The underlying types are auto-generated in supabase.ts via `npm run db:types`.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* import type { Profile, ProfileInsert, ProfileUpdate } from '@/types/database';
|
|
9
|
+
*
|
|
10
|
+
* When adding new tables:
|
|
11
|
+
* 1. Run `npm run db:types` to regenerate supabase.ts
|
|
12
|
+
* 2. Add convenience aliases here for your new tables
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Re-export everything from the auto-generated Supabase types
|
|
16
|
+
export * from './supabase';
|
|
17
|
+
|
|
18
|
+
// Re-import for creating aliases
|
|
19
|
+
import type { Database, Tables, TablesInsert, TablesUpdate } from './supabase';
|
|
20
|
+
|
|
21
|
+
// =============================================================================
|
|
22
|
+
// Generic Table Types
|
|
23
|
+
// =============================================================================
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Table names available in the database schema.
|
|
27
|
+
*/
|
|
28
|
+
export type TableName = keyof Database['public']['Tables'];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Row type for a given table.
|
|
32
|
+
*/
|
|
33
|
+
export type TableRowType<T extends TableName> = Database['public']['Tables'][T]['Row'];
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Profile Types
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
/** Profile row type (read operations) */
|
|
40
|
+
export type Profile = Tables<'profiles'>;
|
|
41
|
+
|
|
42
|
+
/** Profile insert type (create operations) */
|
|
43
|
+
export type ProfileInsert = TablesInsert<'profiles'>;
|
|
44
|
+
|
|
45
|
+
/** Profile update type (update operations) */
|
|
46
|
+
export type ProfileUpdate = TablesUpdate<'profiles'>;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[];
|
|
2
|
+
|
|
3
|
+
export type Database = {
|
|
4
|
+
// Allows to automatically instantiate createClient with right options
|
|
5
|
+
// instead of createClient<Database, { PostgrestVersion: 'XX' }>(URL, KEY)
|
|
6
|
+
__InternalSupabase: {
|
|
7
|
+
PostgrestVersion: '14.1';
|
|
8
|
+
};
|
|
9
|
+
public: {
|
|
10
|
+
Tables: {
|
|
11
|
+
profiles: {
|
|
12
|
+
Row: {
|
|
13
|
+
avatar_url: string | null;
|
|
14
|
+
created_at: string | null;
|
|
15
|
+
email: string;
|
|
16
|
+
full_name: string | null;
|
|
17
|
+
id: string;
|
|
18
|
+
updated_at: string | null;
|
|
19
|
+
};
|
|
20
|
+
Insert: {
|
|
21
|
+
avatar_url?: string | null;
|
|
22
|
+
created_at?: string | null;
|
|
23
|
+
email: string;
|
|
24
|
+
full_name?: string | null;
|
|
25
|
+
id: string;
|
|
26
|
+
updated_at?: string | null;
|
|
27
|
+
};
|
|
28
|
+
Update: {
|
|
29
|
+
avatar_url?: string | null;
|
|
30
|
+
created_at?: string | null;
|
|
31
|
+
email?: string;
|
|
32
|
+
full_name?: string | null;
|
|
33
|
+
id?: string;
|
|
34
|
+
updated_at?: string | null;
|
|
35
|
+
};
|
|
36
|
+
Relationships: [];
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
Views: {
|
|
40
|
+
[_ in never]: never;
|
|
41
|
+
};
|
|
42
|
+
Functions: {
|
|
43
|
+
[_ in never]: never;
|
|
44
|
+
};
|
|
45
|
+
Enums: {
|
|
46
|
+
[_ in never]: never;
|
|
47
|
+
};
|
|
48
|
+
CompositeTypes: {
|
|
49
|
+
[_ in never]: never;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type DatabaseWithoutInternals = Omit<Database, '__InternalSupabase'>;
|
|
55
|
+
|
|
56
|
+
type DefaultSchema = DatabaseWithoutInternals[Extract<keyof Database, 'public'>];
|
|
57
|
+
|
|
58
|
+
export type Tables<
|
|
59
|
+
DefaultSchemaTableNameOrOptions extends
|
|
60
|
+
| keyof (DefaultSchema['Tables'] & DefaultSchema['Views'])
|
|
61
|
+
| { schema: keyof DatabaseWithoutInternals },
|
|
62
|
+
TableName extends DefaultSchemaTableNameOrOptions extends {
|
|
63
|
+
schema: keyof DatabaseWithoutInternals;
|
|
64
|
+
}
|
|
65
|
+
? keyof (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'] &
|
|
66
|
+
DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Views'])
|
|
67
|
+
: never = never,
|
|
68
|
+
> = DefaultSchemaTableNameOrOptions extends {
|
|
69
|
+
schema: keyof DatabaseWithoutInternals;
|
|
70
|
+
}
|
|
71
|
+
? (DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'] &
|
|
72
|
+
DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Views'])[TableName] extends {
|
|
73
|
+
Row: infer R;
|
|
74
|
+
}
|
|
75
|
+
? R
|
|
76
|
+
: never
|
|
77
|
+
: DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema['Tables'] & DefaultSchema['Views'])
|
|
78
|
+
? (DefaultSchema['Tables'] & DefaultSchema['Views'])[DefaultSchemaTableNameOrOptions] extends {
|
|
79
|
+
Row: infer R;
|
|
80
|
+
}
|
|
81
|
+
? R
|
|
82
|
+
: never
|
|
83
|
+
: never;
|
|
84
|
+
|
|
85
|
+
export type TablesInsert<
|
|
86
|
+
DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables'] | { schema: keyof DatabaseWithoutInternals },
|
|
87
|
+
TableName extends DefaultSchemaTableNameOrOptions extends {
|
|
88
|
+
schema: keyof DatabaseWithoutInternals;
|
|
89
|
+
}
|
|
90
|
+
? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables']
|
|
91
|
+
: never = never,
|
|
92
|
+
> = DefaultSchemaTableNameOrOptions extends {
|
|
93
|
+
schema: keyof DatabaseWithoutInternals;
|
|
94
|
+
}
|
|
95
|
+
? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends {
|
|
96
|
+
Insert: infer I;
|
|
97
|
+
}
|
|
98
|
+
? I
|
|
99
|
+
: never
|
|
100
|
+
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables']
|
|
101
|
+
? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends {
|
|
102
|
+
Insert: infer I;
|
|
103
|
+
}
|
|
104
|
+
? I
|
|
105
|
+
: never
|
|
106
|
+
: never;
|
|
107
|
+
|
|
108
|
+
export type TablesUpdate<
|
|
109
|
+
DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables'] | { schema: keyof DatabaseWithoutInternals },
|
|
110
|
+
TableName extends DefaultSchemaTableNameOrOptions extends {
|
|
111
|
+
schema: keyof DatabaseWithoutInternals;
|
|
112
|
+
}
|
|
113
|
+
? keyof DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables']
|
|
114
|
+
: never = never,
|
|
115
|
+
> = DefaultSchemaTableNameOrOptions extends {
|
|
116
|
+
schema: keyof DatabaseWithoutInternals;
|
|
117
|
+
}
|
|
118
|
+
? DatabaseWithoutInternals[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends {
|
|
119
|
+
Update: infer U;
|
|
120
|
+
}
|
|
121
|
+
? U
|
|
122
|
+
: never
|
|
123
|
+
: DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables']
|
|
124
|
+
? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends {
|
|
125
|
+
Update: infer U;
|
|
126
|
+
}
|
|
127
|
+
? U
|
|
128
|
+
: never
|
|
129
|
+
: never;
|
|
130
|
+
|
|
131
|
+
export type Enums<
|
|
132
|
+
DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema['Enums'] | { schema: keyof DatabaseWithoutInternals },
|
|
133
|
+
EnumName extends DefaultSchemaEnumNameOrOptions extends {
|
|
134
|
+
schema: keyof DatabaseWithoutInternals;
|
|
135
|
+
}
|
|
136
|
+
? keyof DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions['schema']]['Enums']
|
|
137
|
+
: never = never,
|
|
138
|
+
> = DefaultSchemaEnumNameOrOptions extends {
|
|
139
|
+
schema: keyof DatabaseWithoutInternals;
|
|
140
|
+
}
|
|
141
|
+
? DatabaseWithoutInternals[DefaultSchemaEnumNameOrOptions['schema']]['Enums'][EnumName]
|
|
142
|
+
: DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema['Enums']
|
|
143
|
+
? DefaultSchema['Enums'][DefaultSchemaEnumNameOrOptions]
|
|
144
|
+
: never;
|
|
145
|
+
|
|
146
|
+
export type CompositeTypes<
|
|
147
|
+
PublicCompositeTypeNameOrOptions extends
|
|
148
|
+
| keyof DefaultSchema['CompositeTypes']
|
|
149
|
+
| { schema: keyof DatabaseWithoutInternals },
|
|
150
|
+
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
|
|
151
|
+
schema: keyof DatabaseWithoutInternals;
|
|
152
|
+
}
|
|
153
|
+
? keyof DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes']
|
|
154
|
+
: never = never,
|
|
155
|
+
> = PublicCompositeTypeNameOrOptions extends {
|
|
156
|
+
schema: keyof DatabaseWithoutInternals;
|
|
157
|
+
}
|
|
158
|
+
? DatabaseWithoutInternals[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'][CompositeTypeName]
|
|
159
|
+
: PublicCompositeTypeNameOrOptions extends keyof DefaultSchema['CompositeTypes']
|
|
160
|
+
? DefaultSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions]
|
|
161
|
+
: never;
|
|
162
|
+
|
|
163
|
+
export const Constants = {
|
|
164
|
+
public: {
|
|
165
|
+
Enums: {},
|
|
166
|
+
},
|
|
167
|
+
} as const;
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
interface ImportMetaEnv {
|
|
4
4
|
readonly VITE_APP_NAME: string;
|
|
5
5
|
readonly VITE_APP_URL: string;
|
|
6
|
+
readonly VITE_API_URL?: string;
|
|
7
|
+
readonly VITE_SENTRY_DSN?: string;
|
|
8
|
+
readonly VITE_CLERK_PUBLISHABLE_KEY?: string;
|
|
9
|
+
readonly MODE: 'development' | 'production' | 'test';
|
|
10
|
+
readonly DEV: boolean;
|
|
11
|
+
readonly PROD: boolean;
|
|
6
12
|
}
|
|
7
13
|
|
|
8
14
|
interface ImportMeta {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
-- ============================================================
|
|
2
|
+
-- PROFILES TABLE (linked to Clerk user_id)
|
|
3
|
+
-- ============================================================
|
|
4
|
+
|
|
5
|
+
-- Create the table
|
|
6
|
+
CREATE TABLE IF NOT EXISTS profiles (
|
|
7
|
+
id TEXT PRIMARY KEY, -- Clerk user_id (from auth.uid())
|
|
8
|
+
email TEXT NOT NULL,
|
|
9
|
+
full_name TEXT,
|
|
10
|
+
avatar_url TEXT,
|
|
11
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
12
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
-- Enable Row Level Security
|
|
16
|
+
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
|
|
17
|
+
|
|
18
|
+
-- ============================================================
|
|
19
|
+
-- RLS POLICIES
|
|
20
|
+
-- Using auth.jwt()->>'sub' for Clerk compatibility (string IDs)
|
|
21
|
+
-- ============================================================
|
|
22
|
+
|
|
23
|
+
-- Users can view their own profile
|
|
24
|
+
-- NOTE: Using auth.jwt()->>'sub' instead of auth.uid() because
|
|
25
|
+
-- Clerk user IDs are strings (e.g., user_xxx), not UUIDs
|
|
26
|
+
CREATE POLICY "Users can view own profile"
|
|
27
|
+
ON profiles FOR SELECT TO authenticated
|
|
28
|
+
USING (id = (select auth.jwt()->>'sub'));
|
|
29
|
+
|
|
30
|
+
-- Users can insert their own profile (first login)
|
|
31
|
+
CREATE POLICY "Users can insert own profile"
|
|
32
|
+
ON profiles FOR INSERT TO authenticated
|
|
33
|
+
WITH CHECK (id = (select auth.jwt()->>'sub'));
|
|
34
|
+
|
|
35
|
+
-- Users can update their own profile
|
|
36
|
+
CREATE POLICY "Users can update own profile"
|
|
37
|
+
ON profiles FOR UPDATE TO authenticated
|
|
38
|
+
USING (id = (select auth.jwt()->>'sub'))
|
|
39
|
+
WITH CHECK (id = (select auth.jwt()->>'sub'));
|
|
40
|
+
|
|
41
|
+
-- Users can delete their own profile
|
|
42
|
+
CREATE POLICY "Users can delete own profile"
|
|
43
|
+
ON profiles FOR DELETE TO authenticated
|
|
44
|
+
USING (id = (select auth.jwt()->>'sub'));
|
|
45
|
+
|
|
46
|
+
-- ============================================================
|
|
47
|
+
-- AUTO-UPDATE TIMESTAMP TRIGGER
|
|
48
|
+
-- ============================================================
|
|
49
|
+
|
|
50
|
+
-- Function to update updated_at (with secure search_path)
|
|
51
|
+
CREATE OR REPLACE FUNCTION public.update_updated_at()
|
|
52
|
+
RETURNS TRIGGER
|
|
53
|
+
LANGUAGE plpgsql
|
|
54
|
+
SECURITY DEFINER
|
|
55
|
+
SET search_path = ''
|
|
56
|
+
AS $$
|
|
57
|
+
BEGIN
|
|
58
|
+
NEW.updated_at = NOW();
|
|
59
|
+
RETURN NEW;
|
|
60
|
+
END;
|
|
61
|
+
$$;
|
|
62
|
+
|
|
63
|
+
-- Trigger for profiles
|
|
64
|
+
CREATE TRIGGER profiles_updated_at
|
|
65
|
+
BEFORE UPDATE ON profiles
|
|
66
|
+
FOR EACH ROW
|
|
67
|
+
EXECUTE FUNCTION update_updated_at();
|