diff --git a/apps/admin/Dockerfile b/apps/admin/Dockerfile index b9576b0..1215c33 100755 --- a/apps/admin/Dockerfile +++ b/apps/admin/Dockerfile @@ -1,17 +1,13 @@ FROM node:20-alpine AS builder WORKDIR /app -COPY package*.json ./ -COPY turbo.json ./ -COPY apps/admin/package.json ./apps/admin/ -COPY packages/shared/package.json ./packages/shared/ -# Install dependencies (only what's needed for admin) -RUN npm install - -# Copy source +# Copy everything first (workspaces need full structure) COPY . . +# Install all dependencies +RUN npm install + # Build shared package first RUN npx turbo run build --filter=@sauci/shared diff --git a/apps/admin/docker-compose.prod.yml b/apps/admin/docker-compose.prod.yml index d893082..497c9d9 100644 --- a/apps/admin/docker-compose.prod.yml +++ b/apps/admin/docker-compose.prod.yml @@ -12,6 +12,4 @@ services: - VITE_AI_MODEL_GENERATE=${VITE_AI_MODEL_GENERATE} - VITE_AI_MODEL_FIX=${VITE_AI_MODEL_FIX} - VITE_AI_MODEL_POLISH=${VITE_AI_MODEL_POLISH} - ports: - - "3000:80" restart: unless-stopped diff --git a/apps/admin/package.json b/apps/admin/package.json index 7d936f1..477b018 100755 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -5,7 +5,8 @@ "type": "module", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "vite build", + "typecheck": "tsc --noEmit", "preview": "vite preview" }, "dependencies": { diff --git a/apps/admin/src/providers/auditDataProvider.ts b/apps/admin/src/providers/auditDataProvider.ts deleted file mode 100644 index fb3b4d8..0000000 --- a/apps/admin/src/providers/auditDataProvider.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { DataProvider, CreateParams, UpdateParams, DeleteParams, DeleteManyParams } from 'react-admin'; -import { dataProvider as baseDataProvider, supabase } from './supabaseProvider'; - -// Tables to audit -const AUDITED_TABLES = [ - 'question_packs', - 'questions', - 'profiles', - 'couples', - 'responses', - 'matches', - 'messages', - 'subscriptions', - 'feedback', -]; - -const logAction = async ( - tableName: string, - recordId: string, - action: 'INSERT' | 'UPDATE' | 'DELETE', - oldValues?: Record | null, - newValues?: Record | null -) => { - try { - await supabase.rpc('log_admin_action', { - p_table_name: tableName, - p_record_id: recordId, - p_action: action, - p_old_values: oldValues || null, - p_new_values: newValues || null, - }); - } catch (error) { - console.error('Failed to log audit action:', error); - // Don't throw - logging failure shouldn't block the operation - } -}; - -export const auditDataProvider: DataProvider = { - ...baseDataProvider, - - create: async ( - resource: string, - params: CreateParams - ) => { - const result = await baseDataProvider.create(resource, params); - - if (AUDITED_TABLES.includes(resource)) { - await logAction(resource, result.data.id, 'INSERT', null, result.data as Record); - } - - return result; - }, - - update: async ( - resource: string, - params: UpdateParams - ) => { - // Fetch old values before update - let oldValues: Record | null = null; - if (AUDITED_TABLES.includes(resource)) { - const { data } = await supabase - .from(resource) - .select('*') - .eq('id', params.id) - .single(); - oldValues = data; - } - - const result = await baseDataProvider.update(resource, params); - - if (AUDITED_TABLES.includes(resource)) { - await logAction(resource, params.id as string, 'UPDATE', oldValues, result.data as Record); - } - - return result; - }, - - delete: async ( - resource: string, - params: DeleteParams - ) => { - // Fetch old values before delete - let oldValues: Record | null = null; - if (AUDITED_TABLES.includes(resource)) { - const { data } = await supabase - .from(resource) - .select('*') - .eq('id', params.id) - .single(); - oldValues = data; - } - - const result = await baseDataProvider.delete(resource, params); - - if (AUDITED_TABLES.includes(resource)) { - await logAction(resource, params.id as string, 'DELETE', oldValues, null); - } - - return result; - }, - - deleteMany: async (resource: string, params: DeleteManyParams) => { - // Fetch old values before delete - if (AUDITED_TABLES.includes(resource)) { - const { data: oldRecords } = await supabase - .from(resource) - .select('*') - .in('id', params.ids); - - const result = await baseDataProvider.deleteMany(resource, params); - - // Log each deletion - for (const record of oldRecords || []) { - await logAction(resource, record.id, 'DELETE', record, null); - } - - return result; - } - - return baseDataProvider.deleteMany(resource, params); - }, -}; diff --git a/apps/admin/src/providers/supabaseProvider.ts b/apps/admin/src/providers/supabaseProvider.ts deleted file mode 100755 index efdd639..0000000 --- a/apps/admin/src/providers/supabaseProvider.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { supabaseDataProvider } from 'ra-supabase'; -import { supabase } from '../config'; - -export { supabase }; - -const resources = { - categories: ['id', 'name', 'description', 'icon', 'sort_order', 'created_at'], - question_packs: ['id', 'name', 'description', 'icon', 'is_premium', 'is_public', 'is_explicit', 'sort_order', 'created_at'], - questions: ['id', 'pack_id', 'text', 'partner_text', 'intensity', 'allowed_couple_genders', 'target_user_genders', 'created_at'], - profiles: ['id', 'name', 'avatar_url', 'is_premium', 'couple_id', 'created_at'], - couples: ['id', 'invite_code', 'created_at'], - responses: ['id', 'user_id', 'question_id', 'couple_id', 'answer', 'created_at'], - matches: ['id', 'couple_id', 'question_id', 'match_type', 'is_new', 'created_at'], - messages: ['id', 'match_id', 'user_id', 'content', 'read_at', 'created_at'], - subscriptions: ['id', 'user_id', 'status', 'product_id', 'expires_at', 'created_at'], - feedback: ['id', 'user_id', 'type', 'title', 'description', 'status', 'created_at'], - audit_logs: ['id', 'table_name', 'record_id', 'action', 'old_values', 'new_values', 'changed_fields', 'admin_user_id', 'admin_role', 'created_at'], - admin_users: ['id', 'user_id', 'role', 'created_at'], -}; - -export const dataProvider = supabaseDataProvider(supabase, resources); diff --git a/apps/admin/src/resources/auditLogs.tsx b/apps/admin/src/resources/auditLogs.tsx deleted file mode 100644 index 5d1a321..0000000 --- a/apps/admin/src/resources/auditLogs.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { - List, - Datagrid, - TextField, - DateField, - FunctionField, - SelectInput, - TextInput, - DateInput, -} from 'react-admin'; - -const auditLogFilters = [ - , - , - , - , -]; - -export const AuditLogList = () => ( - - - - - { - const actionMap: Record = { - INSERT: 'Create', - UPDATE: 'Update', - DELETE: 'Delete', - }; - return actionMap[record.action] || record.action; - }} - /> - - - - record.changed_fields?.join(', ') || '-' - } - /> - { - if (record.action === 'INSERT') return 'New record created'; - if (record.action === 'DELETE') return 'Record deleted'; - return `${record.changed_fields?.length || 0} fields changed`; - }} - /> - - -); diff --git a/apps/admin/src/resources/couples.tsx b/apps/admin/src/resources/couples.tsx deleted file mode 100644 index 1ad1c8e..0000000 --- a/apps/admin/src/resources/couples.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { - List, - Datagrid, - TextField, - DateField, - TextInput, -} from 'react-admin'; - -const coupleFilters = [ - , -]; - -export const CoupleList = () => ( - - - - - - - -); diff --git a/apps/admin/src/resources/matches.tsx b/apps/admin/src/resources/matches.tsx deleted file mode 100644 index 24bf52d..0000000 --- a/apps/admin/src/resources/matches.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { - Show, - SimpleShowLayout, - TextField, - DateField, - ReferenceField, - ReferenceManyField, - List, - Datagrid, - BooleanField, - Labeled, -} from 'react-admin'; - -export const MatchShow = () => ( - - - - - - - - - - - - - - - - - - - - - - - - -); - -export const MatchList = () => ( - - - - - - - - - - - - - -); diff --git a/apps/admin/src/resources/messages.tsx b/apps/admin/src/resources/messages.tsx deleted file mode 100644 index 51feb1e..0000000 --- a/apps/admin/src/resources/messages.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { - List, - Datagrid, - TextField, - DateField, - ReferenceField, - TextInput, -} from 'react-admin'; - -const messageFilters = [ - , -]; - -export const MessageList = () => ( - - - - - - - - - - -); diff --git a/apps/admin/src/resources/packs.tsx b/apps/admin/src/resources/packs.tsx deleted file mode 100755 index 37758d4..0000000 --- a/apps/admin/src/resources/packs.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { List, Datagrid, TextField, BooleanField, EditButton, Create, SimpleForm, TextInput, BooleanInput, Edit } from 'react-admin'; - -export const PackList = () => ( - - - - - - - - - -); - -export const PackEdit = () => ( - - - - - - - - - - -); - -export const PackCreate = () => ( - - - - - - - - - - -); diff --git a/apps/admin/src/resources/questions.tsx b/apps/admin/src/resources/questions.tsx deleted file mode 100755 index 5aac7f4..0000000 --- a/apps/admin/src/resources/questions.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import { - List, - Datagrid, - TextField, - ReferenceField, - NumberField, - EditButton, - Create, - SimpleForm, - TextInput, - NumberInput, - ReferenceInput, - SelectInput, - Edit, - CheckboxGroupInput, - ArrayField, - ChipField, - SingleFieldList, - BooleanField, - FunctionField, -} from 'react-admin'; -import { Typography, Box, Chip, Tooltip, IconButton } from '@mui/material'; -import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; - -const COUPLE_GENDER_CHOICES = [ - { id: 'male+female', name: 'Male + Female' }, - { id: 'male+male', name: 'Male + Male' }, - { id: 'female+female', name: 'Female + Female' }, -]; - -const TARGET_USER_GENDER_CHOICES = [ - { id: 'male', name: 'Male' }, - { id: 'female', name: 'Female' }, - { id: 'non-binary', name: 'Non-binary' }, - { id: 'prefer-not-to-say', name: 'Prefer not to say' }, -]; - -const LabelWithTooltip = ({ label, tooltip }: { label: string; tooltip: string }) => ( - - {label} - - - - - - -); - -const GenderChips = ({ record, source }: { record?: any; source: string }) => { - const values = record?.[source]; - if (!values || values.length === 0) { - return ; - } - return ( - - {values.map((v: string) => ( - - ))} - - ); -}; - -export const QuestionList = () => ( - - - - - - - - record?.partner_text ? 'Yes' : 'No'} - /> - { - const values = record?.target_user_genders; - if (!values || values.length === 0) return 'All'; - return values.join(', '); - }} - /> - - - -); - -const QuestionForm = () => ( - - - - - - - Question Text - - - - - - - - Targeting - - - - } - choices={TARGET_USER_GENDER_CHOICES} - helperText="Leave empty to show to all genders as initiator." - /> - - - } - choices={COUPLE_GENDER_CHOICES} - helperText="Leave empty to show to all couple types." - /> - -); - -export const QuestionEdit = () => ( - - - -); - -export const QuestionCreate = () => ( - - - -); diff --git a/apps/admin/src/resources/users.tsx b/apps/admin/src/resources/users.tsx deleted file mode 100755 index 90aa546..0000000 --- a/apps/admin/src/resources/users.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { - List, - Datagrid, - TextField, - BooleanField, - DateField, - ReferenceField, - Edit, - SimpleForm, - TextInput, - BooleanInput, - ReferenceInput, - SelectInput, - Show, - SimpleShowLayout, - ReferenceManyField, - useRecordContext, - TabbedShowLayout, -} from 'react-admin'; - -export const ProfileList = () => ( - - - - - - - - - - -); - -export const ProfileShow = () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); - -export const ProfileEdit = () => ( - - - - - - - - - -); - -export const ResponseList = () => ( - - - - - - - - - - - - -);