diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e8ab6ad..becae23d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -262,8 +262,8 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v6 with: - node-version: '20' - cache: 'npm' + node-version: "20" + cache: "npm" cache-dependency-path: client/package-lock.json - name: Audit client dependencies @@ -414,7 +414,7 @@ jobs: - name: Install backend dependencies working-directory: backend - run: npm ci + run: npm install - name: Run backend tests working-directory: backend diff --git a/.github/workflows/database.yml b/.github/workflows/database.yml index c06d4e80..fb475d51 100644 --- a/.github/workflows/database.yml +++ b/.github/workflows/database.yml @@ -4,13 +4,13 @@ on: push: branches: [main, develop] paths: - - 'supabase/migrations/**' - - 'supabase/config.toml' + - "supabase/migrations/**" + - "supabase/config.toml" pull_request: branches: [main, develop] paths: - - 'supabase/migrations/**' - - 'supabase/config.toml' + - "supabase/migrations/**" + - "supabase/config.toml" jobs: validate-migrations: @@ -38,13 +38,13 @@ jobs: - name: Setup Node.js for RLS audit uses: actions/setup-node@v6 with: - node-version: '20' - cache: 'npm' + node-version: "20" + cache: "npm" cache-dependency-path: backend/package-lock.json - name: Install dependencies for RLS audit working-directory: backend - run: npm ci + run: npm install - name: Run RLS Policy Audit env: @@ -57,4 +57,4 @@ jobs: - name: Stop Supabase local stack if: always() - run: supabase stop + run: if command -v supabase &> /dev/null; then supabase stop; fi diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b3b0f617..59db9d0b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -69,7 +69,7 @@ jobs: cache-dependency-path: backend/package-lock.json - name: Install dependencies - run: npm ci + run: npm install - name: Run ESLint run: npx eslint src --ext .ts --max-warnings 0 diff --git a/.github/workflows/migration-drift-check.yml b/.github/workflows/migration-drift-check.yml index e8aafce8..d064de55 100644 --- a/.github/workflows/migration-drift-check.yml +++ b/.github/workflows/migration-drift-check.yml @@ -3,18 +3,11 @@ name: Migration Drift Check on: pull_request: paths: - - 'backend/migrations/**' - - 'supabase/migrations/**' - push: - branches: - - main - paths: - - 'backend/migrations/**' - - 'supabase/migrations/**' + - "supabase/migrations/**" + - "backend/migrations/**" jobs: check-migrations: - name: Detect migration drift runs-on: ubuntu-latest steps: - name: Checkout code @@ -23,13 +16,10 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: '20' - cache: 'npm' - - - name: Install dependencies - run: npm ci + node-version: "20" - name: Run migration drift check + run: node scripts/check-migration-drift.js id: drift-check run: | if node scripts/check-migration-drift.js; then @@ -73,4 +63,4 @@ jobs: issue_number: context.issue.number, body }); - } \ No newline at end of file + } diff --git a/.github/workflows/rls-audit.yml b/.github/workflows/rls-audit.yml index c1e0ccab..d4b65092 100644 --- a/.github/workflows/rls-audit.yml +++ b/.github/workflows/rls-audit.yml @@ -4,23 +4,23 @@ on: push: branches: [main, develop] paths: - - 'supabase/migrations/**' - - 'backend/migrations/**' - - 'scripts/check-rls-compliance.js' - - '.github/workflows/rls-audit.yml' + - "supabase/migrations/**" + - "backend/migrations/**" + - "scripts/check-rls-compliance.js" + - ".github/workflows/rls-audit.yml" pull_request: branches: [main, develop] paths: - - 'supabase/migrations/**' - - 'backend/migrations/**' - - 'scripts/check-rls-compliance.js' - - '.github/workflows/rls-audit.yml' + - "supabase/migrations/**" + - "backend/migrations/**" + - "scripts/check-rls-compliance.js" + - ".github/workflows/rls-audit.yml" jobs: audit-rls-policies: name: Audit RLS Policies runs-on: ubuntu-latest - + steps: - name: Checkout repository uses: actions/checkout@v6 @@ -28,13 +28,13 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: '20' - cache: 'npm' + node-version: "20" + cache: "npm" cache-dependency-path: backend/package-lock.json - name: Install dependencies working-directory: backend - run: npm ci + run: npm install - name: Setup Supabase CLI uses: supabase/setup-cli@v2 @@ -61,7 +61,7 @@ jobs: - name: Stop Supabase local stack if: always() - run: supabase stop + run: if command -v supabase &> /dev/null; then supabase stop; fi # Integration with main CI pipeline validate-rls-on-production: @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-latest if: github.ref == 'refs/heads/main' && github.event_name == 'push' needs: audit-rls-policies - + steps: - name: Checkout repository uses: actions/checkout@v6 @@ -77,13 +77,13 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: '20' - cache: 'npm' + node-version: "20" + cache: "npm" cache-dependency-path: backend/package-lock.json - name: Install dependencies working-directory: backend - run: npm ci + run: npm install - name: Validate RLS on Production Database env: @@ -100,7 +100,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'pull_request' needs: audit-rls-policies - + steps: - name: Comment PR with RLS Status uses: actions/github-script@v9 @@ -111,4 +111,4 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, body: '✅ **RLS Policy Audit Passed**\n\nAll tables in the database have Row Level Security enabled with appropriate policies. Your changes maintain data security compliance.' - }); \ No newline at end of file + }); diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2a15488b..02064455 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -85,7 +85,7 @@ jobs: cache-dependency-path: backend/package-lock.json - name: Install dependencies - run: npm ci + run: npm install - name: Run tests with coverage run: npm test -- --coverage --ci diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index dd56d685..2fdd442a 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -8,7 +8,7 @@ on: workflow_dispatch: inputs: force_full_run: - description: 'Force a full typecheck run' + description: "Force a full typecheck run" type: boolean default: false @@ -54,7 +54,7 @@ jobs: fail-fast: false matrix: package: [backend, client, sdk, shared, root] - + if: | needs.changes.outputs.force_full_run == 'true' || (matrix.package == 'backend' && needs.changes.outputs.backend == 'true') || @@ -65,15 +65,15 @@ jobs: steps: - uses: actions/checkout@v6 - + - uses: actions/setup-node@v6 with: node-version: "20" cache: "npm" - + - name: Install dependencies run: npm install - + - name: Run Typecheck run: | if [ "${{ matrix.package }}" = "root" ]; then diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 43ac4232..f3b7547e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,6 +3,22 @@ ## Database Migrations SYNCRO uses the [Supabase CLI](https://supabase.com/docs/guides/cli) to manage database migrations. +Migration files are organized into two directories based on their domain. They are applied in lexicographic order. + +### Ownership Rules + +| Directory | Purpose | Owner | +| ---------------------- | -------------------------------------- | ---------------------- | +| `supabase/migrations/` | Core schema, Auth, RLS policies | Frontend/Supabase team | +| `backend/migrations/` | Backend-specific tables, RPC functions | Backend team | + +### Migration Workflow + +1. **Determine the Domain**: Decide if your change belongs in core/frontend (`supabase/migrations`) or backend-specific (`backend/migrations`). +2. **Check for Drift**: Run `node scripts/check-migration-drift.js` before creating a migration to ensure no duplicates or table conflicts exist. +3. **Create Migration**: Use `supabase migration new `. Move the generated file to `backend/migrations/` if it is a backend-specific change. +4. **Apply and Test**: Apply locally with `supabase db push` or `npm run db:migrate`. +5. **CI Check**: The continuous integration pipeline will automatically run the drift check on your PR to prevent conflicting schema changes. All migration files live in `supabase/migrations/` and are applied in lexicographic order. `supabase/migrations/` is the canonical migration source of truth for this repository. Legacy SQL snapshots under `backend/migrations/` and `backend/scripts/` are kept for reference only. @@ -58,16 +74,17 @@ YYYYMMDDHHMMSS_short_description.sql ``` Examples: + - `20240115000000_create_push_subscriptions.sql` - `20240117000000_add_2fa_tables.sql` ### Applying migrations -| Environment | Command | -|-------------|---------| -| Local | `npm run db:migrate` | +| Environment | Command | +| ----------- | ---------------------------------------------------------------- | +| Local | `npm run db:migrate` | | Production | `npm run db:migrate:prod` (requires `PRODUCTION_DB_URL` env var) | -| Reset local | `npm run db:reset` | +| Reset local | `npm run db:reset` | ### Rollback strategy @@ -104,92 +121,133 @@ Use the same seed file for local development and E2E bootstrap runs. **Never add real emails, payment data, or any PII to seed.sql.** Thank you for your interest in contributing! This guide will help you set up the project, follow conventions, and submit high-quality contributions. + --- + ## Development Setup + - Node.js >= 20 - npm (bundled with Node.js — do not use yarn or pnpm) - Supabase CLI (for database) - (Optional) Stellar CLI for contract interactions + --- + ### Clone and Install + git clone https://github.com//SYNCRO.git cd SYNCRO + ### Backend Setup + cd backend -cp .env.example .env # Fill in required values +cp .env.example .env # Fill in required values npm install npm run dev + ### Client Setup + cd client -cp .env.example .env.local # Fill in required values +cp .env.example .env.local # Fill in required values npm install npm run dev + ### Database Setup + supabase db push + ## Environment Variables + Environment variables are defined in `.env.example`. Key variables include: + - `SUPABASE_URL` – Supabase project URL - `SUPABASE_KEY` – API key - `JWT_SECRET` – Secret for authentication - `REDIS_URL` – Redis connection (if used) - `EMAIL_SERVICE` – SMTP configuration -> Ensure all required variables are set before running the app. + > Ensure all required variables are set before running the app. + ## Branch Naming Convention + Use the following format: feat/add-feature-name fix/bug-description chore/update-dependencies docs/update-readme test/add-unit-tests + ## Branch Naming Convention + Use the following format: feat/add-feature-name fix/bug-description chore/update-dependencies docs/update-readme test/add-unit-tests + ## Pull Request Guidelines + - Reference the issue: -Closes # + Closes # - Ensure all tests pass - Include a clear description of changes - Add a test plan (how reviewers can verify) - Keep PRs focused and small + ## Code Review Standards + ### TypeScript + - No `any` types - Avoid unsafe non-null assertions + ### Security + - No hardcoded secrets - Validate all inputs (use Zod where applicable) + ### Testing + Required for: + - New endpoints - Bug fixes - Business logic + ## Before Submitting - - Code builds successfully (npm run build) - - Tests pass (npm test) - - Environment variables configured - - No lint or type errors - - PR description completed + +- Code builds successfully (npm run build) +- Tests pass (npm test) +- Environment variables configured +- No lint or type errors +- PR description completed + ## Questions or Issues? + If you encounter any issues with the branch protection or have questions about the contribution process: + 1. Check existing issues on GitHub 2. Open a new issue with details about your problem 3. Ask for help in discussions or pull request comments + ## Code of Conduct + - Be respectful and professional in all interactions - Provide constructive feedback in reviews - Help newer contributors learn and improve - Report any code of conduct violations to the maintainers + ## Additional Resources + +- [PR Submission Guide](./PR_SUBMISSION_GUIDE.md) - [PR Submission Guide](./docs/archive/PR_SUBMISSION_GUIDE.md) - [Backend README](./backend/README.md) - [Client README](./client/README.md) - [GitHub Docs on Branch Protection](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches) + --- + Thank you for helping make Synchro better! 🚀 ## Issue Delivery Notes diff --git a/backend/migrations/add_pause_columns.sql b/backend/migrations/add_pause_columns.sql deleted file mode 100644 index c79f6ad9..00000000 --- a/backend/migrations/add_pause_columns.sql +++ /dev/null @@ -1,29 +0,0 @@ --- Ensure pgcrypto is available for UUID generation -CREATE EXTENSION IF NOT EXISTS "pgcrypto"; - --- Users table is handled by Supabase Auth --- Plans table (minimal) -CREATE TABLE IF NOT EXISTS public.plans ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name TEXT NOT NULL, - price NUMERIC(10,2) NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Subscriptions table -CREATE TABLE IF NOT EXISTS public.subscriptions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, - plan_id UUID REFERENCES public.plans(id) ON DELETE SET NULL, - status TEXT NOT NULL DEFAULT 'active', - paused_at TIMESTAMPTZ DEFAULT NULL, - resume_at TIMESTAMPTZ DEFAULT NULL, - pause_reason TEXT DEFAULT NULL, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- Index for fast resume queries -CREATE INDEX IF NOT EXISTS idx_subscriptions_resume_at - ON subscriptions (status, resume_at) - WHERE status = 'paused' AND resume_at IS NOT NULL; \ No newline at end of file diff --git a/backend/migrations/create_audit_logs.sql b/backend/migrations/create_audit_logs.sql deleted file mode 100644 index ebf0569f..00000000 --- a/backend/migrations/create_audit_logs.sql +++ /dev/null @@ -1,36 +0,0 @@ --- Audit logs table for tracking user actions, security events, and system changes -CREATE TABLE IF NOT EXISTS public.audit_logs ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, - action TEXT NOT NULL, - resource_type TEXT NOT NULL, - resource_id TEXT, - metadata JSONB, - ip_address INET, - user_agent TEXT, - created_at TIMESTAMPTZ DEFAULT NOW() -); - --- Indexes for optimal query performance -CREATE INDEX IF NOT EXISTS idx_audit_logs_user_created ON public.audit_logs(user_id, created_at DESC); -CREATE INDEX IF NOT EXISTS idx_audit_logs_resource ON public.audit_logs(resource_type, resource_id); -CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON public.audit_logs(action, created_at DESC); -CREATE INDEX IF NOT EXISTS idx_audit_logs_created ON public.audit_logs(created_at DESC); - --- Enable RLS (Row Level Security) for audit logs -ALTER TABLE public.audit_logs ENABLE ROW LEVEL SECURITY; - --- Audit logs can only be read by users who own them or by admins -CREATE POLICY audit_logs_select_own ON public.audit_logs - FOR SELECT - USING (auth.uid() = user_id OR auth.jwt() ->> 'is_admin' = 'true'); - --- Only the backend (with service role) can insert audit logs -CREATE POLICY audit_logs_insert_backend ON public.audit_logs - FOR INSERT - WITH CHECK (true); - --- Audit logs are immutable (no updates or deletes except by admin) -CREATE POLICY audit_logs_delete_admin ON public.audit_logs - FOR DELETE - USING (auth.jwt() ->> 'is_admin' = 'true'); diff --git a/backend/migrations/create_renewal_tables.sql b/backend/migrations/create_renewal_tables.sql deleted file mode 100644 index 551dde8d..00000000 --- a/backend/migrations/create_renewal_tables.sql +++ /dev/null @@ -1,30 +0,0 @@ --- Create renewal_logs table for tracking renewal execution -CREATE TABLE IF NOT EXISTS renewal_logs ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - subscription_id UUID NOT NULL REFERENCES subscriptions(id) ON DELETE CASCADE, - user_id UUID NOT NULL, - status TEXT NOT NULL CHECK (status IN ('success', 'failed')), - transaction_hash TEXT, - failure_reason TEXT, - error_message TEXT, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - --- Create renewal_approvals table for tracking approvals -CREATE TABLE IF NOT EXISTS renewal_approvals ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - subscription_id UUID NOT NULL REFERENCES subscriptions(id) ON DELETE CASCADE, - approval_id TEXT NOT NULL, - max_spend NUMERIC, - expires_at TIMESTAMPTZ, - used BOOLEAN NOT NULL DEFAULT FALSE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - UNIQUE(subscription_id, approval_id) -); - --- Create indexes -CREATE INDEX idx_renewal_logs_subscription ON renewal_logs(subscription_id); -CREATE INDEX idx_renewal_logs_user ON renewal_logs(user_id); -CREATE INDEX idx_renewal_logs_status ON renewal_logs(status); -CREATE INDEX idx_renewal_approvals_subscription ON renewal_approvals(subscription_id); -CREATE INDEX idx_renewal_approvals_used ON renewal_approvals(used); diff --git a/backend/migrations/create_team_invitations.sql b/backend/migrations/create_team_invitations.sql deleted file mode 100644 index 41cf8998..00000000 --- a/backend/migrations/create_team_invitations.sql +++ /dev/null @@ -1,17 +0,0 @@ --- Team invitations table for pending member invites -create table if not exists public.team_invitations ( - id uuid primary key default gen_random_uuid(), - team_id uuid not null references public.teams(id) on delete cascade, - email text not null, - role text not null default 'member' check (role in ('admin', 'member', 'viewer')), - token uuid not null unique default gen_random_uuid(), - invited_by uuid not null references auth.users(id) on delete cascade, - expires_at timestamp with time zone not null default (now() + interval '7 days'), - accepted_at timestamp with time zone, - created_at timestamp with time zone default now() -); - --- Indexes -create index if not exists team_invitations_token_idx on public.team_invitations(token); -create index if not exists team_invitations_team_id_idx on public.team_invitations(team_id); -create index if not exists team_invitations_email_idx on public.team_invitations(email); diff --git a/backend/src/dependency-vulnerability-scanning/ci.yml b/backend/src/dependency-vulnerability-scanning/ci.yml index 230fc7a0..511be734 100644 --- a/backend/src/dependency-vulnerability-scanning/ci.yml +++ b/backend/src/dependency-vulnerability-scanning/ci.yml @@ -22,7 +22,7 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: '20' + node-version: "20" - name: Validate client environment variables env: @@ -66,13 +66,13 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: '20' - cache: 'npm' + node-version: "20" + cache: "npm" cache-dependency-path: client/package-lock.json - name: Install client dependencies working-directory: client - run: npm ci --ignore-scripts + run: npm install --ignore-scripts - name: Security audit – client working-directory: client @@ -93,8 +93,8 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: '20' - cache: 'npm' + node-version: "20" + cache: "npm" cache-dependency-path: backend/package-lock.json - name: Install backend dependencies @@ -121,13 +121,13 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: '20' - cache: 'npm' + node-version: "20" + cache: "npm" cache-dependency-path: client/package-lock.json - name: Install client dependencies working-directory: client - run: npm ci + run: npm install - name: Build client working-directory: client @@ -156,8 +156,8 @@ jobs: - name: Set up Node.js uses: actions/setup-node@v4 with: - node-version: '20' - cache: 'npm' + node-version: "20" + cache: "npm" cache-dependency-path: backend/package-lock.json - name: Install backend dependencies diff --git a/backend/src/dependency-vulnerability-scanning/security.yml b/backend/src/dependency-vulnerability-scanning/security.yml index 80e06a1b..0aae3446 100644 --- a/backend/src/dependency-vulnerability-scanning/security.yml +++ b/backend/src/dependency-vulnerability-scanning/security.yml @@ -44,7 +44,7 @@ jobs: - name: Install client dependencies (ci — no scripts) working-directory: client - run: npm ci --ignore-scripts + run: npm install --ignore-scripts # --audit-level=high → exit 1 on high or critical CVEs # The JSON output is saved so it can be inspected in the log even after diff --git a/package.json b/package.json index c8f02fa6..350c2140 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,7 @@ { + "scripts": { + "check:migrations": "node scripts/check-migration-drift.js", + "cleanup:migrations": "node scripts/cleanup-duplicate-migrations.js" "name": "syncro-monorepo", "version": "1.0.0", "private": true, diff --git a/scripts/cleanup-duplicate-migrations.js b/scripts/cleanup-duplicate-migrations.js new file mode 100644 index 00000000..bf040f95 --- /dev/null +++ b/scripts/cleanup-duplicate-migrations.js @@ -0,0 +1,25 @@ +const fs = require('fs'); +const path = require('path'); + +const backendMigrationsDir = path.join(__dirname, '..', 'backend', 'migrations'); + +const duplicates = [ + 'create_audit_logs.sql', + 'create_renewal_tables.sql', + 'create_team_invitations.sql', + 'add_pause_columns.sql' +]; + +console.log('🧹 Cleaning up duplicate backend migrations...'); +let successCount = 0; + +duplicates.forEach(file => { + const filePath = path.join(backendMigrationsDir, file); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + console.log(`✅ Deleted: backend/migrations/${file}`); + successCount++; + } +}); + +console.log(`\n✨ Cleanup complete! Removed ${successCount} duplicate files.`); \ No newline at end of file