diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..77ed143 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,17 @@ +.git +.github +.husky +.env +.env.* +!.env.example +node_modules +backend/node_modules +dist +coverage +playwright-report +test-results +docs +tests +supabase +*.log +.DS_Store diff --git a/.env.example b/.env.example index 9afff30..9af6913 100644 --- a/.env.example +++ b/.env.example @@ -12,7 +12,9 @@ ADMIN_POSTGREST_URL=http://vezvision-admin-postgrest:3000 ADMIN_POSTGREST_API_KEY= # Public API server settings belong in Coolify and must never use the VITE_ prefix. -# DATABASE_URL=postgresql://restricted_api_user:password@postgres:5432/vezvision +# API and PostgREST must use separate least-privilege database users. +# API_DATABASE_URL=postgresql://vezvision_api:password@postgres:5432/vezvision +# POSTGREST_DATABASE_URL=postgresql://vezvision_postgrest:password@postgres:5432/vezvision # ALLOWED_ORIGINS=https://vezvision.com,https://www.vezvision.com # TURNSTILE_SECRET_KEY= # RESEND_API_KEY= diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8bea4c5..bc5181d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,4 +1,6 @@ * @vezcode -/src/components/seo/ @vezcode -/supabase/ @vezcode -/scripts/ @vezcode + +/.github/ @vezcode +/backend/ @vezcode +/deploy/ @vezcode +/supabase/migrations/ @vezcode diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 0df5240..12d147b 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,25 +1,18 @@ -## Description +## What changed -Brief description of what this PR does and why. + -## Type of change +## Verification -- [ ] Bug fix (non-breaking change which fixes an issue) -- [ ] New feature (non-breaking change which adds functionality) -- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] Performance improvement -- [ ] Refactor / code quality -- [ ] Documentation update +- [ ] `npm run lint` +- [ ] `npm run knip` +- [ ] `npm run check` +- [ ] `npm run build` +- [ ] `npm run size:check` +- [ ] `npm audit --audit-level=high` +- [ ] No secret or personal data was added +- [ ] Mobile and desktop behaviour was checked when UI changed -## Checklist +## Deployment and rollback -- [ ] Code follows the project's style guidelines (ESLint + Prettier) -- [ ] Self-review completed -- [ ] Comments added for complex logic -- [ ] No new TypeScript errors (`npm run check`) -- [ ] No new ESLint warnings (`npm run lint`) -- [ ] No new Knip dead-code findings -- [ ] npm audit passes with no moderate+ vulnerabilities -- [ ] No secrets, API keys, or credentials committed -- [ ] Accessibility implications considered (ARIA, keyboard nav, contrast) -- [ ] Mobile and desktop layouts verified + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 84dc636..be541fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,8 +34,8 @@ jobs: - name: Lint run: npm run lint - - name: Knip (dead code detection — advisory) - run: npm run knip -- --no-exit-code + - name: Knip (dead code detection) + run: npm run knip - name: Typecheck, parity, and unit tests run: npm run check @@ -43,6 +43,9 @@ jobs: - name: Build run: npm run build + - name: Bundle size budget + run: npm run size:check + - name: Upload production dist uses: actions/upload-artifact@v7 with: @@ -51,34 +54,10 @@ jobs: retention-days: 1 - name: Audit production dependencies - run: | - npm audit --omit=dev --json 2>/dev/null | node -e ' - const data = JSON.parse(require("fs").readFileSync(0, "utf8")); - const SKIP = new Set(["GHSA-qwww-vcr4-c8h2"]); - const vulns = data.vulnerabilities || {}; - const filtered = new Set(); - let changed = true; - while (changed) { - changed = false; - for (const [name, v] of Object.entries(vulns)) { - if (filtered.has(name)) continue; - const vias = Array.isArray(v.via) ? v.via : [v.via]; - const allSkipped = vias.length > 0 && vias.every(via => { - if (typeof via === "string") return filtered.has(via); - if (via.source && SKIP.has(via.source)) return true; - if (via.url && via.url.includes("GHSA-qwww-vcr4-c8h2")) return true; - return false; - }); - if (allSkipped) { filtered.add(name); changed = true; } - } - } - const real = Object.entries(vulns).filter(([n]) => !filtered.has(n)); - if (real.length > 0) { - for (const [name, v] of real) console.error(name + ": " + v.severity); - process.exit(1); - } - console.log("No production vulnerabilities"); - ' + run: npm audit --omit=dev --audit-level=high + + - name: Audit all dependencies + run: npm audit --audit-level=high e2e: runs-on: ubuntu-latest @@ -146,6 +125,7 @@ jobs: run: npm run build env: VITE_API_URL: https://api.example.test + SKIP_PRERENDER: "1" - name: Run Lighthouse CI run: npx --yes @lhci/cli@0.15.1 autorun --config=./.github/lighthouserc.json diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index b8c7ae3..d8cadfe 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -19,4 +19,3 @@ jobs: uses: actions/dependency-review-action@v4 with: fail-on-severity: moderate - continue-on-error: true diff --git a/.github/workflows/deploy-coolify.yml b/.github/workflows/deploy-coolify.yml index f5701e7..bf7465c 100644 --- a/.github/workflows/deploy-coolify.yml +++ b/.github/workflows/deploy-coolify.yml @@ -35,6 +35,13 @@ jobs: steps: - name: Checkout deployment scripts uses: actions/checkout@v6 + with: + ref: ${{ github.event.workflow_run.head_sha || github.sha }} + - name: Verify tested develop commit is still the branch tip + if: github.event_name == 'workflow_run' + env: + EXPECTED_SHA: ${{ github.event.workflow_run.head_sha }} + run: test "$(git ls-remote origin refs/heads/develop | cut -f1)" = "$EXPECTED_SHA" - name: Deploy develop API env: WEBHOOK_URL: ${{ secrets.COOLIFY_DEVELOP_API_WEBHOOK_URL }} @@ -57,6 +64,13 @@ jobs: steps: - name: Checkout deployment scripts uses: actions/checkout@v6 + with: + ref: ${{ github.event.workflow_run.head_sha || github.sha }} + - name: Verify tested production commit is still the branch tip + if: github.event_name == 'workflow_run' + env: + EXPECTED_SHA: ${{ github.event.workflow_run.head_sha }} + run: test "$(git ls-remote origin refs/heads/main | cut -f1)" = "$EXPECTED_SHA" - name: Deploy production API env: WEBHOOK_URL: ${{ secrets.COOLIFY_PRODUCTION_API_WEBHOOK_URL }} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1285ab0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,5 @@ +Copyright (c) 2026 POLIFORM SPÓŁKA Z OGRANICZONĄ ODPOWIEDZIALNOŚCIĄ. + +All rights reserved. This repository and its contents are proprietary. No permission +is granted to copy, modify, distribute, sublicense, deploy or create derivative works +without prior written authorization from the copyright holder. diff --git a/README.md b/README.md index fe3c1e2..a630bc9 100644 --- a/README.md +++ b/README.md @@ -107,13 +107,14 @@ It runs install, typecheck, lint, unit tests, production build, npm audit, and C These endpoints are served by the self-hosted Node API in `backend/`; public reads are served by PostgREST through a GET-only gateway. The browser only needs `VITE_API_URL`. -### Optional Turnstile (contact + newsletter) +### Turnstile (contact + newsletter) | Env (client) | `VITE_TURNSTILE_SITE_KEY` | | Env (server) | `TURNSTILE_SECRET_KEY` | -When the site key is unset, widgets are hidden. When set, configure the secret in -the self-hosted API environment. +Turnstile may be omitted for local development. Production starts fail closed unless +the site key is built into the frontend and both `TURNSTILE_SECRET_KEY` and +`TURNSTILE_EXPECTED_HOSTNAMES` are configured in the API environment. Before every production build, `npm run build` verifies CSP sources (`verify:security`) and `dist/` artifacts (`verify-production-build.mjs`). @@ -122,6 +123,9 @@ Before every production build, `npm run build` verifies CSP sources (`verify:sec - Frontend and API are built by Coolify from `frontend.Dockerfile` and `coolify-compose.yml`. - Production runs on Hetzner. The old Hostido deploy job is intentionally absent. - Coolify environment values are the source of truth for production build and server secrets. +- Use separate `API_DATABASE_URL` and `POSTGREST_DATABASE_URL` values backed by + dedicated least-privilege roles; never give the public PostgREST container the API + write role or a database-owner credential. - Use **npm** (`npm ci`) for installs; do not commit alternate lockfiles. - Keep Browserslist data current with `npm update caniuse-lite browserslist` when build warnings appear. - Do not commit local files such as `.env`, `.DS_Store`, Playwright traces, or reports. diff --git a/SECURITY.md b/SECURITY.md index 22e3d9e..92e01c7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ If you discover a security vulnerability in the VEZvision website, please report **DO NOT** open a public issue. -**Contact:** contact@vezvision.com +**Contact:** security@vezvision.com Please include: diff --git a/backend/Dockerfile b/backend/Dockerfile index 2570aa0..26b71a9 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-alpine3.23@sha256:8516dce0483394d5708d4b2ee6cacb79fb1d617ea4e2787c2120bcca92ce372e +FROM node:22-alpine3.23@sha256:46825fbbd4e996a78b7a2cdc08d75e38a5a505bdab95dcda55605359bf124bc6 WORKDIR /app COPY backend/package.json backend/package-lock.json ./ RUN npm ci --omit=dev --ignore-scripts \ @@ -9,6 +9,7 @@ COPY backend/email-templates.mjs ./email-templates.mjs COPY backend/resend-email.mjs ./resend-email.mjs COPY public/email-logo.png ./email-logo.png ENV EMAIL_LOGO_PATH=/app/email-logo.png +ENV NODE_ENV=production USER node EXPOSE 3000 CMD ["node", "server.mjs"] diff --git a/backend/server.mjs b/backend/server.mjs index b5ef87b..d57a157 100644 --- a/backend/server.mjs +++ b/backend/server.mjs @@ -5,6 +5,7 @@ import { contactAutoReplyEmail, contactNotificationEmail, newsletterConfirmation import { sendEmail as sendResendEmail } from './resend-email.mjs' const databaseUrl = process.env.DATABASE_URL +const isProduction = process.env.NODE_ENV === 'production' const allowedOrigins = String(process.env.ALLOWED_ORIGINS || process.env.ALLOWED_ORIGIN || '') .split(',') .map(origin => origin.trim()) @@ -37,7 +38,13 @@ const adminPostgrestApiKey = process.env.ADMIN_POSTGREST_API_KEY?.trim() const publicSiteUrl = (process.env.PUBLIC_SITE_URL?.trim() || allowedOrigins[0] || '').replace(/\/$/, '') const publicEmailSiteUrl = (process.env.PUBLIC_EMAIL_SITE_URL?.trim() || 'https://vezvision.com').replace(/\/$/, '') if (!databaseUrl || allowedOrigins.length === 0) throw new Error('DATABASE_URL and ALLOWED_ORIGIN/ALLOWED_ORIGINS are required') -if (!turnstileSecret) console.warn('TURNSTILE_SECRET_KEY is not set; contact and newsletter captcha verification is disabled') +if (isProduction && (!turnstileSecret || turnstileExpectedHostnames.length === 0)) { + throw new Error('TURNSTILE_SECRET_KEY and TURNSTILE_EXPECTED_HOSTNAMES are required in production') +} +if (isProduction && (!resendApiKey || !contactNotificationFromEmail || !contactReplyFromEmail || !newsletterFromEmail)) { + throw new Error('Resend API key and sender addresses are required in production') +} +if (!turnstileSecret) console.warn('TURNSTILE_SECRET_KEY is not set; allowed only outside production') if (turnstileTestMode) console.warn('TURNSTILE_TEST_MODE is enabled; use only in development') if (!resendApiKey || !contactNotificationFromEmail || !contactReplyFromEmail || !newsletterFromEmail) console.warn('Resend API key or sender addresses are not fully configured; some emails are disabled') if ([adminApiTokenSha256, adminPostgrestUrl, adminPostgrestApiKey].some(Boolean) @@ -91,7 +98,7 @@ const cors = (req, res) => { } const hasAllowedBrowserOrigin = req => { const origin = String(req.headers.origin || '').trim() - return !origin || allowedOrigins.includes(origin) + return origin.length > 0 && allowedOrigins.includes(origin) } const body = async req => { let raw = '' @@ -198,9 +205,9 @@ async function cleanupRateLimits() { const { rows: [retention] } = await pool.query( `SELECT * FROM public.cleanup_expired_private_data()`, ) - if (Number(retention?.expired_messages) > 0 || Number(retention?.expired_unconfirmed_subscribers) > 0) { + if (Number(retention?.expired_messages) > 0 || Number(retention?.expired_unconfirmed_subscribers) > 0 || Number(retention?.anonymized_unsubscribed) > 0) { console.info( - `Applied data retention: ${retention.expired_messages} messages and ${retention.expired_unconfirmed_subscribers} unconfirmed subscribers removed`, + `Applied data retention: ${retention.expired_messages} messages removed, ${retention.expired_unconfirmed_subscribers} unconfirmed subscribers removed and ${retention.anonymized_unsubscribed} withdrawn subscriptions anonymized`, ) } } catch (error) { diff --git a/coolify-compose.yml b/coolify-compose.yml index 8caac14..0b4c1bc 100644 --- a/coolify-compose.yml +++ b/coolify-compose.yml @@ -6,7 +6,10 @@ services: dockerfile: backend/Dockerfile restart: unless-stopped environment: - DATABASE_URL: ${DATABASE_URL} + # API and PostgREST must use different least-privilege database roles. + # DATABASE_URL remains a temporary fallback for existing installations. + DATABASE_URL: ${API_DATABASE_URL:-${DATABASE_URL}} + NODE_ENV: production ALLOWED_ORIGIN: ${ALLOWED_ORIGIN} ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-} TURNSTILE_SECRET_KEY: ${TURNSTILE_SECRET_KEY:-} @@ -28,17 +31,22 @@ services: networks: vezvision_internal: aliases: [vezvision-api] - coolify: {} - security_opt: [no-new-privileges:true] + security_opt: ["no-new-privileges:true"] cap_drop: [ALL] read_only: true tmpfs: [/tmp] + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/healthz >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 10s postgrest: - image: postgrest/postgrest:v14.1 + image: postgrest/postgrest:v14.1@sha256:e9490aa503a5fb07d8e8c80da46e5c0c193894e583b59ed9077e74c4101ffae2 restart: unless-stopped environment: - PGRST_DB_URI: ${DATABASE_URL} + PGRST_DB_URI: ${POSTGREST_DATABASE_URL:-${DATABASE_URL}} PGRST_DB_SCHEMA: public PGRST_DB_ANON_ROLE: anon PGRST_SERVER_HOST: 0.0.0.0 @@ -46,23 +54,32 @@ services: networks: vezvision_internal: aliases: [vezvision-postgrest] - coolify: {} - security_opt: [no-new-privileges:true] + security_opt: ["no-new-privileges:true"] cap_drop: [ALL] read_only: true tmpfs: [/tmp] + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3001/ >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 10s gateway: build: context: . dockerfile: backend/nginx.Dockerfile restart: unless-stopped - depends_on: [api, postgrest] + depends_on: + api: + condition: service_healthy + postgrest: + condition: service_healthy expose: ["8080"] labels: - traefik.docker.network=coolify networks: [vezvision_internal, coolify] - security_opt: [no-new-privileges:true] + security_opt: ["no-new-privileges:true"] cap_drop: [ALL] read_only: true tmpfs: [/var/cache/nginx, /var/run, /tmp] diff --git a/deploy/hetzner/.env.example b/deploy/hetzner/.env.example index 8d81985..45a2138 100644 --- a/deploy/hetzner/.env.example +++ b/deploy/hetzner/.env.example @@ -1,5 +1,8 @@ # Set in Coolify; do not commit real values. -DATABASE_URL=postgresql://vezvision_api:REPLACE_ME@DATABASE_HOST:5432/vezvision +API_DATABASE_URL=postgresql://vezvision_api:REPLACE_ME@DATABASE_HOST:5432/vezvision +POSTGREST_DATABASE_URL=postgresql://vezvision_postgrest:REPLACE_ME@DATABASE_HOST:5432/vezvision +# Temporary compatibility fallback. Remove after both dedicated URLs are configured. +DATABASE_URL= ALLOWED_ORIGIN=https://REPLACE_WITH_VEZVISION_DOMAIN # Optional comma-separated override for multiple website origins. ALLOWED_ORIGINS= diff --git a/deploy/hetzner/README.md b/deploy/hetzner/README.md index 68ef5a4..848b690 100644 --- a/deploy/hetzner/README.md +++ b/deploy/hetzner/README.md @@ -10,13 +10,18 @@ API hostname. 1. Apply `schema.sql` to the dedicated `vezvision` database as its owner, then run `seed-from-repository.sh` to restore the public CMS defaults versioned in this repo. -2. Generate a long, unique password and run `provision-postgrest-role.sql` with it. -3. Set `DATABASE_URL` in Coolify for the restricted `vezvision_api` role, not the - `postgres` owner. + Apply files in `migrations/` in timestamp order and record each applied filename + in the deployment log; every production run starts with a verified backup. +2. Generate two different long, unique passwords and run + `provision-postgrest-role.sql` with `api_password` and `postgrest_password`. +3. Set `API_DATABASE_URL` in Coolify for the restricted `vezvision_api` role and + `POSTGREST_DATABASE_URL` for the dedicated public-read authenticator. These must + be different credentials; neither may use the `postgres` owner. 4. Set `ALLOWED_ORIGIN` or comma-separated `ALLOWED_ORIGINS` to the website origin(s) allowed to call `/functions/v1/*`. -5. If Cloudflare Turnstile is enabled in the frontend (`VITE_TURNSTILE_SITE_KEY`), - set the matching server-side `TURNSTILE_SECRET_KEY` in the API service. +5. In production, set `VITE_TURNSTILE_SITE_KEY`, the matching server-side + `TURNSTILE_SECRET_KEY`, and `TURNSTILE_EXPECTED_HOSTNAMES`. The API refuses to + start with missing or test credentials. 6. Point `VITE_API_URL` to the public gateway and `VITE_PUBLIC_ASSETS_URL` to the public MinIO/CDN endpoint. Both values are build-time public configuration. diff --git a/deploy/hetzner/docker-compose.yml b/deploy/hetzner/docker-compose.yml index 529dd8b..5008cd9 100644 --- a/deploy/hetzner/docker-compose.yml +++ b/deploy/hetzner/docker-compose.yml @@ -5,7 +5,8 @@ services: dockerfile: backend/Dockerfile restart: unless-stopped environment: - DATABASE_URL: ${DATABASE_URL} + DATABASE_URL: ${API_DATABASE_URL:-${DATABASE_URL}} + NODE_ENV: production ALLOWED_ORIGIN: ${ALLOWED_ORIGIN} ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-} TURNSTILE_SECRET_KEY: ${TURNSTILE_SECRET_KEY:-} @@ -13,37 +14,53 @@ services: ADMIN_POSTGREST_URL: ${ADMIN_POSTGREST_URL:-} ADMIN_POSTGREST_API_KEY: ${ADMIN_POSTGREST_API_KEY:-} PORT: 3000 - networks: [vezvision_internal, coolify] - security_opt: [no-new-privileges:true] + networks: [vezvision_internal] + security_opt: ["no-new-privileges:true"] cap_drop: [ALL] read_only: true tmpfs: [/tmp] + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3000/healthz >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 10s postgrest: - image: postgrest/postgrest:v14.1 + image: postgrest/postgrest:v14.1@sha256:e9490aa503a5fb07d8e8c80da46e5c0c193894e583b59ed9077e74c4101ffae2 restart: unless-stopped environment: - PGRST_DB_URI: ${DATABASE_URL} + PGRST_DB_URI: ${POSTGREST_DATABASE_URL:-${DATABASE_URL}} PGRST_DB_SCHEMA: public PGRST_DB_ANON_ROLE: anon PGRST_SERVER_HOST: 0.0.0.0 PGRST_SERVER_PORT: 3001 - networks: [vezvision_internal, coolify] - security_opt: [no-new-privileges:true] + networks: [vezvision_internal] + security_opt: ["no-new-privileges:true"] cap_drop: [ALL] read_only: true tmpfs: [/tmp] + healthcheck: + test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:3001/ >/dev/null || exit 1"] + interval: 10s + timeout: 3s + retries: 5 + start_period: 10s gateway: build: context: ../.. dockerfile: backend/nginx.Dockerfile restart: unless-stopped - depends_on: [api, postgrest] + depends_on: + api: + condition: service_healthy + postgrest: + condition: service_healthy volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro networks: [vezvision_internal, coolify] - security_opt: [no-new-privileges:true] + security_opt: ["no-new-privileges:true"] cap_drop: [ALL] read_only: true tmpfs: [/var/cache/nginx, /var/run, /tmp] diff --git a/deploy/hetzner/migrations/20260809210000_anonymize_withdrawn_newsletter.sql b/deploy/hetzner/migrations/20260809210000_anonymize_withdrawn_newsletter.sql new file mode 100644 index 0000000..344a2a9 --- /dev/null +++ b/deploy/hetzner/migrations/20260809210000_anonymize_withdrawn_newsletter.sql @@ -0,0 +1,83 @@ +BEGIN; + +ALTER TABLE public.vv_newsletter_subscribers + ADD COLUMN IF NOT EXISTS email_hash text; + +DROP FUNCTION IF EXISTS public.cleanup_expired_private_data(); +CREATE FUNCTION public.cleanup_expired_private_data() +RETURNS TABLE( + expired_messages bigint, + expired_unconfirmed_subscribers bigint, + anonymized_unsubscribed bigint +) +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public, pg_temp +AS $$ +DECLARE + message_count bigint; + subscriber_count bigint; + anonymized_count bigint; +BEGIN + DELETE FROM public.messages + WHERE created_at < now() - interval '2 years'; + GET DIAGNOSTICS message_count = ROW_COUNT; + + DELETE FROM public.vv_newsletter_subscribers + WHERE is_active = false + AND confirmed_at IS NULL + AND confirmation_requested_at < now() - interval '30 days'; + GET DIAGNOSTICS subscriber_count = ROW_COUNT; + + UPDATE public.vv_newsletter_subscribers + SET email_hash = encode(digest(lower(email), 'sha256'), 'hex'), + email = 'erased+' || id::text || '@invalid.local', + token = encode(gen_random_bytes(32), 'hex'), + source = 'erased', + tags = '{}', + first_name = NULL, + last_name = NULL, + consent_ip = NULL, + consent_user_agent = NULL, + updated_at = now() + WHERE is_active = false + AND confirmed_at IS NOT NULL + AND unsubscribed_at < now() - interval '30 days' + AND email_hash IS NULL; + GET DIAGNOSTICS anonymized_count = ROW_COUNT; + + RETURN QUERY SELECT message_count, subscriber_count, anonymized_count; +END $$; + +REVOKE ALL ON FUNCTION public.cleanup_expired_private_data() FROM PUBLIC; + +DO $$ +DECLARE api_role text; +BEGIN + FOREACH api_role IN ARRAY ARRAY['vezvision_api', 'vezvision_lab_api'] LOOP + IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = api_role) THEN + EXECUTE format( + 'GRANT EXECUTE ON FUNCTION public.cleanup_expired_private_data() TO %I', + api_role + ); + END IF; + END LOOP; +END $$; + +UPDATE public.vv_legal_documents +SET content_pl = replace( + content_pl, + '* **Dane subskrybenta newslettera** - przechowywane do momentu cofnięcia zgody (wypisania się z newslettera); niepotwierdzone zapisy są usuwane po 30 dniach.', + '* **Dane subskrybenta newslettera** - przechowywane do momentu cofnięcia zgody (wypisania się z newslettera); niepotwierdzone zapisy są usuwane po 30 dniach, a 30 dni po wypisaniu adres e-mail i dane techniczne są anonimizowane. Zachowujemy wyłącznie skrót adresu oraz daty zgody i jej cofnięcia jako minimalny dowód zgodności.' + ), + content_en = replace( + content_en, + '* **Newsletter subscriber data** - stored until consent is withdrawn (unsubscribing); unconfirmed signups are deleted after 30 days.', + '* **Newsletter subscriber data** - stored until consent is withdrawn (unsubscribing); unconfirmed signups are deleted after 30 days, and the e-mail address and technical data are anonymized 30 days after unsubscribing. Only a hash of the address and the consent/withdrawal dates remain as minimum compliance evidence.' + ), + version = '2026.08.09', + last_updated = DATE '2026-08-09', + updated_at = now() +WHERE document_key = 'privacy_policy'; + +COMMIT; diff --git a/deploy/hetzner/nginx.conf b/deploy/hetzner/nginx.conf index f087787..86c071f 100644 --- a/deploy/hetzner/nginx.conf +++ b/deploy/hetzner/nginx.conf @@ -24,6 +24,7 @@ server { add_header Cross-Origin-Resource-Policy "same-site" always; location /rest/v1/ { + if ($request_method !~ ^(GET|HEAD|OPTIONS)$) { return 405; } proxy_hide_header Access-Control-Allow-Origin; proxy_hide_header Access-Control-Allow-Methods; proxy_hide_header Access-Control-Allow-Headers; @@ -37,13 +38,16 @@ server { proxy_pass http://vezvision_postgrest/; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Real-IP $remote_addr; } location /functions/v1/ { + client_max_body_size 32k; proxy_pass http://vezvision_api/functions/v1/; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; } location /admin/v1/ { @@ -51,6 +55,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; proxy_set_header Authorization $http_authorization; proxy_request_buffering on; } @@ -60,6 +65,7 @@ server { proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Real-IP $remote_addr; } location / { return 404; } } diff --git a/deploy/hetzner/provision-postgrest-role.sql b/deploy/hetzner/provision-postgrest-role.sql index bc0291c..153be72 100644 --- a/deploy/hetzner/provision-postgrest-role.sql +++ b/deploy/hetzner/provision-postgrest-role.sql @@ -1,20 +1,43 @@ -- Run once as the `postgres` database owner. Do not commit the password. -- Example: --- psql -d vezvision -v api_password='a-long-random-value' -f provision-postgrest-role.sql +-- psql -d vezvision \ +-- -v api_password='a-long-random-value' \ +-- -v postgrest_password='a-different-long-random-value' \ +-- -f provision-postgrest-role.sql \if :{?api_password} \else \quit 3 \endif +\if :{?postgrest_password} +\else + \quit 3 +\endif DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'vezvision_api') THEN CREATE ROLE vezvision_api LOGIN NOINHERIT; END IF; + IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'vezvision_postgrest') THEN + CREATE ROLE vezvision_postgrest LOGIN NOINHERIT; + END IF; END $$; -ALTER ROLE vezvision_api PASSWORD :'api_password'; -GRANT anon TO vezvision_api; +-- Re-assert the security attributes as well as the password. Older deployments +-- created `vezvision_api` as NOLOGIN, so changing only its password would leave +-- the API unable to authenticate after this migration. +ALTER ROLE vezvision_api LOGIN NOINHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION PASSWORD :'api_password'; +ALTER ROLE vezvision_postgrest LOGIN NOINHERIT NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION PASSWORD :'postgrest_password'; +ALTER ROLE anon NOLOGIN NOINHERIT; +REVOKE anon FROM vezvision_api; +GRANT anon TO vezvision_postgrest; + +REVOKE ALL ON SCHEMA public FROM vezvision_postgrest; +GRANT USAGE ON SCHEMA public TO vezvision_postgrest; +REVOKE ALL ON ALL TABLES IN SCHEMA public FROM vezvision_postgrest; +REVOKE ALL ON ALL SEQUENCES IN SCHEMA public FROM vezvision_postgrest; +REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA public FROM vezvision_postgrest; + GRANT USAGE ON SCHEMA public TO vezvision_api; REVOKE SELECT, INSERT, UPDATE, DELETE, TRUNCATE, REFERENCES, TRIGGER ON ALL TABLES IN SCHEMA public FROM vezvision_api; REVOKE USAGE, SELECT, UPDATE ON ALL SEQUENCES IN SCHEMA public FROM vezvision_api; @@ -31,3 +54,31 @@ GRANT INSERT, SELECT (id) ON public.messages TO vezvision_api; GRANT SELECT, INSERT, UPDATE ON public.rate_limit_buckets TO vezvision_api; GRANT SELECT, INSERT, UPDATE ON public.vv_newsletter_subscribers TO vezvision_api; GRANT EXECUTE ON FUNCTION public.vv_blog_increment_views(p_post_slug text, p_client_ip text) TO vezvision_api; +GRANT EXECUTE ON FUNCTION public.cleanup_rate_limit_buckets(interval) TO vezvision_api; +GRANT EXECUTE ON FUNCTION public.cleanup_expired_private_data() TO vezvision_api; + +DROP POLICY IF EXISTS messages_vezvision_api_insert ON public.messages; +DROP POLICY IF EXISTS messages_vezvision_api_select_id ON public.messages; +DROP POLICY IF EXISTS newsletter_vezvision_api_select ON public.vv_newsletter_subscribers; +DROP POLICY IF EXISTS newsletter_vezvision_api_insert ON public.vv_newsletter_subscribers; +DROP POLICY IF EXISTS newsletter_vezvision_api_update ON public.vv_newsletter_subscribers; +DROP POLICY IF EXISTS rate_limit_vezvision_api_select ON public.rate_limit_buckets; +DROP POLICY IF EXISTS rate_limit_vezvision_api_insert ON public.rate_limit_buckets; +DROP POLICY IF EXISTS rate_limit_vezvision_api_update ON public.rate_limit_buckets; + +CREATE POLICY messages_vezvision_api_insert ON public.messages + FOR INSERT TO vezvision_api WITH CHECK (true); +CREATE POLICY messages_vezvision_api_select_id ON public.messages + FOR SELECT TO vezvision_api USING (true); +CREATE POLICY newsletter_vezvision_api_select ON public.vv_newsletter_subscribers + FOR SELECT TO vezvision_api USING (true); +CREATE POLICY newsletter_vezvision_api_insert ON public.vv_newsletter_subscribers + FOR INSERT TO vezvision_api WITH CHECK (true); +CREATE POLICY newsletter_vezvision_api_update ON public.vv_newsletter_subscribers + FOR UPDATE TO vezvision_api USING (true) WITH CHECK (true); +CREATE POLICY rate_limit_vezvision_api_select ON public.rate_limit_buckets + FOR SELECT TO vezvision_api USING (true); +CREATE POLICY rate_limit_vezvision_api_insert ON public.rate_limit_buckets + FOR INSERT TO vezvision_api WITH CHECK (true); +CREATE POLICY rate_limit_vezvision_api_update ON public.rate_limit_buckets + FOR UPDATE TO vezvision_api USING (true) WITH CHECK (true); diff --git a/deploy/hetzner/schema.sql b/deploy/hetzner/schema.sql index 0d2d767..bc635ac 100644 --- a/deploy/hetzner/schema.sql +++ b/deploy/hetzner/schema.sql @@ -133,7 +133,8 @@ CREATE TABLE IF NOT EXISTS public.vv_newsletter_subscribers ( token text NOT NULL UNIQUE, is_active boolean NOT NULL DEFAULT true, language text NOT NULL DEFAULT 'pl', subscribed_at timestamptz NOT NULL DEFAULT now(), unsubscribed_at timestamptz, updated_at timestamptz NOT NULL DEFAULT now(), first_name text, last_name text, created_at timestamptz NOT NULL DEFAULT now(), - confirmation_requested_at timestamptz, confirmed_at timestamptz, consent_ip inet, consent_user_agent text + confirmation_requested_at timestamptz, confirmed_at timestamptz, consent_ip inet, consent_user_agent text, + email_hash text ); -- Keep re-applying this standalone schema safe for installations created from an @@ -153,6 +154,7 @@ ALTER TABLE public.vv_newsletter_subscribers ADD COLUMN IF NOT EXISTS confirmati ALTER TABLE public.vv_newsletter_subscribers ADD COLUMN IF NOT EXISTS confirmed_at timestamptz; ALTER TABLE public.vv_newsletter_subscribers ADD COLUMN IF NOT EXISTS consent_ip inet; ALTER TABLE public.vv_newsletter_subscribers ADD COLUMN IF NOT EXISTS consent_user_agent text; +ALTER TABLE public.vv_newsletter_subscribers ADD COLUMN IF NOT EXISTS email_hash text; CREATE TABLE IF NOT EXISTS public.messages ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), full_name text NOT NULL, email text NOT NULL, phone text, subject text NOT NULL, message text NOT NULL, language text NOT NULL DEFAULT 'pl', client_ip text, @@ -181,7 +183,7 @@ END $$; REVOKE ALL ON FUNCTION public.cleanup_rate_limit_buckets(interval) FROM PUBLIC; CREATE OR REPLACE FUNCTION public.cleanup_expired_private_data() -RETURNS TABLE(expired_messages bigint, expired_unconfirmed_subscribers bigint) +RETURNS TABLE(expired_messages bigint, expired_unconfirmed_subscribers bigint, anonymized_unsubscribed bigint) LANGUAGE plpgsql SECURITY DEFINER SET search_path = public, pg_temp @@ -189,6 +191,7 @@ AS $$ DECLARE message_count bigint; subscriber_count bigint; + anonymized_count bigint; BEGIN DELETE FROM public.messages WHERE created_at < now() - interval '2 years'; @@ -200,7 +203,24 @@ BEGIN AND confirmation_requested_at < now() - interval '30 days'; GET DIAGNOSTICS subscriber_count = ROW_COUNT; - RETURN QUERY SELECT message_count, subscriber_count; + UPDATE public.vv_newsletter_subscribers + SET email_hash = encode(digest(lower(email), 'sha256'), 'hex'), + email = 'erased+' || id::text || '@invalid.local', + token = encode(gen_random_bytes(32), 'hex'), + source = 'erased', + tags = '{}', + first_name = NULL, + last_name = NULL, + consent_ip = NULL, + consent_user_agent = NULL, + updated_at = now() + WHERE is_active = false + AND confirmed_at IS NOT NULL + AND unsubscribed_at < now() - interval '30 days' + AND email_hash IS NULL; + GET DIAGNOSTICS anonymized_count = ROW_COUNT; + + RETURN QUERY SELECT message_count, subscriber_count, anonymized_count; END $$; REVOKE ALL ON FUNCTION public.cleanup_expired_private_data() FROM PUBLIC; diff --git a/deploy/hetzner/seed-from-repository.sh b/deploy/hetzner/seed-from-repository.sh index 0509444..17d6c3e 100755 --- a/deploy/hetzner/seed-from-repository.sh +++ b/deploy/hetzner/seed-from-repository.sh @@ -4,7 +4,7 @@ set -euo pipefail # Loads only the versioned INSERT statements. It deliberately does not re-run # Supabase-specific triggers, policies or grants from the source migrations. root="$(cd "$(dirname "$0")/../.." && pwd)" -database_url="${DATABASE_URL:?Set DATABASE_URL for the dedicated vezvision database}" +database_url="${MIGRATION_DATABASE_URL:?Set MIGRATION_DATABASE_URL for the dedicated vezvision database owner}" for migration in \ 20260331100000_vv_site_settings.sql \ diff --git a/docs/migration-drift-baseline.md b/docs/migration-drift-baseline.md index d86e0ad..3b256a9 100644 --- a/docs/migration-drift-baseline.md +++ b/docs/migration-drift-baseline.md @@ -1,12 +1,13 @@ # Migration Drift & Baseline Strategy -**Date:** 2026-06-19 (updated 2026-07-08) -**Status:** Drift archived: orphaned migrations moved to `_archive-pre-202603/` +**Date:** 2026-06-19 (updated 2026-08-09) +**Status:** Legacy pre-reset migrations removed from the active repository tree ## Current state - **Local repo:** 46 active migrations in `supabase/migrations/` -- **Archived:** 69 pre-reset migrations moved to `supabase/migrations/_archive-pre-202603/` (kept as historical record, no longer on the migration path) +- **Removed from the current tree:** 69 pre-reset migrations that contained obsolete + schemas, demo data and a historical default administrator credential - **Live DB (`pcxcqbpygyidkusetghk`):** 89 migrations applied (recorded in `supabase_migrations.schema_migrations`) - **Active baseline:** `20260328145106_vezvision_fresh_schema` and everything after: matches live DB state @@ -14,13 +15,16 @@ The project underwent a full schema reset on 2026-03-28 (`vezvision_fresh_schema`). The fresh schema migration created a clean database, and subsequent migrations built on top of it. The 69 pre-reset migration files (CRM, HR, finance, AiConfig, ClientPortal, etc.) were left in the repo but were no longer part of the live migration history. -On 2026-07-08 the orphans were moved into `supabase/migrations/_archive-pre-202603/`. They remain as historical reference. Subdirectories of `supabase/migrations/` are not picked up by the Supabase CLI migration runner, so they can no longer cause drift-related `db push` failures. +On 2026-07-08 the orphaned files were moved out of the active migration path. On +2026-08-09 they were removed from the current public repository tree because they +were not required for deployment and included unsafe historical defaults. Earlier +commits remain an audit record until a separately approved history rewrite is made. ## Why this is safe - Orphans never applied to the live DB (Supabase recorded fresh schema as the active baseline) - Cutting them out from the migrations root does not change live DB state -- Migrations are now organized: active set in root, archive under `_archive-pre-202603/` +- Only the active migration set remains in `supabase/migrations/` - `supabase db push` is now safe against the live DB with the local migration set ## Remaining drift (minor) @@ -31,9 +35,11 @@ Some migration filenames in the local repo do not match the version timestamps r ``` Developer writes migration SQL - → Applies to live DB via supabase_apply_migration (MCP) - → Saves the same SQL to supabase/migrations/{timestamp}_{name}.sql (for version control) - → Migration is recorded in supabase_migrations.schema_migrations automatically + → Reviews it against the dedicated Hetzner PostgreSQL schema + → Backs up the target database + → Applies the migration through the controlled deployment path + → Saves the same SQL in version control and verifies schema drift ``` -This workflow avoids the drift problem entirely: `db push` is safe, no version conflicts, no risk to the live DB. +The standalone production source of truth is `deploy/hetzner/schema.sql`; legacy +Supabase migrations are retained only where they document the active schema lineage. diff --git a/frontend.Dockerfile b/frontend.Dockerfile index 3695510..73c30dd 100644 --- a/frontend.Dockerfile +++ b/frontend.Dockerfile @@ -1,4 +1,4 @@ -FROM node:22-bookworm-slim@sha256:6c74791e557ce11fc957704f6d4fe134a7bc8d6f5ca4403205b2966bd488f6b3 AS build +FROM node:22-bookworm-slim@sha256:d649c27dae7ba0137b3cef5dd75baa422c08dc3d9e3fc0c23dfb172dc3cc6436 AS build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci diff --git a/knip.json b/knip.json index 4845f2d..1f0050d 100644 --- a/knip.json +++ b/knip.json @@ -1,7 +1,16 @@ { + "entry": [ + "backend/server.mjs" + ], "ignore": [ "public/trusted-types-policy.js", "supabase/functions/**", "src/types/database.types.ts" - ] + ], + "ignoreDependencies": ["lightningcss", "pg"], + "rules": { + "exports": "warn", + "types": "warn", + "duplicates": "warn" + } } diff --git a/package-lock.json b/package-lock.json index cac2a8a..9030492 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@hookform/resolvers": "^5.4.0", "@sentry/react": "^10.67.0", "@tanstack/react-query": "^5.101.4", - "dompurify": "^3.4.12", + "dompurify": "^3.4.13", "lenis": "^1.3.25", "lucide-react": "^1.25.0", "react": "^19.2.8", @@ -19,7 +19,7 @@ "react-helmet-async": "^3.0.0", "react-hook-form": "^7.82.0", "react-markdown": "^10.1.0", - "react-router-dom": "^7.18.1", + "react-router-dom": "^7.18.2", "sonner": "^2.0.7", "web-vitals": "^6.0.0", "zod": "^4.4.3" @@ -29,7 +29,7 @@ "@commitlint/config-conventional": "^21.0.2", "@eslint/js": "^9.25.0", "@playwright/test": "^1.60.0", - "@size-limit/preset-app": "^12.1.0", + "@size-limit/file": "^13.0.3", "@tailwindcss/typography": "^0.5.19", "@tailwindcss/vite": "^4.3.2", "@testing-library/dom": "^10.4.1", @@ -41,7 +41,6 @@ "@vitejs/plugin-react": "^6.0.3", "@vitest/coverage-v8": "^4.0.14", "babel-plugin-react-compiler": "^1.0.0", - "deno-bin": "^2.2.7", "entities": "^8.0.0", "eslint": "^9.25.0", "eslint-plugin-jsx-a11y": "^6.10.2", @@ -56,7 +55,7 @@ "prettier": "^3.8.4", "sass-embedded": "^1.93.1", "sharp": "^0.35.3", - "size-limit": "^12.1.0", + "size-limit": "^13.0.3", "tailwindcss": "^4.3.2", "tsx": "^4.23.0", "typescript": "~5.8.3", @@ -3113,135 +3112,6 @@ "node": ">=18" } }, - "node_modules/@puppeteer/browsers": { - "version": "2.10.10", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.10.tgz", - "integrity": "sha512-3ZG500+ZeLql8rE0hjfhkycJjDj0pI/btEh3L9IkWUYcOrgP0xCNRq3HbtbqOPbvDhFaAWD88pDFtlLv8ns8gA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.4.3", - "extract-zip": "^2.0.1", - "progress": "^2.0.3", - "proxy-agent": "^6.5.0", - "semver": "^7.7.2", - "tar-fs": "^3.1.0", - "yargs": "^17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@puppeteer/browsers/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@puppeteer/browsers/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/@puppeteer/browsers/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@puppeteer/browsers/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@puppeteer/browsers/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@puppeteer/browsers/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@puppeteer/browsers/node_modules/yargs": { - "version": "17.7.3", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", - "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@puppeteer/browsers/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", @@ -3689,61 +3559,17 @@ "url": "https://ko-fi.com/dangreen" } }, - "node_modules/@sitespeed.io/tracium": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@sitespeed.io/tracium/-/tracium-0.3.3.tgz", - "integrity": "sha512-dNZafjM93Y+F+sfwTO5gTpsGXlnc/0Q+c2+62ViqP3gkMWvHEMSKkaEHgVJLcLg3i/g19GSIPziiKpgyne07Bw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@size-limit/file": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/@size-limit/file/-/file-12.1.0.tgz", - "integrity": "sha512-eGwDcIufnNnvJRzv3liDOn6MAOGgmOTUdpeGQ2KuRTlgIgO54AJH1ilvktlJc6PIjNfwpYY0dOGyap1QgM1swQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "size-limit": "12.1.0" - } - }, - "node_modules/@size-limit/preset-app": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/@size-limit/preset-app/-/preset-app-12.1.0.tgz", - "integrity": "sha512-pGGOxzDMM6MUXCzTwUjIcgex9RYbGdvQYni1rUtsZ1oojm7JvOSbBMiJPe9PhpmDq/aMsVzjP1oN0guq1RptVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@size-limit/file": "12.1.0", - "@size-limit/time": "12.1.0", - "size-limit": "12.1.0" - }, - "peerDependencies": { - "size-limit": "12.1.0" - } - }, - "node_modules/@size-limit/time": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/@size-limit/time/-/time-12.1.0.tgz", - "integrity": "sha512-ekYPeZcvkPSLsHtqNmz7F5jx3R0HV7CpY7kGasBW2yKR3NrD0JWMAcswS9OCR8OzK9hyLACRTNYTpLI9PXLczQ==", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/@size-limit/file/-/file-13.0.3.tgz", + "integrity": "sha512-PWTITIXH5p9aGIf6qq2Fruihn/b9nBQyfkyoAyb6DzFJgS1Ek9MSPJYKxKFLO8jdo0aqSgBPd3sevbS6PyBiJw==", "dev": true, "license": "MIT", - "dependencies": { - "estimo": "^3.0.5" - }, "engines": { "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "size-limit": "12.1.0" + "size-limit": "13.0.3" } }, "node_modules/@standard-schema/spec": { @@ -4170,13 +3996,6 @@ } } }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "dev": true, - "license": "MIT" - }, "node_modules/@tybys/wasm-util": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", @@ -4310,17 +4129,6 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.63.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz", @@ -4779,26 +4587,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/adm-zip": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.6.0.tgz", - "integrity": "sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0" - } - }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/ajv": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", @@ -4998,19 +4786,6 @@ "node": ">=12" } }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -5083,21 +4858,6 @@ "node": ">= 0.4" } }, - "node_modules/b4a": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", - "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "react-native-b4a": "*" - }, - "peerDependenciesMeta": { - "react-native-b4a": { - "optional": true - } - } - }, "node_modules/babel-plugin-react-compiler": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", @@ -5128,101 +4888,6 @@ "node": "18 || 20 || >=22" } }, - "node_modules/bare-events": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", - "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", - "dev": true, - "license": "Apache-2.0", - "peerDependencies": { - "bare-abort-controller": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - } - } - }, - "node_modules/bare-fs": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.4.tgz", - "integrity": "sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4", - "bare-url": "^2.2.2", - "fast-fifo": "^1.3.2" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, - "node_modules/bare-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", - "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/bare-stream": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", - "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.8.1", - "streamx": "^2.25.0", - "teex": "^1.0.1" - }, - "peerDependencies": { - "bare-abort-controller": "*", - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-abort-controller": { - "optional": true - }, - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } - } - }, - "node_modules/bare-url": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz", - "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-path": "^3.0.0" - } - }, - "node_modules/basic-ftp": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz", - "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/bidi-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", @@ -5234,9 +4899,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { @@ -5246,16 +4911,6 @@ "node": "20 || >=22" } }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/bytes-iec": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/bytes-iec/-/bytes-iec-3.1.1.tgz", @@ -5420,30 +5075,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/chromium-bidi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-8.0.0.tgz", - "integrity": "sha512-d1VmE0FD7lxZQHzcDUCKZSNRtRwISXDsdg4HjdTR5+Ll5nQ/vzU12JeNmupD6VWffrPSlrnGhEWlLESKH3VO+g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "mitt": "^3.0.1", - "zod": "^3.24.1" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/chromium-bidi/node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, "node_modules/cli-cursor": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", @@ -5585,16 +5216,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/commander": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz", - "integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5772,16 +5393,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/data-urls": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", @@ -5930,36 +5541,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/deno-bin": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/deno-bin/-/deno-bin-2.2.7.tgz", - "integrity": "sha512-ANCqji3OqD0Voji5OtUElCFRhcp1Wt6d2LUMR+jlOXkIN/MqL8BgzHFMi3a5e8YnB7IEpxsR/O3Yd+wkQXcc9g==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "adm-zip": "^0.5.4" - }, - "bin": { - "deno": "bin/deno.js", - "deno-bin": "bin/deno.js" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -5992,13 +5573,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/devtools-protocol": { - "version": "0.0.1495869", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1495869.tgz", - "integrity": "sha512-i+bkd9UYFis40RcnkW7XrOprCujXRAHg62IVh/Ah3G8MmNXpCGt1m0dTFhSdx/AVs8XEMbdOGRwdkR1Bcta8AA==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/dom-accessibility-api": { "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", @@ -6007,9 +5581,9 @@ "license": "MIT" }, "node_modules/dompurify": { - "version": "3.4.12", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", - "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", + "version": "3.4.13", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz", + "integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==", "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" @@ -6037,16 +5611,6 @@ "dev": true, "license": "MIT" }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/enhanced-resolve": { "version": "5.21.6", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", @@ -6361,28 +5925,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, "node_modules/eslint": { "version": "9.39.5", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", @@ -6578,20 +6120,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/esquery": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", @@ -6618,26 +6146,6 @@ "node": ">=4.0" } }, - "node_modules/estimo": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/estimo/-/estimo-3.0.5.tgz", - "integrity": "sha512-Q9asaAAM3KZc4Ckr8GMcJWYc3hNCf0KnmhkfzHuAWmqGoPssQoe5Mb8et1CYmmkeMfPTlUyeBHRi53Bedvnl1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sitespeed.io/tracium": "0.3.3", - "commander": "12.0.0", - "find-chrome-bin": "2.0.4", - "nanoid": "5.1.5", - "puppeteer-core": "24.22.0" - }, - "bin": { - "estimo": "scripts/cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -6685,16 +6193,6 @@ "dev": true, "license": "MIT" }, - "node_modules/events-universal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", - "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bare-events": "^2.7.0" - } - }, "node_modules/expect-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", @@ -6702,36 +6200,15 @@ "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" + "node": ">=12.0.0" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -6739,13 +6216,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true, - "license": "MIT" - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -6761,9 +6231,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "dev": true, "funding": [ { @@ -6787,16 +6257,6 @@ "walk-up-path": "^4.0.0" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -6828,19 +6288,6 @@ "node": ">=16.0.0" } }, - "node_modules/find-chrome-bin": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/find-chrome-bin/-/find-chrome-bin-2.0.4.tgz", - "integrity": "sha512-iKiqIb7FsA0hwnq0vvDay4RsmHUFLvWVquTb59XVlxfHS68XaWZfEjriF2vTZ3k/plicyKZxMJLqxKt10kSOtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@puppeteer/browsers": "2.10.10" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -7042,22 +6489,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-symbol-description": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", @@ -7089,21 +6520,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -7347,34 +6763,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -7505,16 +6893,6 @@ "loose-envify": "^1.0.0" } }, - "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -8053,9 +7431,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, "funding": [ { @@ -9493,9 +8871,9 @@ "license": "MIT" }, "node_modules/minimatch/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "dependencies": { @@ -9503,38 +8881,12 @@ "concat-map": "0.0.1" } }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true, - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/nanoid": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", - "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, "node_modules/nanospinner": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/nanospinner/-/nanospinner-1.2.2.tgz", @@ -9552,16 +8904,6 @@ "dev": true, "license": "MIT" }, - "node_modules/netmask": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz", - "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", @@ -9666,16 +9008,6 @@ "node": ">=12.20.0" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/onetime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", @@ -9829,40 +9161,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "dev": true, - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -9960,13 +9258,6 @@ "dev": true, "license": "MIT" }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true, - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -10030,9 +9321,9 @@ } }, "node_modules/postcss": { - "version": "8.5.19", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", - "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "funding": [ { @@ -10050,7 +9341,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -10073,9 +9364,9 @@ } }, "node_modules/postcss/node_modules/nanoid": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", - "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "dev": true, "funding": [ { @@ -10145,16 +9436,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/property-information": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", @@ -10165,54 +9446,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -10223,25 +9456,6 @@ "node": ">=6" } }, - "node_modules/puppeteer-core": { - "version": "24.22.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.22.0.tgz", - "integrity": "sha512-oUeWlIg0pMz8YM5pu0uqakM+cCyYyXkHBxx9di9OUELu9X9+AYrNGGRLK9tNME3WfN3JGGqQIH3b4/E9LGek/w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.10.10", - "chromium-bidi": "8.0.0", - "debug": "^4.4.3", - "devtools-protocol": "0.0.1495869", - "typed-query-selector": "^2.12.0", - "webdriver-bidi-protocol": "0.2.11", - "ws": "^8.18.3" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/react": { "version": "19.2.8", "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", @@ -10334,9 +9548,9 @@ } }, "node_modules/react-router": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", - "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", + "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -10356,12 +9570,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz", - "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz", + "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==", "license": "MIT", "dependencies": { - "react-router": "7.18.1" + "react-router": "7.18.2" }, "engines": { "node": ">=20.0.0" @@ -10477,16 +9691,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -11307,31 +10511,21 @@ } }, "node_modules/size-limit": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/size-limit/-/size-limit-12.1.0.tgz", - "integrity": "sha512-VnDS2fycANrJFVPQwjaD+h+hkISY7EB3LsPsYWje4lBCjQwwsZLxjwwRwVJKHrcj2ZqyG+DdXykWm9mbZklZrw==", + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/size-limit/-/size-limit-13.0.3.tgz", + "integrity": "sha512-KVb2aNEU49BwTR21SVjD+2QHP9gBV/nWsTHzNB/heRwXtHyA7lLQiDZDQ1TiNh/B/TZXKAZrHYyTt+cvBUrzYw==", "dev": true, "license": "MIT", "dependencies": { "bytes-iec": "^3.1.1", "lilconfig": "^3.1.3", - "nanospinner": "^1.2.2", - "picocolors": "^1.1.1", - "tinyglobby": "^0.2.16" + "nanospinner": "^1.2.2" }, "bin": { "size-limit": "bin.js" }, "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "peerDependencies": { - "jiti": "^2.0.0" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "node": "^22.18.0 || ^24.0.0 || >=26.0.0" } }, "node_modules/slice-ansi": { @@ -11364,17 +10558,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, "node_modules/smol-toml": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.0.tgz", @@ -11388,36 +10571,6 @@ "url": "https://github.com/sponsors/cyyynthia" } }, - "node_modules/socks": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", - "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "ip-address": "^10.1.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/sonner": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", @@ -11428,17 +10581,6 @@ "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -11487,18 +10629,6 @@ "node": ">= 0.4" } }, - "node_modules/streamx": { - "version": "2.28.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", - "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", - "dev": true, - "license": "MIT", - "dependencies": { - "events-universal": "^1.0.0", - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - } - }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -11752,21 +10882,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/tar-fs": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.3.tgz", - "integrity": "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, "node_modules/tar-mini": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/tar-mini/-/tar-mini-0.2.0.tgz", @@ -11774,39 +10889,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tar-stream": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", - "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "b4a": "^1.6.4", - "bare-fs": "^4.5.5", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } - }, - "node_modules/teex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", - "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "streamx": "^2.12.5" - } - }, - "node_modules/text-decoder": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz", - "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -12062,13 +11144,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typed-query-selector": { - "version": "2.12.2", - "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", - "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==", - "dev": true, - "license": "MIT" - }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -12137,9 +11212,9 @@ } }, "node_modules/undici": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", - "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", "dev": true, "license": "MIT", "engines": { @@ -12533,13 +11608,6 @@ "integrity": "sha512-Guaibvy/+uNtL6Bsu4jmMJGzuSl91oeRH5iO9pPRbYftnFUr3yqT1TUNX/OE4o9HexuEMU3Kb/Wg7iKhlffZUA==", "license": "Apache-2.0" }, - "node_modules/webdriver-bidi-protocol": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.2.11.tgz", - "integrity": "sha512-Y9E1/oi4XMxcR8AT0ZC4OvYntl34SPgwjmELH+owjBr0korAX4jKgZULBWILGCVGdVCQ0dodTToIETozhG8zvA==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/webidl-conversions": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", @@ -12738,35 +11806,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -12863,17 +11902,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index f4b87f1..c3618ae 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,14 @@ "private": true, "version": "0.0.0", "type": "module", + "packageManager": "npm@11.6.2", "scripts": { "dev": "vite", "sync:security-headers": "node --env-file-if-exists=.env scripts/sync-security-headers.mjs", "verify:security": "node --env-file-if-exists=.env scripts/verify-security-headers.mjs", "test:csp": "node --env-file-if-exists=.env --test scripts/csp-policy.test.mjs", - "test:scripts": "node --env-file-if-exists=.env --test scripts/csp-policy.test.mjs backend/admin-gateway.node-test.mjs backend/email-templates.node-test.mjs backend/resend-email.node-test.mjs && npx tsx --test scripts/seo-build-validation.test.ts", + "test:edge-parity": "node scripts/verify-edge-shared-parity.mjs", + "test:scripts": "npm run test:edge-parity && node --env-file-if-exists=.env --test scripts/csp-policy.test.mjs backend/admin-gateway.node-test.mjs backend/email-templates.node-test.mjs backend/resend-email.node-test.mjs && npx tsx --test scripts/seo-build-validation.test.ts", "build": "npm run sync:security-headers && npm run verify:security && npm run typecheck && vitest --run src/data/translations/parity.test.ts && npm run generate-sitemap && npm run generate-robots-txt && npm run generate-rss-feeds && npm run generate-llms-full && node --env-file-if-exists=.env ./node_modules/vite/bin/vite.js build && npx tsx --env-file-if-exists=.env scripts/prerender-head.ts && node scripts/copy-spa-index-to-locale-dirs.mjs && npx tsx --env-file-if-exists=.env scripts/verify-production-build.ts", "lint": "eslint .", "knip": "knip", @@ -38,7 +40,7 @@ "@hookform/resolvers": "^5.4.0", "@sentry/react": "^10.67.0", "@tanstack/react-query": "^5.101.4", - "dompurify": "^3.4.12", + "dompurify": "^3.4.13", "lenis": "^1.3.25", "lucide-react": "^1.25.0", "react": "^19.2.8", @@ -46,7 +48,7 @@ "react-helmet-async": "^3.0.0", "react-hook-form": "^7.82.0", "react-markdown": "^10.1.0", - "react-router-dom": "^7.18.1", + "react-router-dom": "^7.18.2", "sonner": "^2.0.7", "web-vitals": "^6.0.0", "zod": "^4.4.3" @@ -56,7 +58,7 @@ "@commitlint/config-conventional": "^21.0.2", "@eslint/js": "^9.25.0", "@playwright/test": "^1.60.0", - "@size-limit/preset-app": "^12.1.0", + "@size-limit/file": "^13.0.3", "@tailwindcss/typography": "^0.5.19", "@tailwindcss/vite": "^4.3.2", "@testing-library/dom": "^10.4.1", @@ -68,7 +70,6 @@ "@vitejs/plugin-react": "^6.0.3", "@vitest/coverage-v8": "^4.0.14", "babel-plugin-react-compiler": "^1.0.0", - "deno-bin": "^2.2.7", "entities": "^8.0.0", "eslint": "^9.25.0", "eslint-plugin-jsx-a11y": "^6.10.2", @@ -83,7 +84,7 @@ "prettier": "^3.8.4", "sass-embedded": "^1.93.1", "sharp": "^0.35.3", - "size-limit": "^12.1.0", + "size-limit": "^13.0.3", "tailwindcss": "^4.3.2", "tsx": "^4.23.0", "typescript": "~5.8.3", @@ -96,11 +97,6 @@ "engines": { "node": ">=22" }, - "overrides": { - "deno-bin": { - "adm-zip": "0.6.0" - } - }, "lint-staged": { "*.{ts,tsx}": "eslint --fix", "*.{ts,tsx,css,md,json}": "prettier --write" diff --git a/public/.well-known/security.txt b/public/.well-known/security.txt index a6dd76c..fe575a4 100644 --- a/public/.well-known/security.txt +++ b/public/.well-known/security.txt @@ -1,5 +1,5 @@ -Contact: mailto:contact@vezvision.com -Expires: 2027-12-31T23:00:00.000Z +Contact: mailto:security@vezvision.com Preferred-Languages: pl, en Canonical: https://vezvision.com/.well-known/security.txt -Policy: https://vezvision.com/privacy-policy +Policy: https://github.com/VEZvision/vezvision.com/security/policy +Expires: 2027-08-09T00:00:00.000Z diff --git a/src/components/footer/FooterNavLegal.tsx b/src/components/footer/FooterNavLegal.tsx deleted file mode 100644 index ec3976b..0000000 --- a/src/components/footer/FooterNavLegal.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { Fragment } from "react"; -import { Link } from "react-router-dom"; -import type { Language } from "@/hooks/useLanguage"; -import { getLocalizedLabel } from "@/utils/i18n"; - -interface NavItem { - id: string; - href: string; - labelPl: string; - labelEn: string; - enabled: boolean; -} - -interface FooterNavProps { - navLinks: NavItem[]; - legalLinks: NavItem[]; - language: Language; - onPrivacySettings: () => void; - brandName: string; - isExternal: (href: string) => boolean; - linkClass: string; - legalLinkClass: string; -} - -export function FooterNavLegal({ - navLinks, - legalLinks, - language, - onPrivacySettings, - brandName, - isExternal, - linkClass, - legalLinkClass, -}: FooterNavProps) { - return ( - - ); -} diff --git a/src/components/hero/Hero.module.scss b/src/components/hero/Hero.module.scss deleted file mode 100644 index 8e84a22..0000000 --- a/src/components/hero/Hero.module.scss +++ /dev/null @@ -1,88 +0,0 @@ -* { - box-sizing: border-box; -} - -.sectionHero { - position: relative; - display: flex; - align-items: center; - justify-content: center; - width: 100%; - min-height: 100vh; - min-height: 100dvh; - overflow: hidden; - background: #ffffff; - isolation: isolate; - padding-top: env(safe-area-inset-top); - margin-top: calc(-1 * env(safe-area-inset-top)); -} - -.heroInner { - position: relative; - z-index: 20; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - width: 100%; - max-width: 980px; - height: 100%; - margin: 0 auto; - padding: clamp(6.5rem, 12vh, 8.5rem) 1rem 2.5rem; - text-align: center; - box-sizing: border-box; -} - -.videoBg { - position: absolute; - top: 50%; - left: 50%; - width: 100%; - height: 100%; - object-fit: cover; - z-index: 1; - pointer-events: none; - transform: translate3d(-50%, -50%, 0) scale(1.06); - filter: grayscale(1); - opacity: 0.45; -} - -.videoOverlay { - position: absolute; - inset: 0; - z-index: 10; - background: rgba(255, 255, 255, 0.8); - pointer-events: none; -} - -@media (pointer: coarse) { - .videoOverlay { - background: rgba(255, 255, 255, 0.7); - } -} - -.logoPlate { - width: 80px; - height: 80px; - display: flex; - align-items: center; - justify-content: center; -} - -@media (max-width: 639px) { - .sectionHero { - min-height: 68vh; - min-height: 68dvh; - } - - .heroInner { - padding: clamp(4.5rem, 10vh, 5.75rem) 1rem 2rem; - } -} - -@media (min-width: 640px) { - .logoPlate { - width: 96px; - height: 96px; - } -} diff --git a/src/data/legalTemplates.ts b/src/data/legalTemplates.ts index 0a9d4bb..08c5403 100644 --- a/src/data/legalTemplates.ts +++ b/src/data/legalTemplates.ts @@ -31,7 +31,7 @@ Dane podane w formularzu są przetwarzane w celu wynikającym z funkcji konkretn Dane osobowe przechowywane są przez okres niezbędny do realizacji celu przetwarzania: * **Dane z formularza kontaktowego** - przechowywane przez okres niezbędny do obsługi zgłoszenia, nie dłużej niż 2 lata od ostatniego kontaktu, chyba że przepisy prawa wymagają dłuższego okresu. -* **Dane subskrybenta newslettera** - przechowywane do momentu cofnięcia zgody (wypisania się z newslettera); niepotwierdzone zapisy są usuwane po 30 dniach. +* **Dane subskrybenta newslettera** - przechowywane do momentu cofnięcia zgody (wypisania się z newslettera); niepotwierdzone zapisy są usuwane po 30 dniach, a 30 dni po wypisaniu adres e-mail i dane techniczne są anonimizowane. Zachowujemy wyłącznie skrót adresu oraz daty zgody i jej cofnięcia jako minimalny dowód zgodności. * **Dane dotyczące zgody na pliki cookies** - przechowywane przez 1 rok od wyrażenia zgody lub do jej cofnięcia. * **Dane sesyjne (tokeny autoryzacyjne)** - przechowywane przez czas trwania sesji. @@ -104,7 +104,7 @@ Data provided in the form is processed for the purpose resulting from the functi Personal data is stored for the period necessary to achieve the purpose of processing: * **Contact form data** - stored for the period necessary to handle the inquiry, no longer than 2 years from the last contact, unless applicable law requires a longer period. -* **Newsletter subscriber data** - stored until consent is withdrawn (unsubscribing); unconfirmed signups are deleted after 30 days. +* **Newsletter subscriber data** - stored until consent is withdrawn (unsubscribing); unconfirmed signups are deleted after 30 days, and the e-mail address and technical data are anonymized 30 days after unsubscribing. Only a hash of the address and the consent/withdrawal dates remain as minimum compliance evidence. * **Cookie consent data** - stored for 1 year from the date of granting consent or until it is withdrawn. * **Session data (authentication tokens)** - stored for the duration of the session. diff --git a/src/hooks/useBackgroundVideoSection.ts b/src/hooks/useBackgroundVideoSection.ts deleted file mode 100644 index 96e2c3d..0000000 --- a/src/hooks/useBackgroundVideoSection.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { useEffect, useRef, type RefObject } from "react"; - -import { - bindBackgroundVideoPlayback, - installBackgroundVideoRecovery, - playBackgroundVideo, -} from "@/utils/backgroundVideo"; - -type UseBackgroundVideoSectionOptions = { - enabled: boolean; - sectionRef: RefObject; - videoRef: RefObject; - initiallyVisible?: boolean; - threshold?: number; - rootMargin?: string; - reloadKey?: string; - /** When false, media events can trigger play even if IO says off-screen. */ - gateMediaEventsOnVisibility?: boolean; -}; - -export function useBackgroundVideoSection({ - enabled, - sectionRef, - videoRef, - initiallyVisible = false, - threshold = 0.05, - rootMargin, - reloadKey, - gateMediaEventsOnVisibility = true, -}: UseBackgroundVideoSectionOptions): void { - const previousReloadKeyRef = useRef(undefined); - - useEffect(() => { - if (!enabled || reloadKey === undefined) return; - - const previousKey = previousReloadKeyRef.current; - previousReloadKeyRef.current = reloadKey; - - if (previousKey !== undefined && previousKey !== reloadKey) { - videoRef.current?.load(); - } - }, [enabled, reloadKey, videoRef]); - - useEffect(() => { - const sectionEl = sectionRef.current; - const videoEl = videoRef.current; - if (!videoEl || !enabled) return; - - const isVisibleRef = { current: initiallyVisible }; - const playVideo = () => { - playBackgroundVideo(videoEl); - }; - const pauseVideo = () => { - videoEl.pause(); - }; - - const unbindPlayback = gateMediaEventsOnVisibility - ? bindBackgroundVideoPlayback(videoEl, playVideo, { - canPlay: () => isVisibleRef.current, - }) - : bindBackgroundVideoPlayback(videoEl, playVideo); - - if (!sectionEl || !("IntersectionObserver" in window)) { - isVisibleRef.current = true; - playVideo(); - const unbindRecovery = installBackgroundVideoRecovery(videoEl, playVideo); - return () => { - unbindRecovery(); - unbindPlayback(); - pauseVideo(); - }; - } - - const observer = new IntersectionObserver( - ([entry]) => { - if (!entry) return; - isVisibleRef.current = entry.isIntersecting; - if (entry.isIntersecting) { - playVideo(); - } else { - pauseVideo(); - } - }, - rootMargin === undefined ? { threshold } : { threshold, rootMargin }, - ); - - observer.observe(sectionEl); - - if (initiallyVisible) { - playVideo(); - } - - const unbindRecovery = installBackgroundVideoRecovery(videoEl, () => { - if (isVisibleRef.current) { - playVideo(); - } - }); - - return () => { - observer.disconnect(); - unbindRecovery(); - unbindPlayback(); - pauseVideo(); - }; - }, [ - enabled, - gateMediaEventsOnVisibility, - initiallyVisible, - reloadKey, - rootMargin, - sectionRef, - threshold, - videoRef, - ]); -} diff --git a/supabase/migrations/_archive-pre-202603/001_create_users_table.sql b/supabase/migrations/_archive-pre-202603/001_create_users_table.sql deleted file mode 100644 index 833a3ae..0000000 --- a/supabase/migrations/_archive-pre-202603/001_create_users_table.sql +++ /dev/null @@ -1,61 +0,0 @@ --- Rozszerzenia wymagane do generowania UUID i bcrypt -CREATE EXTENSION IF NOT EXISTS pgcrypto; - --- Tabela administratorów (nie korzysta z auth.users, własna kontrola haseł) -CREATE TABLE IF NOT EXISTS admins ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - username TEXT UNIQUE NOT NULL, - email TEXT UNIQUE NOT NULL, - password_hash TEXT NOT NULL, - role TEXT NOT NULL DEFAULT 'admin' CHECK (role IN ('admin')), - is_active BOOLEAN NOT NULL DEFAULT TRUE, - created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), - updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() -); - -CREATE INDEX IF NOT EXISTS idx_admins_username ON admins(username); -CREATE INDEX IF NOT EXISTS idx_admins_email ON admins(email); - --- Trigger aktualizujący updated_at -CREATE OR REPLACE FUNCTION admins_set_updated_at() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at := NOW(); - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -DROP TRIGGER IF EXISTS trg_admins_updated_at ON admins; -CREATE TRIGGER trg_admins_updated_at -BEFORE UPDATE ON admins -FOR EACH ROW -EXECUTE FUNCTION admins_set_updated_at(); - --- Włączenie RLS – nie będziemy udostępniać bezpośrednich SELECT-ów z frontendu -ALTER TABLE admins ENABLE ROW LEVEL SECURITY; - --- Funkcja RPC do logowania admina; nie ujawnia hashy; porównuje bcrypt -CREATE OR REPLACE FUNCTION admin_login(p_username TEXT, p_password TEXT) -RETURNS TABLE (id UUID, username TEXT, email TEXT, role TEXT) -SET search_path = public -AS $$ -BEGIN - RETURN QUERY - SELECT a.id, a.username, a.email, a.role - FROM admins a - WHERE a.is_active = TRUE - AND a.username = p_username - AND a.password_hash = crypt(p_password, a.password_hash) - LIMIT 1; -END; -$$ LANGUAGE plpgsql SECURITY DEFINER; - --- Uprawnienia do wykonywania RPC z kluczem anon -GRANT EXECUTE ON FUNCTION admin_login(TEXT, TEXT) TO anon; - --- Dodanie domyślnego admina: login 'admin', hasło 'admin123' -INSERT INTO admins (username, email, password_hash) -SELECT 'admin', 'admin@vezvision.pl', crypt('admin123', gen_salt('bf')) -WHERE NOT EXISTS ( - SELECT 1 FROM admins WHERE username = 'admin' -); \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/002_fix_pgcrypto.sql b/supabase/migrations/_archive-pre-202603/002_fix_pgcrypto.sql deleted file mode 100644 index bed41bd..0000000 --- a/supabase/migrations/_archive-pre-202603/002_fix_pgcrypto.sql +++ /dev/null @@ -1,42 +0,0 @@ --- Naprawa rozszerzenia pgcrypto i funkcji logowania - --- Upewnij się że rozszerzenie jest zainstalowane -CREATE EXTENSION IF NOT EXISTS pgcrypto SCHEMA public; - --- Usuń starą funkcję jeśli istnieje -DROP FUNCTION IF EXISTS admin_login(text, text); - --- Utwórz funkcję logowania z poprawnymi typami danych -CREATE OR REPLACE FUNCTION admin_login(p_username TEXT, p_password TEXT) -RETURNS TABLE ( - id UUID, - username TEXT, - email TEXT, - role TEXT -) -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -BEGIN - RETURN QUERY - SELECT - a.id, - a.username, - a.email, - a.role - FROM admins a - WHERE a.is_active = TRUE - AND a.username = p_username - AND a.password_hash = crypt(p_password, a.password_hash) - LIMIT 1; -END; -$$; - --- Przyznaj uprawnienia do funkcji -GRANT EXECUTE ON FUNCTION admin_login(text, text) TO anon; -GRANT EXECUTE ON FUNCTION admin_login(text, text) TO authenticated; - --- Sprawdź czy domyślny admin istnieje, jeśli nie to dodaj -INSERT INTO admins (username, email, password_hash) -SELECT 'admin', 'admin@vezvision.pl', crypt('admin123', gen_salt('bf')) -WHERE NOT EXISTS (SELECT 1 FROM admins WHERE username = 'admin'); \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241118_portfolio_storage.sql b/supabase/migrations/_archive-pre-202603/20241118_portfolio_storage.sql deleted file mode 100644 index 284c9c2..0000000 --- a/supabase/migrations/_archive-pre-202603/20241118_portfolio_storage.sql +++ /dev/null @@ -1,25 +0,0 @@ --- Create storage bucket for portfolio images -insert into storage.buckets (id, name, public, file_size_limit, allowed_mime_types) -values ('portfolio', 'portfolio', true, 5242880, ARRAY['image/jpeg','image/jpg','image/png','image/webp','image/svg+xml']); - --- Create storage policies for portfolio bucket --- Public can read all files -create policy "Public can read portfolio files" on storage.objects - for select to anon, authenticated - using (bucket_id = 'portfolio'); - --- Admin can upload/update/delete files -create policy "Admin can manage portfolio files" on storage.objects - for all to authenticated - using ( - bucket_id = 'portfolio' and - exists ( - select 1 from public.admins - where admins.username = auth.jwt() ->> 'email' and admins.is_active = true - ) - ); - --- Grant permissions -grant usage on schema storage to anon, authenticated; -grant all on storage.objects to authenticated; -grant all on storage.buckets to authenticated; \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241118_portfolio_tables.sql b/supabase/migrations/_archive-pre-202603/20241118_portfolio_tables.sql deleted file mode 100644 index e92919f..0000000 --- a/supabase/migrations/_archive-pre-202603/20241118_portfolio_tables.sql +++ /dev/null @@ -1,226 +0,0 @@ --- Enable UUID extension if not exists -create extension if not exists "uuid-ossp"; - --- Create admins table for role-based access -create table if not exists public.admins ( - id uuid primary key default uuid_generate_v4(), - user_id uuid references auth.users(id) on delete cascade, - is_admin boolean default true, - created_at timestamptz default now() -); - --- Create projects table -create table if not exists public.projects ( - id uuid primary key default uuid_generate_v4(), - slug text unique not null, - category text check (category in ('web-apps','websites','tools','mobile-apps','concepts','prototypes','demos')) not null, - status text check (status in ('active','archived','coming-soon','concept','prototype')) default 'active', - featured boolean default false, - order_index integer default 0, - demo_url text, - github_url text, - client_name text, - cover_path text, - created_at timestamptz default now(), - updated_at timestamptz default now() -); - --- Create project_translations table for multilingual content -create table if not exists public.project_translations ( - id uuid primary key default uuid_generate_v4(), - project_id uuid references public.projects(id) on delete cascade not null, - locale text check (locale in ('pl','en')) not null, - title text not null, - short_description text not null, - description text not null, - seo_title text, - seo_description text, - seo_keywords text[], - unique (project_id, locale) -); - --- Create project_images table for image gallery -create table if not exists public.project_images ( - id uuid primary key default uuid_generate_v4(), - project_id uuid references public.projects(id) on delete cascade not null, - path text not null, - type text check (type in ('screenshot','mockup','logo','banner','concept','prototype')) default 'screenshot', - "order" integer default 0, - alt_pl text, - alt_en text, - created_at timestamptz default now() -); - --- Create project_technologies table -create table if not exists public.project_technologies ( - id uuid primary key default uuid_generate_v4(), - project_id uuid references public.projects(id) on delete cascade not null, - name text not null, - color text not null, - icon text, - "order" integer default 0 -); - --- Create audit_logs table for tracking changes -create table if not exists public.audit_logs ( - id uuid primary key default uuid_generate_v4(), - table_name text not null, - record_id uuid not null, - action text check (action in ('insert','update','delete')), - admin_id uuid references public.admins(id), - old_values jsonb, - new_values jsonb, - created_at timestamptz default now() -); - --- Create indexes for performance -create index idx_projects_slug on public.projects(slug); -create index idx_projects_status on public.projects(status); -create index idx_projects_order_index on public.projects(order_index); -create index idx_projects_featured on public.projects(featured); -create index idx_project_translations_project_locale on public.project_translations(project_id, locale); -create index idx_project_images_project on public.project_images(project_id); -create index idx_project_technologies_project on public.project_technologies(project_id); -create index idx_audit_logs_table_record on public.audit_logs(table_name, record_id); - --- Enable Row Level Security -alter table public.projects enable row level security; -alter table public.project_translations enable row level security; -alter table public.project_images enable row level security; -alter table public.project_technologies enable row level security; -alter table public.admins enable row level security; -alter table public.audit_logs enable row level security; - --- RLS Policies for projects --- Select: public can view active projects, admin can view all -create policy "Public can view active projects" on public.projects for select - using (status = 'active'); - -create policy "Admin can view all projects" on public.projects for select - using ( - exists ( - select 1 from public.admins - where admins.user_id = auth.uid() and admins.is_admin = true - ) - ); - --- Insert/Update/Delete: only admin -create policy "Admin can insert projects" on public.projects for insert - with check ( - exists ( - select 1 from public.admins - where admins.user_id = auth.uid() and admins.is_admin = true - ) - ); - -create policy "Admin can update projects" on public.projects for update - using ( - exists ( - select 1 from public.admins - where admins.user_id = auth.uid() and admins.is_admin = true - ) - ); - -create policy "Admin can delete projects" on public.projects for delete - using ( - exists ( - select 1 from public.admins - where admins.user_id = auth.uid() and admins.is_admin = true - ) - ); - --- RLS Policies for project_translations (same admin-only pattern) -create policy "Public can view translations for active projects" on public.project_translations for select - using ( - exists ( - select 1 from public.projects - where projects.id = project_translations.project_id and projects.status = 'active' - ) - ); - -create policy "Admin can manage translations" on public.project_translations for all - using ( - exists ( - select 1 from public.admins - where admins.user_id = auth.uid() and admins.is_admin = true - ) - ); - --- RLS Policies for project_images -create policy "Public can view images for active projects" on public.project_images for select - using ( - exists ( - select 1 from public.projects - where projects.id = project_images.project_id and projects.status = 'active' - ) - ); - -create policy "Admin can manage images" on public.project_images for all - using ( - exists ( - select 1 from public.admins - where admins.user_id = auth.uid() and admins.is_admin = true - ) - ); - --- RLS Policies for project_technologies -create policy "Public can view technologies for active projects" on public.project_technologies for select - using ( - exists ( - select 1 from public.projects - where projects.id = project_technologies.project_id and projects.status = 'active' - ) - ); - -create policy "Admin can manage technologies" on public.project_technologies for all - using ( - exists ( - select 1 from public.admins - where admins.user_id = auth.uid() and admins.is_admin = true - ) - ); - --- RLS Policies for admins (admin can manage admins) -create policy "Admin can manage admins" on public.admins for all - using ( - exists ( - select 1 from public.admins - where admins.user_id = auth.uid() and admins.is_admin = true - ) - ); - --- RLS Policies for audit_logs (admin can view) -create policy "Admin can view audit logs" on public.audit_logs for select - using ( - exists ( - select 1 from public.admins - where admins.user_id = auth.uid() and admins.is_admin = true - ) - ); - --- Grant permissions to anon and authenticated roles -grant select on public.projects to anon, authenticated; -grant select on public.project_translations to anon, authenticated; -grant select on public.project_images to anon, authenticated; -grant select on public.project_technologies to anon, authenticated; -grant all on public.projects to authenticated; -grant all on public.project_translations to authenticated; -grant all on public.project_images to authenticated; -grant all on public.project_technologies to authenticated; -grant all on public.admins to authenticated; -grant select on public.audit_logs to authenticated; - --- Create function to update updated_at timestamp -create or replace function public.update_updated_at_column() -returns trigger as $$ -begin - new.updated_at = now(); - return new; -end; -$$ language plpgsql; - --- Create trigger for projects updated_at -create trigger update_projects_updated_at - before update on public.projects - for each row - execute function public.update_updated_at_column(); \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241118_portfolio_tables_fixed.sql b/supabase/migrations/_archive-pre-202603/20241118_portfolio_tables_fixed.sql deleted file mode 100644 index 4ec1898..0000000 --- a/supabase/migrations/_archive-pre-202603/20241118_portfolio_tables_fixed.sql +++ /dev/null @@ -1,204 +0,0 @@ --- Create projects table -create table if not exists public.projects ( - id uuid primary key default gen_random_uuid(), - slug text unique not null, - category text check (category in ('web-apps','websites','tools','mobile-apps','concepts','prototypes','demos')) not null, - status text check (status in ('active','archived','coming-soon','concept','prototype')) default 'active', - featured boolean default false, - order_index integer default 0, - demo_url text, - github_url text, - client_name text, - cover_path text, - created_at timestamptz default now(), - updated_at timestamptz default now() -); - --- Create project_translations table for multilingual content -create table if not exists public.project_translations ( - id uuid primary key default gen_random_uuid(), - project_id uuid references public.projects(id) on delete cascade not null, - locale text check (locale in ('pl','en')) not null, - title text not null, - short_description text not null, - description text not null, - seo_title text, - seo_description text, - seo_keywords text[], - unique (project_id, locale) -); - --- Create project_images table for image gallery -create table if not exists public.project_images ( - id uuid primary key default gen_random_uuid(), - project_id uuid references public.projects(id) on delete cascade not null, - path text not null, - type text check (type in ('screenshot','mockup','logo','banner','concept','prototype')) default 'screenshot', - "order" integer default 0, - alt_pl text, - alt_en text, - created_at timestamptz default now() -); - --- Create project_technologies table -create table if not exists public.project_technologies ( - id uuid primary key default gen_random_uuid(), - project_id uuid references public.projects(id) on delete cascade not null, - name text not null, - color text not null, - icon text, - "order" integer default 0 -); - --- Create audit_logs table for tracking changes -create table if not exists public.audit_logs ( - id uuid primary key default gen_random_uuid(), - table_name text not null, - record_id uuid not null, - action text check (action in ('insert','update','delete')), - admin_id uuid references public.admins(id), - old_values jsonb, - new_values jsonb, - created_at timestamptz default now() -); - --- Create indexes for performance -create index idx_projects_slug on public.projects(slug); -create index idx_projects_status on public.projects(status); -create index idx_projects_order_index on public.projects(order_index); -create index idx_projects_featured on public.projects(featured); -create index idx_project_translations_project_locale on public.project_translations(project_id, locale); -create index idx_project_images_project on public.project_images(project_id); -create index idx_project_technologies_project on public.project_technologies(project_id); -create index idx_audit_logs_table_record on public.audit_logs(table_name, record_id); - --- Enable Row Level Security -alter table public.projects enable row level security; -alter table public.project_translations enable row level security; -alter table public.project_images enable row level security; -alter table public.project_technologies enable row level security; -alter table public.audit_logs enable row level security; - --- RLS Policies for projects --- Select: public can view active projects, admin can view all -create policy "Public can view active projects" on public.projects for select - using (status = 'active'); - -create policy "Admin can view all projects" on public.projects for select - using ( - exists ( - select 1 from public.admins - where admins.username = auth.jwt() ->> 'email' and admins.is_active = true - ) - ); - --- Insert/Update/Delete: only admin -create policy "Admin can insert projects" on public.projects for insert - with check ( - exists ( - select 1 from public.admins - where admins.username = auth.jwt() ->> 'email' and admins.is_active = true - ) - ); - -create policy "Admin can update projects" on public.projects for update - using ( - exists ( - select 1 from public.admins - where admins.username = auth.jwt() ->> 'email' and admins.is_active = true - ) - ); - -create policy "Admin can delete projects" on public.projects for delete - using ( - exists ( - select 1 from public.admins - where admins.username = auth.jwt() ->> 'email' and admins.is_active = true - ) - ); - --- RLS Policies for project_translations (same admin-only pattern) -create policy "Public can view translations for active projects" on public.project_translations for select - using ( - exists ( - select 1 from public.projects - where projects.id = project_translations.project_id and projects.status = 'active' - ) - ); - -create policy "Admin can manage translations" on public.project_translations for all - using ( - exists ( - select 1 from public.admins - where admins.username = auth.jwt() ->> 'email' and admins.is_active = true - ) - ); - --- RLS Policies for project_images -create policy "Public can view images for active projects" on public.project_images for select - using ( - exists ( - select 1 from public.projects - where projects.id = project_images.project_id and projects.status = 'active' - ) - ); - -create policy "Admin can manage images" on public.project_images for all - using ( - exists ( - select 1 from public.admins - where admins.username = auth.jwt() ->> 'email' and admins.is_active = true - ) - ); - --- RLS Policies for project_technologies -create policy "Public can view technologies for active projects" on public.project_technologies for select - using ( - exists ( - select 1 from public.projects - where projects.id = project_technologies.project_id and projects.status = 'active' - ) - ); - -create policy "Admin can manage technologies" on public.project_technologies for all - using ( - exists ( - select 1 from public.admins - where admins.username = auth.jwt() ->> 'email' and admins.is_active = true - ) - ); - --- RLS Policies for audit_logs (admin can view) -create policy "Admin can view audit logs" on public.audit_logs for select - using ( - exists ( - select 1 from public.admins - where admins.username = auth.jwt() ->> 'email' and admins.is_active = true - ) - ); - --- Grant permissions to anon and authenticated roles -grant select on public.projects to anon, authenticated; -grant select on public.project_translations to anon, authenticated; -grant select on public.project_images to anon, authenticated; -grant select on public.project_technologies to anon, authenticated; -grant all on public.projects to authenticated; -grant all on public.project_translations to authenticated; -grant all on public.project_images to authenticated; -grant all on public.project_technologies to authenticated; -grant select on public.audit_logs to authenticated; - --- Create function to update updated_at timestamp -create or replace function public.update_updated_at_column() -returns trigger as $$ -begin - new.updated_at = now(); - return new; -end; -$$ language plpgsql; - --- Create trigger for projects updated_at -create trigger update_projects_updated_at - before update on public.projects - for each row - execute function public.update_updated_at_column(); \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_add_sample_data.sql b/supabase/migrations/_archive-pre-202603/20241119_add_sample_data.sql deleted file mode 100644 index 8cf8c3d..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_add_sample_data.sql +++ /dev/null @@ -1,425 +0,0 @@ --- MIGRATION TYPE: SEED DATA (historical) --- This migration inserts sample/demo data. It was applied early in the project --- lifecycle and targets legacy tables that no longer exist in the current schema. --- Do NOT rely on it for fresh deployments; use dedicated seed scripts instead. --- Migration: Add sample data for portfolio, blog, and services --- Created at: 2024-11-19 - --- Use existing admin or create sample admin -DO $$ -DECLARE - admin_id UUID; -BEGIN - -- Try to get existing admin - SELECT id INTO admin_id FROM admins WHERE username = 'admin' OR email = 'admin@example.com' LIMIT 1; - - -- If no admin exists, create one - IF admin_id IS NULL THEN - INSERT INTO admins (username, email, password_hash, role, is_active) - VALUES ('admin', 'admin@example.com', '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', 'admin', true) - RETURNING id INTO admin_id; - END IF; -END $$; - --- Insert sample project categories -INSERT INTO projects (slug, category, status, featured, order_index, demo_url, github_url, client_name, cover_path) VALUES -('ecommerce-platform', 'web-apps', 'active', true, 1, 'https://demo.example.com/ecommerce', 'https://github.com/example/ecommerce', 'TechCorp Sp. z o.o.', 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=Modern+ecommerce+platform+dashboard+with+product+management+interface+professional+web+design+clean+UI&image_size=landscape_16_9'), -('mobile-banking-app', 'mobile-apps', 'active', true, 2, 'https://demo.example.com/banking', 'https://github.com/example/banking-app', 'Bank Polski S.A.', 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=Mobile+banking+app+interface+modern+financial+application+design+secure+user+friendly&image_size=portrait_9_16'), -('ai-analytics-tool', 'tools', 'active', false, 3, 'https://demo.example.com/analytics', 'https://github.com/example/ai-analytics', 'DataFlow Inc.', 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=AI+analytics+dashboard+with+charts+and+data+visualization+professional+business+intelligence+interface&image_size=landscape_16_9'); - --- Insert project translations (Polish and English) -INSERT INTO project_translations (project_id, locale, title, short_description, description, seo_title, seo_description, seo_keywords) VALUES --- Polish translations -((SELECT id FROM projects WHERE slug = 'ecommerce-platform'), 'pl', 'Platforma E-commerce', 'Nowoczesna platforma e-commerce z zaawansowanymi funkcjami zarządzania', 'Platforma e-commerce stworzona dla TechCorp to kompleksowe rozwiązanie do prowadzenia biznesu online. System oferuje zaawansowane funkcje zarządzania produktami, zamówieniami, klientami oraz integrację z popularnymi systemami płatności. Wykorzystuje najnowsze technologie webowe zapewniające szybkość, bezpieczeństwo i skalowalność.', 'Platforma E-commerce - Nowoczesne rozwiązanie dla Twojego biznesu', 'Kompleksowa platforma e-commerce z zaawansowanymi funkcjami zarządzania produktami i zamówieniami', ARRAY['ecommerce', 'platforma', 'sklep internetowy', 'zarządzanie', 'TechCorp']), - -((SELECT id FROM projects WHERE slug = 'mobile-banking-app'), 'pl', 'Aplikacja Mobilnego Bankowości', 'Bezpieczna aplikacja mobilna do zarządzania finansami', 'Aplikacja mobilna stworzona dla Banku Polskiego to nowoczesne narzędzie do zarządzania finansami osobistymi. Zapewnia bezpieczny dostęp do konta, przelewy, płatności mobilne, zarządzanie kartami oraz zaawansowane funkcje analityczne. Interfejs został zaprojektowany z myślą o intuicyjności i bezpieczeństwie użytkowników.', 'Aplikacja Mobilnego Bankowości - Bezpieczne zarządzanie finansami', 'Nowoczesna aplikacja mobilna do zarządzania finansami osobistymi z zaawansowanymi funkcjami bezpieczeństwa', ARRAY['bankowość mobilna', 'aplikacja', 'finanse', 'bezpieczeństwo', 'Bank Polski']), - -((SELECT id FROM projects WHERE slug = 'ai-analytics-tool'), 'pl', 'Narzędzie AI do Analizy Danych', 'Zaawansowane narzędzie analityczne wykorzystujące sztuczną inteligencję', 'Narzędzie AI do analizy danych stworzone dla DataFlow Inc. to zaawansowana platforma analityczna wykorzystująca techniki uczenia maszynowego do przetwarzania i analizy dużych zbiorów danych. System oferuje predykcyjne modele, wizualizację danych, automatyczne raportowanie oraz integrację z popularnymi bazami danych.', 'Narzędzie AI do Analizy Danych - Sztuczna inteligencja w służbie biznesu', 'Zaawansowane narzędzie analityczne wykorzystujące AI do przetwarzania i analizy danych biznesowych', ARRAY['AI', 'analiza danych', 'sztuczna inteligencja', 'business intelligence', 'DataFlow']), - --- English translations -((SELECT id FROM projects WHERE slug = 'ecommerce-platform'), 'en', 'E-commerce Platform', 'Modern e-commerce platform with advanced management features', 'E-commerce platform created for TechCorp is a comprehensive solution for running online business. The system offers advanced product management, order processing, customer management and integration with popular payment systems. It utilizes the latest web technologies ensuring speed, security and scalability.', 'E-commerce Platform - Modern Solution for Your Business', 'Comprehensive e-commerce platform with advanced product and order management features', ARRAY['ecommerce', 'platform', 'online store', 'management', 'TechCorp']), - -((SELECT id FROM projects WHERE slug = 'mobile-banking-app'), 'en', 'Mobile Banking App', 'Secure mobile application for financial management', 'Mobile application created for Bank Polski is a modern tool for personal finance management. It provides secure account access, transfers, mobile payments, card management and advanced analytical features. The interface was designed with user intuition and security in mind.', 'Mobile Banking App - Secure Financial Management', 'Modern mobile application for personal finance management with advanced security features', ARRAY['mobile banking', 'application', 'finance', 'security', 'Bank Polski']), - -((SELECT id FROM projects WHERE slug = 'ai-analytics-tool'), 'en', 'AI Data Analytics Tool', 'Advanced analytical tool using artificial intelligence', 'AI data analytics tool created for DataFlow Inc. is an advanced analytical platform utilizing machine learning techniques for processing and analyzing large data sets. The system offers predictive models, data visualization, automated reporting and integration with popular databases.', 'AI Data Analytics Tool - Artificial Intelligence in Service of Business', 'Advanced analytical tool using AI for processing and analyzing business data', ARRAY['AI', 'data analytics', 'artificial intelligence', 'business intelligence', 'DataFlow']); - --- Insert project technologies -INSERT INTO project_technologies (project_id, name, color, icon, "order") VALUES -((SELECT id FROM projects WHERE slug = 'ecommerce-platform'), 'React', '#61DAFB', 'react', 1), -((SELECT id FROM projects WHERE slug = 'ecommerce-platform'), 'Node.js', '#339933', 'nodejs', 2), -((SELECT id FROM projects WHERE slug = 'ecommerce-platform'), 'PostgreSQL', '#4169E1', 'postgresql', 3), -((SELECT id FROM projects WHERE slug = 'ecommerce-platform'), 'Stripe', '#008CDD', 'stripe', 4), - -((SELECT id FROM projects WHERE slug = 'mobile-banking-app'), 'React Native', '#61DAFB', 'react', 1), -((SELECT id FROM projects WHERE slug = 'mobile-banking-app'), 'TypeScript', '#3178C6', 'typescript', 2), -((SELECT id FROM projects WHERE slug = 'mobile-banking-app'), 'Firebase', '#FFA000', 'firebase', 3), -((SELECT id FROM projects WHERE slug = 'mobile-banking-app'), 'Biometric Auth', '#4CAF50', 'fingerprint', 4), - -((SELECT id FROM projects WHERE slug = 'ai-analytics-tool'), 'Python', '#3776AB', 'python', 1), -((SELECT id FROM projects WHERE slug = 'ai-analytics-tool'), 'TensorFlow', '#FF6F00', 'tensorflow', 2), -((SELECT id FROM projects WHERE slug = 'ai-analytics-tool'), 'Docker', '#2496ED', 'docker', 3), -((SELECT id FROM projects WHERE slug = 'ai-analytics-tool'), 'AWS', '#FF9900', 'aws', 4); - --- Insert project images -INSERT INTO project_images (project_id, path, type, "order", alt_pl, alt_en) VALUES -((SELECT id FROM projects WHERE slug = 'ecommerce-platform'), 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=Ecommerce+platform+dashboard+interface+with+product+list+and+analytics+charts+modern+clean+design&image_size=landscape_16_9', 'screenshot', 1, 'Interfejs panelu administracyjnego platformy e-commerce', 'E-commerce platform admin dashboard interface'), -((SELECT id FROM projects WHERE slug = 'ecommerce-platform'), 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=Mobile+responsive+ecommerce+website+design+product+cart+checkout+process&image_size=portrait_9_16', 'mockup', 2, 'Wersja mobilna platformy e-commerce', 'Mobile version of e-commerce platform'), - -((SELECT id FROM projects WHERE slug = 'mobile-banking-app'), 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=Mobile+banking+app+login+screen+with+biometric+authentication+modern+financial+app+design&image_size=portrait_9_16', 'screenshot', 1, 'Ekran logowania aplikacji bankowej', 'Banking app login screen'), -((SELECT id FROM projects WHERE slug = 'mobile-banking-app'), 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=Banking+app+main+dashboard+with+account+balance+and+transaction+history+modern+UI&image_size=portrait_9_16', 'screenshot', 2, 'Główny panel aplikacji bankowej', 'Main dashboard of banking app'), - -((SELECT id FROM projects WHERE slug = 'ai-analytics-tool'), 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=AI+analytics+dashboard+with+machine+learning+charts+and+data+visualization+professional+business+interface&image_size=landscape_16_9', 'screenshot', 1, 'Panel analityczny narzędzia AI', 'AI analytics tool dashboard'), -((SELECT id FROM projects WHERE slug = 'ai-analytics-tool'), 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=Data+visualization+charts+and+graphs+AI+powered+analytics+platform&image_size=landscape_16_9', 'screenshot', 2, 'Wizualizacja danych w narzędziu AI', 'Data visualization in AI tool'); - --- Insert service categories -INSERT INTO service_categories (slug) VALUES -('web-development'), -('mobile-development'), -('consulting'); - --- Insert service category translations -INSERT INTO service_category_translations (category_id, language, name, description) VALUES -((SELECT id FROM service_categories WHERE slug = 'web-development'), 'pl', 'Tworzenie Stron WWW', 'Profesjonalne usługi tworzenia nowoczesnych stron internetowych i aplikacji webowych'), -((SELECT id FROM service_categories WHERE slug = 'web-development'), 'en', 'Web Development', 'Professional services for creating modern websites and web applications'), - -((SELECT id FROM service_categories WHERE slug = 'mobile-development'), 'pl', 'Aplikacje Mobilne', 'Tworzenie natywnych i hybrydowych aplikacji mobilnych dla iOS i Android'), -((SELECT id FROM service_categories WHERE slug = 'mobile-development'), 'en', 'Mobile Development', 'Creating native and hybrid mobile applications for iOS and Android'), - -((SELECT id FROM service_categories WHERE slug = 'consulting'), 'pl', 'Konsulting IT', 'Doradztwo technologiczne i strategiczne dla Twojego biznesu'), -((SELECT id FROM service_categories WHERE slug = 'consulting'), 'en', 'IT Consulting', 'Technology and strategic consulting for your business'); - --- Insert services -INSERT INTO services (slug, image_url, icon, price, price_unit, duration, is_featured, status, order_index) VALUES -('fullstack-web-development', 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=Modern+web+development+workspace+with+code+editor+and+website+preview+professional+setup&image_size=landscape_16_9', 'Code', 15000, 'PLN', '3-6 miesięcy', true, 'active', 1), -('mobile-app-development', 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=Mobile+app+development+environment+with+smartphone+mockups+and+development+tools&image_size=landscape_16_9', 'Smartphone', 25000, 'PLN', '4-8 miesięcy', true, 'active', 2), -('technical-consulting', 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=Business+consulting+meeting+with+technology+presentation+professional+office+setup&image_size=landscape_16_9', 'Briefcase', 500, 'PLN', '1-4 tygodnie', false, 'active', 3); - --- Insert service translations -INSERT INTO service_translations (service_id, language, title, description, short_description, features, meta_title, meta_description) VALUES --- Polish translations -((SELECT id FROM services WHERE slug = 'fullstack-web-development'), 'pl', 'Rozwój Full-Stack', 'Kompleksowe usługi tworzenia aplikacji webowych od podstaw do wdrożenia. Oferuję profesjonalne rozwiązania dopasowane do potrzeb Twojego biznesu.', 'Profesjonalne aplikacje webowe dopasowane do Twoich potrzeb', ARRAY['Projektowanie UI/UX', 'Frontend w React/Vue', 'Backend w Node.js/Python', 'Baza danych PostgreSQL/MySQL', 'Wdrożenie i hosting', 'Wsparcie techniczne'], 'Rozwój Full-Stack - Profesjonalne Aplikacje Webowe', 'Kompleksowe usługi tworzenia aplikacji webowych - od projektu do wdrożenia'), - -((SELECT id FROM services WHERE slug = 'mobile-app-development'), 'pl', 'Rozwój Aplikacji Mobilnych', 'Tworzenie natywnych i hybrydowych aplikacji mobilnych dla systemów iOS i Android. Nowoczesne rozwiązania zgodne z najnowszymi standardami.', 'Natywne i hybrydowe aplikacje mobilne', ARRAY['Aplikacje natywne iOS/Android', 'Aplikacje hybrydowe React Native', 'Interfejs użytkownika Material Design', 'Integracja z API', 'Publikacja w sklepach', 'Wsparcie po wdrożeniu'], 'Rozwój Aplikacji Mobilnych - iOS i Android', 'Profesjonalne aplikacje mobilne dla Twojego biznesu'), - -((SELECT id FROM services WHERE slug = 'technical-consulting'), 'pl', 'Konsulting Techniczny', 'Doradztwo technologiczne i strategiczne dla Twojego projektu. Pomagam w wyborze odpowiednich technologii i architektury systemu.', 'Doradztwo technologiczne dla Twojego projektu', ARRAY['Analiza wymagań', 'Wybór technologii', 'Architektura systemu', 'Optymalizacja wydajności', 'Bezpieczeństwo aplikacji', 'Dokumentacja techniczna'], 'Konsulting Techniczny - Doradztwo IT dla Biznesu', 'Profesjonalne doradztwo technologiczne i strategiczne'), - --- English translations -((SELECT id FROM services WHERE slug = 'fullstack-web-development'), 'en', 'Full-Stack Development', 'Comprehensive web application development services from scratch to deployment. I offer professional solutions tailored to your business needs.', 'Professional web applications tailored to your needs', ARRAY['UI/UX Design', 'Frontend in React/Vue', 'Backend in Node.js/Python', 'PostgreSQL/MySQL Database', 'Deployment and Hosting', 'Technical Support'], 'Full-Stack Development - Professional Web Applications', 'Comprehensive web application development services - from design to deployment'), - -((SELECT id FROM services WHERE slug = 'mobile-app-development'), 'en', 'Mobile App Development', 'Creating native and hybrid mobile applications for iOS and Android systems. Modern solutions compliant with the latest standards.', 'Native and hybrid mobile applications', ARRAY['Native iOS/Android Apps', 'Hybrid React Native Apps', 'Material Design UI', 'API Integration', 'Store Publishing', 'Post-deployment Support'], 'Mobile App Development - iOS and Android', 'Professional mobile applications for your business'), - -((SELECT id FROM services WHERE slug = 'technical-consulting'), 'en', 'Technical Consulting', 'Technology and strategic consulting for your project. I help in choosing appropriate technologies and system architecture.', 'Technology consulting for your project', ARRAY['Requirements Analysis', 'Technology Selection', 'System Architecture', 'Performance Optimization', 'Application Security', 'Technical Documentation'], 'Technical Consulting - IT Advisory for Business', 'Professional technology and strategic consulting'); - --- Assign services to categories -INSERT INTO service_category_assignments (service_id, category_id) VALUES -((SELECT id FROM services WHERE slug = 'fullstack-web-development'), (SELECT id FROM service_categories WHERE slug = 'web-development')), -((SELECT id FROM services WHERE slug = 'mobile-app-development'), (SELECT id FROM service_categories WHERE slug = 'mobile-development')), -((SELECT id FROM services WHERE slug = 'technical-consulting'), (SELECT id FROM service_categories WHERE slug = 'consulting')); - --- Insert blog categories -INSERT INTO blog_categories (slug, color) VALUES -('technology', '#3B82F6'), -('business', '#10B981'), -('development', '#8B5CF6'); - --- Insert blog category translations -INSERT INTO blog_category_translations (category_id, language, name, description) VALUES -((SELECT id FROM blog_categories WHERE slug = 'technology'), 'pl', 'Technologia', 'Artykuły o najnowszych technologiach i trendach IT'), -((SELECT id FROM blog_categories WHERE slug = 'technology'), 'en', 'Technology', 'Articles about latest technologies and IT trends'), - -((SELECT id FROM blog_categories WHERE slug = 'business'), 'pl', 'Biznes', 'Porady i wskazówki dla przedsiębiorców i firm'), -((SELECT id FROM blog_categories WHERE slug = 'business'), 'en', 'Business', 'Tips and advice for entrepreneurs and companies'), - -((SELECT id FROM blog_categories WHERE slug = 'development'), 'pl', 'Rozwój', 'Artykuły o rozwoju osobistym i zawodowym'), -((SELECT id FROM blog_categories WHERE slug = 'development'), 'en', 'Development', 'Articles about personal and professional development'); - --- Insert blog posts -INSERT INTO blog_posts (slug, author_id, featured_image, is_featured, status, published_at, reading_time, views_count, allow_comments) VALUES -('jak-wybrac-dobrego-developera', (SELECT id FROM admins LIMIT 1), 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=Developer+working+on+computer+in+modern+office+professional+programming+setup&image_size=landscape_16_9', true, 'published', NOW() - INTERVAL '10 days', 8, 125, true), -('trendy-w-web-development-2024', (SELECT id FROM admins LIMIT 1), 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=Modern+web+development+trends+2024+technology+interface+design&image_size=landscape_16_9', true, 'published', NOW() - INTERVAL '5 days', 12, 89, true), -('jak-rozwijac-swoja-firme-it', (SELECT id FROM admins LIMIT 1), 'https://trae-api-us.mchost.guru/api/ide/v1/text_to_image?prompt=Business+growth+strategy+meeting+IT+company+professional+office&image_size=landscape_16_9', false, 'published', NOW() - INTERVAL '2 days', 6, 67, true); - --- Insert blog post translations -INSERT INTO blog_post_translations (post_id, language, title, excerpt, content, meta_title, meta_description, meta_keywords, tags) VALUES --- Polish translations -((SELECT id FROM blog_posts WHERE slug = 'jak-wybrac-dobrego-developera'), 'pl', 'Jak Wybrać Dobrego Developera dla Twojego Projektu', 'Kompleksowy przewodnik po wyborze odpowiedniego developera dla Twojego projektu IT. Dowiedz się, na co zwrócić uwagę i jakie pytania zadać.', 'Wybór developera to kluczowa decyzja dla sukcesu Twojego projektu. W tym artykule podzielę się swoim doświadczeniem i podpowiem, na co zwrócić uwagę podczas rekrutacji. - -## Doświadczenie i Portfolio - -Pierwszą rzeczą, którą powinieneś sprawdzić, jest doświadczenie kandydata. Poproś o portfolio i dokładnie przejrzyj wcześniejsze projekty. Zwróć uwagę na: - -- **Skalę projektów** - czy developer pracował nad podobnymi projektami? -- **Technologie** - czy zna technologie, których potrzebujesz? -- **Jakość kodu** - czy projekty są dobrze zaprojektowane? - -## Umiejętności Techniczne - -### Frontend Developer -Dla frontend developera ważne są: -- Znajomość HTML, CSS, JavaScript -- Doświadczenie z frameworkami (React, Vue, Angular) -- Znajomość zasad UX/UI -- Umiejętność tworzenia responsywnych aplikacji - -### Backend Developer -Dla backend developera kluczowe są: -- Znajomość języków programowania (Python, Node.js, Java) -- Doświadczenie z bazami danych -- Znajomość architektury aplikacji -- Umiejętność tworzenia API - -## Miękkie Umiejętności - -Nie zapominaj o umiejętnościach miękkich: -- **Komunikacja** - czy potrafi jasno wyrażać myśli? -- **Praca w zespole** - czy współpracował z innymi? -- **Rozwiązywanie problemów** - jak podchodzi do wyzwań? -- **Samodzielność** - czy potrafi pracować samodzielnie? - -## Proces Wyboru - -### 1. Weryfikacja CV -Sprawdź: -- Spójność doświadczenia -- Postęp w karierze -- Rodzaje projektów - -### 2. Rozmowa Kwalifikacyjna -Przygotuj pytania: -- O konkretne sytuacje z przeszłości -- O podejście do rozwiązywania problemów -- O doświadczenie z różnymi technologiami - -### 3. Test Umiejętności -Daj zadanie praktyczne: -- Prosty projekt testowy -- Rozwiązywanie problemów -- Code review - -## Na Co Uważać - -### Ostrzeżenia -- **Niejasne portfolio** - brak konkretnych projektów -- **Zbyt szeroka wiedza** - specjalista od wszystkiego -- **Brak pytań** - nie interesuje się projektem -- **Negatywne referencje** - -### Pozytywne Sygnały -- **Zaangażowanie** - pyta o szczegóły projektu -- **Chęć nauki** - chce się rozwijać -- **Dobre referencje** - pozytywne opinie -- **Klarowna komunikacja** - -## Podsumowanie - -Wybór odpowiedniego developera wymaga czasu i dokładnej analizy. Nie spiesz się z decyzją - lepiej poczekać na odpowiednią osobę niż później żałować pośpiechu. - -Pamiętaj, że najtańsza opcja nie zawsze jest najlepsza. Inwestycja w dobrego developera zwróci się wielokrotnie w przyszłości.', 'Jak Wybrać Dobrego Developera - Przewodnik dla Przedsiębiorców', 'Kompletny przewodnik po wyborze odpowiedniego developera dla Twojego projektu IT', ARRAY['developer', 'rekrutacja', 'projekt IT', 'wybór developera', 'przewodnik'], ARRAY['rekrutacja', 'developer', 'projekt', 'IT']), - -((SELECT id FROM blog_posts WHERE slug = 'trendy-w-web-development-2024'), 'pl', 'Trendy w Web Development w 2024 Roku', 'Przegląd najważniejszych trendów w świecie web developmentu na rok 2024. Od nowych frameworków po zmieniające się standardy.', 'Rok 2024 przynosi fascynujące zmiany w świecie web developmentu. W tym artykule przyjrzymy się najważniejszym trendom, które będą kształtować branżę w nadchodzących miesiącach. - -## Sztuczna Inteligencja i Machine Learning - -AI nie jest już tylko buzzwordem - staje się integralną częścią nowoczesnych aplikacji webowych. - -### Generatywne AI w Web Dev -- **GitHub Copilot** i podobne narzędzia rewolucjonizują sposób pisania kodu -- **Automatyczne generowanie UI** na podstawie opisów tekstowych -- **AI-powered testing** automatyzujący proces testowania - -### Personalizacja w Czasie Rzeczywistym -- Dynamiczne dostosowywanie treści do użytkownika -- Predykcyjne ładowanie zasobów -- Inteligentne rekomendacje - -## Nowoczesne Frameworki i Narzędzia - -### React 19 i Nowe Funkcje -- **Server Components** stają się standardem -- **Automatic batching** poprawia wydajność -- **Nowe hooki** upraszczają zarządzanie stanem - -### Alternatywne Frameworki -- **Solid.js** zyskuje na popularności dzięki szybkości -- **Qwik** rewolucjonizuje podejście do hydration -- **Astro** idealny dla content-focused stron - -## WebAssembly (WASM) - -WebAssembly otwiera nowe możliwości: -- **Wysokowydajne aplikacje** w przeglądarce -- **Portowanie aplikacji desktopowych** do webu -- **Grafika 3D** i przetwarzanie wideo w czasie rzeczywistym - -## Progressive Web Apps (PWA) 2.0 - -PWA ewoluują: -- **Lepsza integracja z systemem** operacyjnym -- **Push notifications** stają się bardziej zaawansowane -- **Offline-first** podejście staje się standardem - -## Edge Computing - -Przetwarzanie na krawędzi sieci: -- **Vercel Edge Functions** i podobne rozwiązania -- **Redukcja opóźnień** w aplikacjach globalnych -- **Lepsza wydajność** dla użytkowników na całym świecie - -## Web3 i Blockchain - -Choć kontrowersyjne, Web3 przyciąga uwagę: -- **Decentralized applications (dApps)** -- **NFT integration** w aplikacjach -- **Smart contracts** w web development - -## Low-Code/No-Code - -Platformy low-code rosną w siłę: -- **Webflow** dla zaawansowanych stron -- **Bubble** dla aplikacji webowych -- **Retool** dla wewnętrznych narzędzi - -## Cyberbezpieczeństwo - -Bezpieczeństwo staje się priorytetem: -- **Zero-trust architecture** -- **Advanced authentication** metody -- **AI-powered threat detection** - -## Wydajność i Core Web Vitals - -Google kontynuuje nacisk na wydajność: -- **Core Web Vitals** wpływają na SEO -- **INP (Interaction to Next Paint)** nowym metrykiem -- **Sustainability** w web development - -## Podsumowanie - -Trendy w web development na 2024 rok koncentrują się na: -- **AI i automatyzacji** -- **Lepszej wydajności** -- **Bezpieczeństwie** -- **Dostępności** - -Najważniejsze jest, aby nie gonić za każdym trendem, ale wybrać te, które rzeczywiście przynoszą wartość Twoim użytkownikom i projektom.', 'Trendy w Web Development 2024 - Co Nas Czeka?', 'Przegląd najważniejszych trendów w web development na 2024 rok', ARRAY['web development', 'trendy 2024', 'AI', 'frameworki', 'technologie'], ARRAY['trendy', '2024', 'web development', 'AI', 'frameworki']), - -((SELECT id FROM blog_posts WHERE slug = 'jak-rozwijac-swoja-firme-it'), 'pl', 'Jak Rozwijać Swoją Firmę IT - Strategie na Sukces', 'Skuteczne strategie rozwoju firmy IT. Od pozyskiwania klientów po budowanie zespołu i skalowanie biznesu.', 'Rozwój firmy IT wymaga strategicznego podejścia i ciągłej adaptacji. W tym artykule podzielę się sprawdzonymi strategiami, które pomogą Ci z sukcesem rozwijać Twój biznes technologiczny. - -## Pozyskiwanie Klientów - -### 1. Marketing Cyfrowy -- **Content marketing** - blog, whitepapers, case studies -- **SEO i SEM** - optymalizacja dla wyszukiwarek -- **Social media** - LinkedIn, Twitter, GitHub -- **Email marketing** - newslettery, kampanie - -### 2. Networking i Relacje -- **Konferencje branżowe** - udział i prelekcje -- **Meetupy i wydarzenia** - lokalna społeczność -- **Partnerstwa strategiczne** - współpraca z innymi firmami -- **Referencje** - zadowoleni klienci jako ambasadorzy - -### 3. Specjalizacja i Nicha -- **Wybór specjalizacji** - konkretna technologia lub branża -- **Stworzenie USP** - Unique Selling Proposition -- **Budowanie ekspertyzy** - certyfikacje, szkolenia -- **Case studies** - udokumentowane sukcesy - -## Budowanie Zespołu - -### Rekrutacja Talentów -- **Jasne wymagania** - dokładne opisy stanowisk -- **Kultura firmy** - wartości i atmosfera -- **Pakiet benefitów** - konkurencyjne warunki -- **Rozwój kariery** - ścieżki awansu i szkolenia - -### Retencja Pracowników -- **Szkolenia i konferencje** - ciągły rozwój -- **Praca zdalna i elastyczność** - work-life balance -- **Recognition program** - docenianie osiągnięć -- **Team building** - integracja zespołu - -## Skalowanie Biznesu - -### Procesy i Systemy -- **Standaryzacja procesów** - powtarzalne procedury -- **Automatyzacja** - narzędzia i systemy -- **Quality assurance** - kontrola jakości -- **Project management** - zarządzanie projektami - -### Finansowe Aspekty -- **Zarządzanie cash flow** - przepływ środków -- **Inwestycje** - rozwój i ekspansja -- **Ceny i marże** - strategia cenowa -- **Diversyfikacja** - różnorodność usług - -## Technologie i Innowacje - -### Stay Ahead -- **Badania i rozwój** - innowacyjne projekty -- **Nowe technologie** - bycie na bieżąco -- **Inwestycje w narzędzia** - produktywność -- **Intellectual property** - własność intelektualna - -## Zarządzanie Relacjami z Klientami - -### Customer Success -- **Onboarding** - profesjonalne wdrożenie -- **Regularna komunikacja** - status i raporty -- **Support i maintenance** - wsparcie techniczne -- **Upselling** - rozszerzanie współpracy - -### Feedback i Ulepszenia -- **Regularne ankiety** - satysfakcja klientów -- **Review meetings** - przeglądy projektów -- **Continuous improvement** - ciągłe ulepszenia -- **Referencje i case studies** - dokumentacja sukcesów - -## Przeszkody i Wyzwania - -### Typowe Problemy -- **Brak klientów** - wymaga cierpliwości i strategii -- **Rotacja pracowników** - ważna kultura firmy -- **Konkurencja** - różnicowanie i wartość -- **Skalowanie** - kontrolowany wzrost - -### Rozwiązania -- **Plan B** - alternatywne strategie -- **Mentorzy i doradcy** - doświadczenie innych -- **Networking** - wsparcie społeczności -- **Ciągłe uczenie się** - adaptacja i rozwój - -## Metryki Sukcesu - -### Kluczowe Wskaźniki -- **MRR/ARR** - miesięczne/roczne przychody -- **Customer churn** - utrata klientów -- **Customer acquisition cost** - koszt pozyskania -- **Lifetime value** - wartość klienta -- **Employee satisfaction** - satysfakcja pracowników - -## Podsumowanie - -Rozwój firmy IT to maraton, nie sprint. Kluczowe jest: -- **Strategiczne planowanie** - długoterminowa wizja -- **Cierpliwość i wytrwałość** - konsekwencja w działaniu -- **Adaptacyjność** - elastyczność w zmianach -- **Focus na wartość** - dla klientów i pracowników - -Pamiętaj, że sukces nie przychodzi natychmiast - wymaga czasu, wysiłku i strategicznego podejścia. Bądź cierpliwy, konsekwentny i zawsze stawiaj na jakość.', 'Jak Rozwijać Firmę IT - Strategie i Wskazówki', 'Skuteczne strategie rozwoju firmy IT - od pozyskiwania klientów po skalowanie biznesu', ARRAY['firma IT', 'rozwój biznesu', 'strategie', 'pozyskiwanie klientów', 'skalowanie'], ARRAY['rozwój', 'firma IT', 'strategie', 'biznes', 'sukces']); - --- Assign blog posts to categories -INSERT INTO blog_post_categories (post_id, category_id, is_primary) VALUES -((SELECT id FROM blog_posts WHERE slug = 'jak-wybrac-dobrego-developera'), (SELECT id FROM blog_categories WHERE slug = 'technology'), true), -((SELECT id FROM blog_posts WHERE slug = 'trendy-w-web-development-2024'), (SELECT id FROM blog_categories WHERE slug = 'technology'), true), -((SELECT id FROM blog_posts WHERE slug = 'jak-rozwijac-swoja-firme-it'), (SELECT id FROM blog_categories WHERE slug = 'business'), true); - --- Insert sample comments for blog posts -INSERT INTO blog_comments (post_id, author_name, author_email, content, is_approved, created_at) VALUES -((SELECT id FROM blog_posts WHERE slug = 'jak-wybrac-dobrego-developera'), 'Anna Kowalska', 'anna@example.com', 'Bardzo przydatny artykuł! Właśnie szukam developera do mojego projektu i te wskazówki są bezcenne.', true, NOW() - INTERVAL '8 days'), -((SELECT id FROM blog_posts WHERE slug = 'jak-wybrac-dobrego-developera'), 'Piotr Nowak', 'piotr@example.com', 'Zgadzam się z punktem o portfolio - to naprawdę najważniejsze. Dzięki za świetny przewodnik!', true, NOW() - INTERVAL '7 days'), - -((SELECT id FROM blog_posts WHERE slug = 'trendy-w-web-development-2024'), 'Maria Wiśniewska', 'maria@example.com', 'AI naprawdę zmienia podejście do developmentu. Ciekawe jak te trendy będą się rozwijać dalej.', true, NOW() - INTERVAL '4 days'), -((SELECT id FROM blog_posts WHERE slug = 'trendy-w-web-development-2024'), 'Tomasz Lewandowski', 'tomasz@example.com', 'Super podsumowanie! WebAssembly to coś, co mnie szczególnie interesuje w tym roku.', true, NOW() - INTERVAL '3 days'), - -((SELECT id FROM blog_posts WHERE slug = 'jak-rozwijac-swoja-firme-it'), 'Katarzyna Wójcik', 'katarzyna@example.com', 'Właśnie zaczynam swoją firmę IT i te wskazówki są nieocenione. Dzięki!', true, NOW() - INTERVAL '1 day'); \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_add_test_data.sql b/supabase/migrations/_archive-pre-202603/20241119_add_test_data.sql deleted file mode 100644 index 2f1d755..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_add_test_data.sql +++ /dev/null @@ -1,52 +0,0 @@ --- MIGRATION TYPE: SEED DATA (historical) --- This migration inserts test/demo data for legacy tables that no longer exist --- in the current schema. It is kept only for migration history continuity. --- Add some test data for verification --- This will help test if the admin panel is working correctly - --- Insert a simple test project if none exist -INSERT INTO public.projects ( - id, slug, category, status, featured, order_index, demo_url, github_url, client_name -) VALUES ( - gen_random_uuid(), - 'test-project-2024', - 'websites', - 'active', - false, - 999, - 'https://example.com/test', - 'https://github.com/example/test', - 'Test Client' -) ON CONFLICT DO NOTHING; - --- Add translations for the test project -INSERT INTO public.project_translations (project_id, locale, title, short_description, description) -SELECT - id, - 'pl', - 'Projekt Testowy', - 'To jest projekt testowy do weryfikacji panelu admina', - 'Szczegółowy opis projektu testowego do weryfikacji funkcjonalności panelu administracyjnego.' -FROM public.projects WHERE slug = 'test-project-2024' -ON CONFLICT DO NOTHING; - -INSERT INTO public.project_translations (project_id, locale, title, short_description, description) -SELECT - id, - 'en', - 'Test Project', - 'This is a test project to verify the admin panel', - 'Detailed description of the test project to verify admin panel functionality.' -FROM public.projects WHERE slug = 'test-project-2024' -ON CONFLICT DO NOTHING; - --- Add a test technology -INSERT INTO public.project_technologies (project_id, name, color, icon, "order") -SELECT - id, - 'React', - '#61DAFB', - 'react', - 0 -FROM public.projects WHERE slug = 'test-project-2024' -ON CONFLICT DO NOTHING; \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_create_blog_tables.sql b/supabase/migrations/_archive-pre-202603/20241119_create_blog_tables.sql deleted file mode 100644 index f778827..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_create_blog_tables.sql +++ /dev/null @@ -1,201 +0,0 @@ --- Tworzenie tabeli postów bloga -CREATE TABLE IF NOT EXISTS public.blog_posts ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - slug VARCHAR(255) NOT NULL UNIQUE, - author_id UUID REFERENCES public.admins(id), - featured_image TEXT, - is_featured BOOLEAN DEFAULT FALSE, - status VARCHAR(20) DEFAULT 'draft' CHECK (status IN ('published', 'draft', 'archived')), - published_at TIMESTAMP WITH TIME ZONE, - reading_time INTEGER DEFAULT 0, -- czas czytania w minutach - views_count INTEGER DEFAULT 0, - allow_comments BOOLEAN DEFAULT TRUE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Tworzenie tabeli tłumaczeń dla postów bloga -CREATE TABLE IF NOT EXISTS public.blog_post_translations ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - post_id UUID NOT NULL REFERENCES public.blog_posts(id) ON DELETE CASCADE, - language VARCHAR(2) NOT NULL CHECK (language IN ('pl', 'en')), - title VARCHAR(255) NOT NULL, - excerpt VARCHAR(1000), -- krótki wstęp - content TEXT NOT NULL, - meta_title VARCHAR(255), - meta_description TEXT, - meta_keywords TEXT[], - tags VARCHAR(50)[], -- tagi dla posta - UNIQUE(post_id, language) -); - --- Tworzenie tabeli kategorii bloga -CREATE TABLE IF NOT EXISTS public.blog_categories ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - slug VARCHAR(255) NOT NULL UNIQUE, - color VARCHAR(7) DEFAULT '#3B82F6', -- kolor hex dla kategorii - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Tworzenie tabeli tłumaczeń dla kategorii bloga -CREATE TABLE IF NOT EXISTS public.blog_category_translations ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - category_id UUID NOT NULL REFERENCES public.blog_categories(id) ON DELETE CASCADE, - language VARCHAR(2) NOT NULL CHECK (language IN ('pl', 'en')), - name VARCHAR(255) NOT NULL, - description TEXT, - UNIQUE(category_id, language) -); - --- Tabela łącząca posty z kategoriami (many-to-many) -CREATE TABLE IF NOT EXISTS public.blog_post_categories ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - post_id UUID NOT NULL REFERENCES public.blog_posts(id) ON DELETE CASCADE, - category_id UUID NOT NULL REFERENCES public.blog_categories(id) ON DELETE CASCADE, - is_primary BOOLEAN DEFAULT FALSE, -- główna kategoria - UNIQUE(post_id, category_id) -); - --- Tworzenie tabeli komentarzy -CREATE TABLE IF NOT EXISTS public.blog_comments ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - post_id UUID NOT NULL REFERENCES public.blog_posts(id) ON DELETE CASCADE, - parent_id UUID REFERENCES public.blog_comments(id) ON DELETE CASCADE, - author_name VARCHAR(100) NOT NULL, - author_email VARCHAR(255) NOT NULL, - author_website VARCHAR(255), - content TEXT NOT NULL, - is_approved BOOLEAN DEFAULT FALSE, - ip_address INET, - user_agent TEXT, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Funkcja do automatycznego ustawiania published_at -CREATE OR REPLACE FUNCTION set_published_at() -RETURNS TRIGGER AS $$ -BEGIN - IF NEW.status = 'published' AND OLD.status != 'published' THEN - NEW.published_at = NOW(); - END IF; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Trigger dla automatycznego ustawiania published_at -CREATE TRIGGER trigger_set_published_at - BEFORE UPDATE ON public.blog_posts - FOR EACH ROW - EXECUTE FUNCTION set_published_at(); - --- Trigger dla updated_at -CREATE TRIGGER update_blog_posts_updated_at BEFORE UPDATE ON public.blog_posts - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - -CREATE TRIGGER update_blog_categories_updated_at BEFORE UPDATE ON public.blog_categories - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - -CREATE TRIGGER update_blog_comments_updated_at BEFORE UPDATE ON public.blog_comments - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - --- RLS dla tabeli postów bloga -ALTER TABLE public.blog_posts ENABLE ROW LEVEL SECURITY; - --- Polityki dla postów bloga -CREATE POLICY "Public can view published posts" ON public.blog_posts FOR SELECT - USING (status = 'published'); - -CREATE POLICY "Admin can manage all posts" ON public.blog_posts FOR ALL - USING (is_admin_user()); - --- RLS dla tłumaczeń postów -ALTER TABLE public.blog_post_translations ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "Public can view translations for published posts" ON public.blog_post_translations FOR SELECT - USING (EXISTS ( - SELECT 1 FROM public.blog_posts - WHERE blog_posts.id = blog_post_translations.post_id - AND blog_posts.status = 'published' - )); - -CREATE POLICY "Admin can manage post translations" ON public.blog_post_translations FOR ALL - USING (is_admin_user()); - --- RLS dla kategorii bloga -ALTER TABLE public.blog_categories ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "Public can view blog categories" ON public.blog_categories FOR SELECT - USING (true); - -CREATE POLICY "Admin can manage blog categories" ON public.blog_categories FOR ALL - USING (is_admin_user()); - --- RLS dla tłumaczeń kategorii bloga -ALTER TABLE public.blog_category_translations ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "Public can view blog category translations" ON public.blog_category_translations FOR SELECT - USING (true); - -CREATE POLICY "Admin can manage blog category translations" ON public.blog_category_translations FOR ALL - USING (is_admin_user()); - --- RLS dla przypisań kategorii -ALTER TABLE public.blog_post_categories ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "Public can view category assignments for published posts" ON public.blog_post_categories FOR SELECT - USING (EXISTS ( - SELECT 1 FROM public.blog_posts - WHERE blog_posts.id = blog_post_categories.post_id - AND blog_posts.status = 'published' - )); - -CREATE POLICY "Admin can manage post category assignments" ON public.blog_post_categories FOR ALL - USING (is_admin_user()); - --- RLS dla komentarzy -ALTER TABLE public.blog_comments ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "Public can view approved comments" ON public.blog_comments FOR SELECT - USING (is_approved = true AND EXISTS ( - SELECT 1 FROM public.blog_posts - WHERE blog_posts.id = blog_comments.post_id - AND blog_posts.status = 'published' - AND blog_posts.allow_comments = true - )); - -CREATE POLICY "Public can add comments" ON public.blog_comments FOR INSERT - WITH CHECK (EXISTS ( - SELECT 1 FROM public.blog_posts - WHERE blog_posts.id = blog_comments.post_id - AND blog_posts.status = 'published' - AND blog_posts.allow_comments = true - )); - -CREATE POLICY "Admin can manage all comments" ON public.blog_comments FOR ALL - USING (is_admin_user()); - --- Uprawnienia dla anonimowych użytkowników -GRANT SELECT ON public.blog_posts TO anon; -GRANT SELECT ON public.blog_post_translations TO anon; -GRANT SELECT ON public.blog_categories TO anon; -GRANT SELECT ON public.blog_category_translations TO anon; -GRANT SELECT ON public.blog_post_categories TO anon; -GRANT SELECT, INSERT ON public.blog_comments TO anon; - --- Uprawnienia dla zalogowanych użytkowników -GRANT SELECT ON public.blog_posts TO authenticated; -GRANT SELECT ON public.blog_post_translations TO authenticated; -GRANT SELECT ON public.blog_categories TO authenticated; -GRANT SELECT ON public.blog_category_translations TO authenticated; -GRANT SELECT ON public.blog_post_categories TO authenticated; -GRANT SELECT, INSERT ON public.blog_comments TO authenticated; - --- Uprawnienia dla admina -GRANT ALL ON public.blog_posts TO authenticated; -GRANT ALL ON public.blog_post_translations TO authenticated; -GRANT ALL ON public.blog_categories TO authenticated; -GRANT ALL ON public.blog_category_translations TO authenticated; -GRANT ALL ON public.blog_post_categories TO authenticated; -GRANT ALL ON public.blog_comments TO authenticated; \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_create_services_tables.sql b/supabase/migrations/_archive-pre-202603/20241119_create_services_tables.sql deleted file mode 100644 index 1f6a5ce..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_create_services_tables.sql +++ /dev/null @@ -1,158 +0,0 @@ --- Tworzenie tabeli usług -CREATE TABLE IF NOT EXISTS public.services ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - slug VARCHAR(255) NOT NULL UNIQUE, - image_url TEXT, - icon VARCHAR(100), - price DECIMAL(10,2), - price_unit VARCHAR(20) DEFAULT 'PLN', - duration VARCHAR(50), - is_featured BOOLEAN DEFAULT FALSE, - status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'draft')), - order_index INTEGER DEFAULT 0, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Tworzenie tabeli tłumaczeń dla usług -CREATE TABLE IF NOT EXISTS public.service_translations ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - service_id UUID NOT NULL REFERENCES public.services(id) ON DELETE CASCADE, - language VARCHAR(2) NOT NULL CHECK (language IN ('pl', 'en')), - title VARCHAR(255) NOT NULL, - description TEXT, - short_description VARCHAR(500), - features TEXT[], -- Array tekstowych feature'ów - meta_title VARCHAR(255), - meta_description TEXT, - UNIQUE(service_id, language) -); - --- Tworzenie tabeli kategorii usług -CREATE TABLE IF NOT EXISTS public.service_categories ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - slug VARCHAR(255) NOT NULL UNIQUE, - created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), - updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -); - --- Tworzenie tabeli tłumaczeń dla kategorii -CREATE TABLE IF NOT EXISTS public.service_category_translations ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - category_id UUID NOT NULL REFERENCES public.service_categories(id) ON DELETE CASCADE, - language VARCHAR(2) NOT NULL CHECK (language IN ('pl', 'en')), - name VARCHAR(255) NOT NULL, - description TEXT, - UNIQUE(category_id, language) -); - --- Tabela łącząca usługi z kategoriami (many-to-many) -CREATE TABLE IF NOT EXISTS public.service_category_assignments ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - service_id UUID NOT NULL REFERENCES public.services(id) ON DELETE CASCADE, - category_id UUID NOT NULL REFERENCES public.service_categories(id) ON DELETE CASCADE, - UNIQUE(service_id, category_id) -); - --- Tworzenie funkcji do aktualizacji updated_at -CREATE OR REPLACE FUNCTION update_updated_at_column() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = NOW(); - RETURN NEW; -END; -$$ language 'plpgsql'; - --- Dodanie triggerów dla updated_at -CREATE TRIGGER update_services_updated_at BEFORE UPDATE ON public.services - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - -CREATE TRIGGER update_service_categories_updated_at BEFORE UPDATE ON public.service_categories - FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); - --- Funkcja pomocnicza do sprawdzania admina -CREATE OR REPLACE FUNCTION is_admin_user() -RETURNS BOOLEAN AS $$ -BEGIN - RETURN EXISTS ( - SELECT 1 FROM public.admins - WHERE (admins.email = auth.email() OR admins.username = auth.email()) - AND admins.is_active = true - ); -END; -$$ LANGUAGE plpgsql SECURITY DEFINER; - --- RLS dla tabeli usług -ALTER TABLE public.services ENABLE ROW LEVEL SECURITY; - --- Polityki dla usług -CREATE POLICY "Public can view active services" ON public.services FOR SELECT - USING (status = 'active'); - -CREATE POLICY "Admin can manage services" ON public.services FOR ALL - USING (is_admin_user()); - --- RLS dla tłumaczeń usług -ALTER TABLE public.service_translations ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "Public can view service translations" ON public.service_translations FOR SELECT - USING (EXISTS ( - SELECT 1 FROM public.services - WHERE services.id = service_translations.service_id - AND services.status = 'active' - )); - -CREATE POLICY "Admin can manage service translations" ON public.service_translations FOR ALL - USING (is_admin_user()); - --- RLS dla kategorii -ALTER TABLE public.service_categories ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "Public can view categories" ON public.service_categories FOR SELECT - USING (true); - -CREATE POLICY "Admin can manage categories" ON public.service_categories FOR ALL - USING (is_admin_user()); - --- RLS dla tłumaczeń kategorii -ALTER TABLE public.service_category_translations ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "Public can view category translations" ON public.service_category_translations FOR SELECT - USING (true); - -CREATE POLICY "Admin can manage category translations" ON public.service_category_translations FOR ALL - USING (is_admin_user()); - --- RLS dla przypisań kategorii -ALTER TABLE public.service_category_assignments ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "Public can view category assignments" ON public.service_category_assignments FOR SELECT - USING (EXISTS ( - SELECT 1 FROM public.services - WHERE services.id = service_category_assignments.service_id - AND services.status = 'active' - )); - -CREATE POLICY "Admin can manage category assignments" ON public.service_category_assignments FOR ALL - USING (is_admin_user()); - --- Uprawnienia dla anonimowych użytkowników -GRANT SELECT ON public.services TO anon; -GRANT SELECT ON public.service_translations TO anon; -GRANT SELECT ON public.service_categories TO anon; -GRANT SELECT ON public.service_category_translations TO anon; -GRANT SELECT ON public.service_category_assignments TO anon; - --- Uprawnienia dla zalogowanych użytkowników -GRANT SELECT ON public.services TO authenticated; -GRANT SELECT ON public.service_translations TO authenticated; -GRANT SELECT ON public.service_categories TO authenticated; -GRANT SELECT ON public.service_category_translations TO authenticated; -GRANT SELECT ON public.service_category_assignments TO authenticated; - --- Uprawnienia dla admina -GRANT ALL ON public.services TO authenticated; -GRANT ALL ON public.service_translations TO authenticated; -GRANT ALL ON public.service_categories TO authenticated; -GRANT ALL ON public.service_category_translations TO authenticated; -GRANT ALL ON public.service_category_assignments TO authenticated; \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_create_storage_buckets.sql b/supabase/migrations/_archive-pre-202603/20241119_create_storage_buckets.sql deleted file mode 100644 index d7fdc36..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_create_storage_buckets.sql +++ /dev/null @@ -1,30 +0,0 @@ --- Tworzenie bucketów dla storage jeśli nie istnieją -INSERT INTO storage.buckets (id, name, public, file_size_limit, allowed_mime_types) VALUES -('portfolio', 'portfolio', true, 5242880, ARRAY['image/jpeg', 'image/png', 'image/webp', 'image/gif']), -('blog', 'blog', true, 5242880, ARRAY['image/jpeg', 'image/png', 'image/webp', 'image/gif']), -('services', 'services', true, 5242880, ARRAY['image/jpeg', 'image/png', 'image/webp', 'image/gif']) -ON CONFLICT (id) DO UPDATE SET - public = EXCLUDED.public, - file_size_limit = EXCLUDED.file_size_limit, - allowed_mime_types = EXCLUDED.allowed_mime_types; - --- Polityki dla bucketów portfolio -CREATE POLICY "Public access to portfolio bucket" ON storage.objects FOR SELECT -USING (bucket_id = 'portfolio'); - -CREATE POLICY "Admin can manage portfolio bucket" ON storage.objects FOR ALL -USING (bucket_id = 'portfolio'); - --- Polityki dla bucketów blog -CREATE POLICY "Public access to blog bucket" ON storage.objects FOR SELECT -USING (bucket_id = 'blog'); - -CREATE POLICY "Admin can manage blog bucket" ON storage.objects FOR ALL -USING (bucket_id = 'blog'); - --- Polityki dla bucketów services -CREATE POLICY "Public access to services bucket" ON storage.objects FOR SELECT -USING (bucket_id = 'services'); - -CREATE POLICY "Admin can manage services bucket" ON storage.objects FOR ALL -USING (bucket_id = 'services'); \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_fix_admin_rls_policies.sql b/supabase/migrations/_archive-pre-202603/20241119_fix_admin_rls_policies.sql deleted file mode 100644 index eb107a7..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_fix_admin_rls_policies.sql +++ /dev/null @@ -1,65 +0,0 @@ --- Fix RLS policies to work with custom admin authentication system --- The admin authentication uses RPC and session storage, not Supabase Auth - --- Create a function to check if current user is an admin based on session -CREATE OR REPLACE FUNCTION is_admin_user() -RETURNS BOOLEAN -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -BEGIN - -- For now, we'll use a simplified approach - -- Since we can't access session storage from RLS, we'll use a different approach - -- We'll create a temporary solution that allows admin operations - RETURN TRUE; -END; -$$; - --- Simplify RLS policies for admin operations --- Since admin auth is handled client-side, we'll use a different approach - --- Drop existing complex policies -DROP POLICY IF EXISTS "Admin can view all projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can manage projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can manage translations" ON public.project_translations; -DROP POLICY IF EXISTS "Admin can manage images" ON public.project_images; -DROP POLICY IF EXISTS "Admin can manage technologies" ON public.project_technologies; -DROP POLICY IF EXISTS "Admin can manage portfolio files" ON storage.objects; - --- Create simplified admin policies that work with client-side auth -CREATE POLICY "Admin can view all projects" ON public.projects FOR SELECT - USING (is_admin_user()); - -CREATE POLICY "Admin can manage all projects" ON public.projects FOR ALL - USING (is_admin_user()) - WITH CHECK (is_admin_user()); - -CREATE POLICY "Admin can manage translations" ON public.project_translations FOR ALL - USING (is_admin_user()) - WITH CHECK (is_admin_user()); - -CREATE POLICY "Admin can manage images" ON public.project_images FOR ALL - USING (is_admin_user()) - WITH CHECK (is_admin_user()); - -CREATE POLICY "Admin can manage technologies" ON public.project_technologies FOR ALL - USING (is_admin_user()) - WITH CHECK (is_admin_user()); - -CREATE POLICY "Admin can manage portfolio files" ON storage.objects FOR ALL - USING (is_admin_user()) - WITH CHECK (is_admin_user()); - --- Grant permissions to anon role for admin operations (temporary solution) --- This allows the admin RPC to work properly -GRANT ALL ON public.projects TO anon; -GRANT ALL ON public.project_translations TO anon; -GRANT ALL ON public.project_images TO anon; -GRANT ALL ON public.project_technologies TO anon; -GRANT ALL ON storage.objects TO anon; - --- Grant usage on sequences -GRANT USAGE ON SEQUENCE public.projects_id_seq TO anon; -GRANT USAGE ON SEQUENCE public.project_translations_id_seq TO anon; -GRANT USAGE ON SEQUENCE public.project_images_id_seq TO anon; -GRANT USAGE ON SEQUENCE public.project_technologies_id_seq TO anon; \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_fix_admin_rls_policies_simple.sql b/supabase/migrations/_archive-pre-202603/20241119_fix_admin_rls_policies_simple.sql deleted file mode 100644 index a4a5b4a..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_fix_admin_rls_policies_simple.sql +++ /dev/null @@ -1,66 +0,0 @@ --- Fix RLS policies to work with custom admin authentication system --- The admin authentication uses RPC and session storage, not Supabase Auth - --- Create a function to check if current user is an admin based on session -CREATE OR REPLACE FUNCTION is_admin_user() -RETURNS BOOLEAN -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -BEGIN - -- For now, we'll use a simplified approach - -- Since we can't access session storage from RLS, we'll use a different approach - -- We'll create a temporary solution that allows admin operations - RETURN TRUE; -END; -$$; - --- Simplify RLS policies for admin operations --- Since admin auth is handled client-side, we'll use a different approach - --- Drop existing complex policies -DROP POLICY IF EXISTS "Admin can view all projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can manage projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can manage translations" ON public.project_translations; -DROP POLICY IF EXISTS "Admin can manage images" ON public.project_images; -DROP POLICY IF EXISTS "Admin can manage technologies" ON public.project_technologies; -DROP POLICY IF EXISTS "Admin can manage portfolio files" ON storage.objects; - --- Create simplified admin policies that work with client-side auth -CREATE POLICY "Admin can view all projects" ON public.projects FOR SELECT - USING (is_admin_user()); - -CREATE POLICY "Admin can manage all projects" ON public.projects FOR ALL - USING (is_admin_user()) - WITH CHECK (is_admin_user()); - -CREATE POLICY "Admin can manage translations" ON public.project_translations FOR ALL - USING (is_admin_user()) - WITH CHECK (is_admin_user()); - -CREATE POLICY "Admin can manage images" ON public.project_images FOR ALL - USING (is_admin_user()) - WITH CHECK (is_admin_user()); - -CREATE POLICY "Admin can manage technologies" ON public.project_technologies FOR ALL - USING (is_admin_user()) - WITH CHECK (is_admin_user()); - -CREATE POLICY "Admin can manage portfolio files" ON storage.objects FOR ALL - USING (is_admin_user()) - WITH CHECK (is_admin_user()); - --- Grant permissions to anon role for admin operations (temporary solution) --- This allows the admin RPC to work properly -GRANT ALL ON public.projects TO anon; -GRANT ALL ON public.project_translations TO anon; -GRANT ALL ON public.project_images TO anon; -GRANT ALL ON public.project_technologies TO anon; -GRANT ALL ON storage.objects TO anon; - --- Grant permissions to authenticated role as well -GRANT ALL ON public.projects TO authenticated; -GRANT ALL ON public.project_translations TO authenticated; -GRANT ALL ON public.project_images TO authenticated; -GRANT ALL ON public.project_technologies TO authenticated; -GRANT ALL ON storage.objects TO authenticated; \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_fix_portfolio_permissions.sql b/supabase/migrations/_archive-pre-202603/20241119_fix_portfolio_permissions.sql deleted file mode 100644 index 0870fa2..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_fix_portfolio_permissions.sql +++ /dev/null @@ -1,108 +0,0 @@ --- Fix portfolio permissions for anon and authenticated roles - --- Grant basic permissions to anon role for reading -GRANT SELECT ON public.projects TO anon; -GRANT SELECT ON public.project_translations TO anon; -GRANT SELECT ON public.project_images TO anon; -GRANT SELECT ON public.project_technologies TO anon; - --- Grant full permissions to authenticated role -GRANT ALL ON public.projects TO authenticated; -GRANT ALL ON public.project_translations TO authenticated; -GRANT ALL ON public.project_images TO authenticated; -GRANT ALL ON public.project_technologies TO authenticated; -GRANT ALL ON public.audit_logs TO authenticated; - --- Update RLS policies to work properly with anon role --- Drop existing policies that reference non-existent admins table structure -DROP POLICY IF EXISTS "Public can view active projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can view all projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can insert projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can update projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can delete projects" ON public.projects; - -DROP POLICY IF EXISTS "Public can view translations for active projects" ON public.project_translations; -DROP POLICY IF EXISTS "Admin can manage translations" ON public.project_translations; - -DROP POLICY IF EXISTS "Public can view images for active projects" ON public.project_images; -DROP POLICY IF EXISTS "Admin can manage images" ON public.project_images; - -DROP POLICY IF EXISTS "Public can view technologies for active projects" ON public.project_technologies; -DROP POLICY IF EXISTS "Admin can manage technologies" ON public.project_technologies; - --- Create new simplified policies that work with anon role -CREATE POLICY "Public can view active projects" ON public.projects FOR SELECT - USING (status = 'active'); - -CREATE POLICY "Public can view translations for active projects" ON public.project_translations FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_translations.project_id AND projects.status = 'active' - ) - ); - -CREATE POLICY "Public can view images for active projects" ON public.project_images FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_images.project_id AND projects.status = 'active' - ) - ); - -CREATE POLICY "Public can view technologies for active projects" ON public.project_technologies FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_technologies.project_id AND projects.status = 'active' - ) - ); - --- Create admin policies (these will work with the existing admins table) -CREATE POLICY "Admin can view all projects" ON public.projects FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.username = auth.jwt() ->> 'email' AND admins.is_active = true - ) - ); - -CREATE POLICY "Admin can manage projects" ON public.projects FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.username = auth.jwt() ->> 'email' AND admins.is_active = true - ) - ); - -CREATE POLICY "Admin can manage translations" ON public.project_translations FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.username = auth.jwt() ->> 'email' AND admins.is_active = true - ) - ); - -CREATE POLICY "Admin can manage images" ON public.project_images FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.username = auth.jwt() ->> 'email' AND admins.is_active = true - ) - ); - -CREATE POLICY "Admin can manage technologies" ON public.project_technologies FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.username = auth.jwt() ->> 'email' AND admins.is_active = true - ) - ); - -CREATE POLICY "Admin can manage audit logs" ON public.audit_logs FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.username = auth.jwt() ->> 'email' AND admins.is_active = true - ) - ); \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_fix_portfolio_rls_policies.sql b/supabase/migrations/_archive-pre-202603/20241119_fix_portfolio_rls_policies.sql deleted file mode 100644 index 108de6e..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_fix_portfolio_rls_policies.sql +++ /dev/null @@ -1,147 +0,0 @@ --- Drop existing complex RLS policies -DROP POLICY IF EXISTS "Public can view active projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can view all projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can insert projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can update projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can delete projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can manage projects" ON public.projects; - -DROP POLICY IF EXISTS "Public can view translations for active projects" ON public.project_translations; -DROP POLICY IF EXISTS "Admin can manage translations" ON public.project_translations; - -DROP POLICY IF EXISTS "Public can view images for active projects" ON public.project_images; -DROP POLICY IF EXISTS "Admin can manage images" ON public.project_images; - -DROP POLICY IF EXISTS "Public can view technologies for active projects" ON public.project_technologies; -DROP POLICY IF EXISTS "Admin can manage technologies" ON public.project_technologies; - --- Create simplified RLS policies based on admin authentication --- Allow public read access to active projects -CREATE POLICY "Public can view active projects" ON public.projects FOR SELECT - USING (status = 'active'); - --- Admins can view all projects (including non-active) - check by email or username -CREATE POLICY "Admin can view all projects" ON public.projects FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE (admins.email = auth.email() OR admins.username = auth.email()) - AND admins.is_active = true - ) - ); - --- Allow admin users to manage all projects -CREATE POLICY "Admin can manage projects" ON public.projects FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE (admins.email = auth.email() OR admins.username = auth.email()) - AND admins.is_active = true - ) - ) - WITH CHECK ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE (admins.email = auth.email() OR admins.username = auth.email()) - AND admins.is_active = true - ) - ); - --- Allow public read access to translations of active projects -CREATE POLICY "Public can view translations for active projects" ON public.project_translations FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_translations.project_id AND projects.status = 'active' - ) - ); - --- Allow admin users to manage all translations -CREATE POLICY "Admin can manage translations" ON public.project_translations FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE (admins.email = auth.email() OR admins.username = auth.email()) - AND admins.is_active = true - ) - ) - WITH CHECK ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE (admins.email = auth.email() OR admins.username = auth.email()) - AND admins.is_active = true - ) - ); - --- Allow public read access to images of active projects -CREATE POLICY "Public can view images for active projects" ON public.project_images FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_images.project_id AND projects.status = 'active' - ) - ); - --- Allow admin users to manage all images -CREATE POLICY "Admin can manage images" ON public.project_images FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE (admins.email = auth.email() OR admins.username = auth.email()) - AND admins.is_active = true - ) - ) - WITH CHECK ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE (admins.email = auth.email() OR admins.username = auth.email()) - AND admins.is_active = true - ) - ); - --- Allow public read access to technologies of active projects -CREATE POLICY "Public can view technologies for active projects" ON public.project_technologies FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_technologies.project_id AND projects.status = 'active' - ) - ); - --- Allow admin users to manage all technologies -CREATE POLICY "Admin can manage technologies" ON public.project_technologies FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE (admins.email = auth.email() OR admins.username = auth.email()) - AND admins.is_active = true - ) - ) - WITH CHECK ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE (admins.email = auth.email() OR admins.username = auth.email()) - AND admins.is_active = true - ) - ); - --- Simplify storage policies for 'portfolio' bucket -DROP POLICY IF EXISTS "Authenticated can manage portfolio files" ON storage.objects; -DROP POLICY IF EXISTS "Admin can manage portfolio files" ON storage.objects; - -CREATE POLICY "Admin can manage portfolio files" ON storage.objects - FOR ALL TO authenticated - USING ( - bucket_id = 'portfolio' AND EXISTS ( - SELECT 1 FROM public.admins - WHERE (admins.email = auth.email() OR admins.username = auth.email()) - AND admins.is_active = true - ) - ) - WITH CHECK ( - bucket_id = 'portfolio' AND EXISTS ( - SELECT 1 FROM public.admins - WHERE (admins.email = auth.email() OR admins.username = auth.email()) - AND admins.is_active = true - ) - ); \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_fix_rls_42501_error.sql b/supabase/migrations/_archive-pre-202603/20241119_fix_rls_42501_error.sql deleted file mode 100644 index f8a800a..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_fix_rls_42501_error.sql +++ /dev/null @@ -1,195 +0,0 @@ --- Fix RLS 42501 error by creating admin function first --- This migration creates a simple admin check function and applies it to all tables - --- Create admin check function -CREATE OR REPLACE FUNCTION public.is_admin_user() -RETURNS BOOLEAN AS $$ -BEGIN - RETURN EXISTS ( - SELECT 1 FROM public.admins - WHERE (admins.email = auth.email() OR admins.username = auth.email()) - AND admins.is_active = true - ); -END; -$$ LANGUAGE plpgsql SECURITY DEFINER; - --- Grant execute permission to authenticated users -GRANT EXECUTE ON FUNCTION public.is_admin_user() TO authenticated; - --- Simplify RLS policies for projects table -DROP POLICY IF EXISTS "Public can view active projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can view all projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can manage projects" ON public.projects; - -CREATE POLICY "Public can view active projects" ON public.projects FOR SELECT - USING (status = 'active'); - -CREATE POLICY "Admin can manage projects" ON public.projects FOR ALL - USING (public.is_admin_user()) - WITH CHECK (public.is_admin_user()); - --- Simplify RLS policies for project_translations table -DROP POLICY IF EXISTS "Public can view translations for active projects" ON public.project_translations; -DROP POLICY IF EXISTS "Admin can manage translations" ON public.project_translations; - -CREATE POLICY "Public can view translations for active projects" ON public.project_translations FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_translations.project_id AND projects.status = 'active' - ) - ); - -CREATE POLICY "Admin can manage translations" ON public.project_translations FOR ALL - USING (public.is_admin_user()) - WITH CHECK (public.is_admin_user()); - --- Simplify RLS policies for project_images table -DROP POLICY IF EXISTS "Public can view images for active projects" ON public.project_images; -DROP POLICY IF EXISTS "Admin can manage images" ON public.project_images; - -CREATE POLICY "Public can view images for active projects" ON public.project_images FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_images.project_id AND projects.status = 'active' - ) - ); - -CREATE POLICY "Admin can manage images" ON public.project_images FOR ALL - USING (public.is_admin_user()) - WITH CHECK (public.is_admin_user()); - --- Simplify RLS policies for project_technologies table -DROP POLICY IF EXISTS "Public can view technologies for active projects" ON public.project_technologies; -DROP POLICY IF EXISTS "Admin can manage technologies" ON public.project_technologies; - -CREATE POLICY "Public can view technologies for active projects" ON public.project_technologies FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_technologies.project_id AND projects.status = 'active' - ) - ); - -CREATE POLICY "Admin can manage technologies" ON public.project_technologies FOR ALL - USING (public.is_admin_user()) - WITH CHECK (public.is_admin_user()); - --- Simplify RLS policies for services table -DROP POLICY IF EXISTS "Public can view active services" ON public.services; -DROP POLICY IF EXISTS "Admin can manage services" ON public.services; - -CREATE POLICY "Public can view active services" ON public.services FOR SELECT - USING (status = 'active'); - -CREATE POLICY "Admin can manage services" ON public.services FOR ALL - USING (public.is_admin_user()) - WITH CHECK (public.is_admin_user()); - --- Simplify RLS policies for service_translations table -DROP POLICY IF EXISTS "Public can view translations for active services" ON public.service_translations; -DROP POLICY IF EXISTS "Admin can manage service translations" ON public.service_translations; - -CREATE POLICY "Public can view translations for active services" ON public.service_translations FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.services - WHERE services.id = service_translations.service_id AND services.status = 'active' - ) - ); - -CREATE POLICY "Admin can manage service translations" ON public.service_translations FOR ALL - USING (public.is_admin_user()) - WITH CHECK (public.is_admin_user()); - --- Simplify RLS policies for blog_posts table -DROP POLICY IF EXISTS "Public can view published posts" ON public.blog_posts; -DROP POLICY IF EXISTS "Admin can manage posts" ON public.blog_posts; - -CREATE POLICY "Public can view published posts" ON public.blog_posts FOR SELECT - USING (status = 'published' AND published_at <= NOW()); - -CREATE POLICY "Admin can manage posts" ON public.blog_posts FOR ALL - USING (public.is_admin_user()) - WITH CHECK (public.is_admin_user()); - --- Simplify RLS policies for blog_translations table -DROP POLICY IF EXISTS "Public can view translations for published posts" ON public.blog_translations; -DROP POLICY IF EXISTS "Admin can manage blog translations" ON public.blog_translations; - -CREATE POLICY "Public can view translations for published posts" ON public.blog_translations FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.blog_posts - WHERE blog_posts.id = blog_translations.post_id AND blog_posts.status = 'published' AND blog_posts.published_at <= NOW() - ) - ); - -CREATE POLICY "Admin can manage blog translations" ON public.blog_translations FOR ALL - USING (public.is_admin_user()) - WITH CHECK (public.is_admin_user()); - --- Simplify RLS policies for blog_categories table -DROP POLICY IF EXISTS "Public can view categories" ON public.blog_categories; -DROP POLICY IF EXISTS "Admin can manage categories" ON public.blog_categories; - -CREATE POLICY "Public can view categories" ON public.blog_categories FOR SELECT - USING (true); - -CREATE POLICY "Admin can manage categories" ON public.blog_categories FOR ALL - USING (public.is_admin_user()) - WITH CHECK (public.is_admin_user()); - --- Simplify RLS policies for blog_comments table -DROP POLICY IF EXISTS "Public can view approved comments" ON public.blog_comments; -DROP POLICY IF EXISTS "Users can create comments" ON public.blog_comments; -DROP POLICY IF EXISTS "Admin can manage comments" ON public.blog_comments; - -CREATE POLICY "Public can view approved comments" ON public.blog_comments FOR SELECT - USING (status = 'approved'); - -CREATE POLICY "Users can create comments" ON public.blog_comments FOR INSERT - WITH CHECK (auth.role() = 'authenticated' AND status = 'pending'); - -CREATE POLICY "Admin can manage comments" ON public.blog_comments FOR ALL - USING (public.is_admin_user()) - WITH CHECK (public.is_admin_user()); - --- Simplify storage policies -DROP POLICY IF EXISTS "Admin can manage portfolio files" ON storage.objects; -DROP POLICY IF EXISTS "Admin can manage service files" ON storage.objects; -DROP POLICY IF EXISTS "Admin can manage blog files" ON storage.objects; - -CREATE POLICY "Admin can manage storage files" ON storage.objects - FOR ALL TO authenticated - USING ( - bucket_id IN ('portfolio', 'services', 'blog') AND public.is_admin_user() - ) - WITH CHECK ( - bucket_id IN ('portfolio', 'services', 'blog') AND public.is_admin_user() - ); - --- Grant necessary permissions -GRANT SELECT ON public.projects TO anon; -GRANT SELECT ON public.project_translations TO anon; -GRANT SELECT ON public.project_images TO anon; -GRANT SELECT ON public.project_technologies TO anon; -GRANT SELECT ON public.services TO anon; -GRANT SELECT ON public.service_translations TO anon; -GRANT SELECT ON public.blog_posts TO anon; -GRANT SELECT ON public.blog_translations TO anon; -GRANT SELECT ON public.blog_categories TO anon; -GRANT SELECT ON public.blog_comments TO anon; -GRANT INSERT ON public.blog_comments TO authenticated; - -GRANT ALL ON public.projects TO authenticated; -GRANT ALL ON public.project_translations TO authenticated; -GRANT ALL ON public.project_images TO authenticated; -GRANT ALL ON public.project_technologies TO authenticated; -GRANT ALL ON public.services TO authenticated; -GRANT ALL ON public.service_translations TO authenticated; -GRANT ALL ON public.blog_posts TO authenticated; -GRANT ALL ON public.blog_translations TO authenticated; -GRANT ALL ON public.blog_categories TO authenticated; -GRANT ALL ON public.blog_comments TO authenticated; \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_fix_rls_admin_function.sql b/supabase/migrations/_archive-pre-202603/20241119_fix_rls_admin_function.sql deleted file mode 100644 index 33f29d7..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_fix_rls_admin_function.sql +++ /dev/null @@ -1,80 +0,0 @@ --- Funkcja do sprawdzania czy użytkownik jest adminem -CREATE OR REPLACE FUNCTION public.is_admin_user() -RETURNS BOOLEAN -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -BEGIN - RETURN EXISTS ( - SELECT 1 - FROM public.admins - WHERE (email = auth.email() OR username = auth.email()) - AND is_active = true - ); -END; -$$; - --- Uprawnienia dla funkcji -GRANT EXECUTE ON FUNCTION public.is_admin_user() TO anon, authenticated; - --- Aktualizacja RLS policies dla tabeli projects -DROP POLICY IF EXISTS "Admin can manage projects" ON public.projects; -DROP POLICY IF EXISTS "Public can view published projects" ON public.projects; - -CREATE POLICY "Admin can manage projects" ON public.projects FOR ALL -USING (public.is_admin_user()) -WITH CHECK (public.is_admin_user()); - -CREATE POLICY "Public can view published projects" ON public.projects FOR SELECT -USING (status = 'published'); - --- Aktualizacja RLS policies dla tabeli project_translations -DROP POLICY IF EXISTS "Admin can manage project translations" ON public.project_translations; -DROP POLICY IF EXISTS "Public can view project translations" ON public.project_translations; - -CREATE POLICY "Admin can manage project translations" ON public.project_translations FOR ALL -USING (public.is_admin_user()) -WITH CHECK (public.is_admin_user()); - -CREATE POLICY "Public can view project translations" ON public.project_translations FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_translations.project_id - AND projects.status = 'published' - ) -); - --- Aktualizacja RLS policies dla tabeli project_images -DROP POLICY IF EXISTS "Admin can manage project images" ON public.project_images; -DROP POLICY IF EXISTS "Public can view project images" ON public.project_images; - -CREATE POLICY "Admin can manage project images" ON public.project_images FOR ALL -USING (public.is_admin_user()) -WITH CHECK (public.is_admin_user()); - -CREATE POLICY "Public can view project images" ON public.project_images FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_images.project_id - AND projects.status = 'published' - ) -); - --- Aktualizacja RLS policies dla tabeli project_technologies -DROP POLICY IF EXISTS "Admin can manage project technologies" ON public.project_technologies; -DROP POLICY IF EXISTS "Public can view project technologies" ON public.project_technologies; - -CREATE POLICY "Admin can manage project technologies" ON public.project_technologies FOR ALL -USING (public.is_admin_user()) -WITH CHECK (public.is_admin_user()); - -CREATE POLICY "Public can view project technologies" ON public.project_technologies FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_technologies.project_id - AND projects.status = 'published' - ) -); \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_fix_rls_services_blog.sql b/supabase/migrations/_archive-pre-202603/20241119_fix_rls_services_blog.sql deleted file mode 100644 index 67e1273..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_fix_rls_services_blog.sql +++ /dev/null @@ -1,126 +0,0 @@ --- RLS Policies dla tabeli services -DROP POLICY IF EXISTS "Admin can manage services" ON public.services; -DROP POLICY IF EXISTS "Public can view published services" ON public.services; - -CREATE POLICY "Admin can manage services" ON public.services FOR ALL -USING (public.is_admin_user()) -WITH CHECK (public.is_admin_user()); - -CREATE POLICY "Public can view published services" ON public.services FOR SELECT -USING (status = 'published'); - --- RLS Policies dla tabeli service_translations -DROP POLICY IF EXISTS "Admin can manage service translations" ON public.service_translations; -DROP POLICY IF EXISTS "Public can view service translations" ON public.service_translations; - -CREATE POLICY "Admin can manage service translations" ON public.service_translations FOR ALL -USING (public.is_admin_user()) -WITH CHECK (public.is_admin_user()); - -CREATE POLICY "Public can view service translations" ON public.service_translations FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.services - WHERE services.id = service_translations.service_id - AND services.status = 'published' - ) -); - --- RLS Policies dla tabeli service_categories -DROP POLICY IF EXISTS "Admin can manage service categories" ON public.service_categories; -DROP POLICY IF EXISTS "Public can view service categories" ON public.service_categories; - -CREATE POLICY "Admin can manage service categories" ON public.service_categories FOR ALL -USING (public.is_admin_user()) -WITH CHECK (public.is_admin_user()); - -CREATE POLICY "Public can view service categories" ON public.service_categories FOR SELECT -USING (true); - --- RLS Policies dla tabeli service_category_mappings -DROP POLICY IF EXISTS "Admin can manage service category mappings" ON public.service_category_mappings; -DROP POLICY IF EXISTS "Public can view service category mappings" ON public.service_category_mappings; - -CREATE POLICY "Admin can manage service category mappings" ON public.service_category_mappings FOR ALL -USING (public.is_admin_user()) -WITH CHECK (public.is_admin_user()); - -CREATE POLICY "Public can view service category mappings" ON public.service_category_mappings FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.services - WHERE services.id = service_category_mappings.service_id - AND services.status = 'published' - ) -); - --- RLS Policies dla tabeli blog_posts -DROP POLICY IF EXISTS "Admin can manage blog posts" ON public.blog_posts; -DROP POLICY IF EXISTS "Public can view published blog posts" ON public.blog_posts; - -CREATE POLICY "Admin can manage blog posts" ON public.blog_posts FOR ALL -USING (public.is_admin_user()) -WITH CHECK (public.is_admin_user()); - -CREATE POLICY "Public can view published blog posts" ON public.blog_posts FOR SELECT -USING (status = 'published'); - --- RLS Policies dla tabeli blog_post_translations -DROP POLICY IF EXISTS "Admin can manage blog post translations" ON public.blog_post_translations; -DROP POLICY IF EXISTS "Public can view blog post translations" ON public.blog_post_translations; - -CREATE POLICY "Admin can manage blog post translations" ON public.blog_post_translations FOR ALL -USING (public.is_admin_user()) -WITH CHECK (public.is_admin_user()); - -CREATE POLICY "Public can view blog post translations" ON public.blog_post_translations FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.blog_posts - WHERE blog_posts.id = blog_post_translations.post_id - AND blog_posts.status = 'published' - ) -); - --- RLS Policies dla tabeli blog_categories -DROP POLICY IF EXISTS "Admin can manage blog categories" ON public.blog_categories; -DROP POLICY IF EXISTS "Public can view blog categories" ON public.blog_categories; - -CREATE POLICY "Admin can manage blog categories" ON public.blog_categories FOR ALL -USING (public.is_admin_user()) -WITH CHECK (public.is_admin_user()); - -CREATE POLICY "Public can view blog categories" ON public.blog_categories FOR SELECT -USING (true); - --- RLS Policies dla tabeli blog_post_categories -DROP POLICY IF EXISTS "Admin can manage blog post categories" ON public.blog_post_categories; -DROP POLICY IF EXISTS "Public can view blog post categories" ON public.blog_post_categories; - -CREATE POLICY "Admin can manage blog post categories" ON public.blog_post_categories FOR ALL -USING (public.is_admin_user()) -WITH CHECK (public.is_admin_user()); - -CREATE POLICY "Public can view blog post categories" ON public.blog_post_categories FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.blog_posts - WHERE blog_posts.id = blog_post_categories.post_id - AND blog_posts.status = 'published' - ) -); - --- RLS Policies dla tabeli blog_comments -DROP POLICY IF EXISTS "Admin can manage blog comments" ON public.blog_comments; -DROP POLICY IF EXISTS "Public can view approved blog comments" ON public.blog_comments; -DROP POLICY IF EXISTS "Users can create blog comments" ON public.blog_comments; - -CREATE POLICY "Admin can manage blog comments" ON public.blog_comments FOR ALL -USING (public.is_admin_user()) -WITH CHECK (public.is_admin_user()); - -CREATE POLICY "Public can view approved blog comments" ON public.blog_comments FOR SELECT -USING (status = 'approved'); - -CREATE POLICY "Users can create blog comments" ON public.blog_comments FOR INSERT -WITH CHECK (true); \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_fix_simple_rls.sql b/supabase/migrations/_archive-pre-202603/20241119_fix_simple_rls.sql deleted file mode 100644 index 54e28c6..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_fix_simple_rls.sql +++ /dev/null @@ -1,193 +0,0 @@ --- Prosta funkcja do sprawdzania czy użytkownik jest zalogowany jako admin -CREATE OR REPLACE FUNCTION public.is_admin_session() -RETURNS BOOLEAN -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -BEGIN - -- Sprawdzamy czy istnieje sesja admina w localStorage/sessionStorage - -- W naszej aplikacji używamy własnego systemu sesji, nie auth.users - RETURN true; -- Dla uproszczenia, zakładamy że admin jest zalogowany -END; -$$; - --- Uprawnienia dla funkcji -GRANT EXECUTE ON FUNCTION public.is_admin_session() TO anon, authenticated; - --- Uproszczone RLS policies dla tabeli projects - pozwalają na wszystko dla admina -DROP POLICY IF EXISTS "Admin can manage projects" ON public.projects; -DROP POLICY IF EXISTS "Public can view published projects" ON public.projects; - -CREATE POLICY "Admin can manage projects" ON public.projects FOR ALL -USING (true) -- Uproszczone - zakładamy że admin jest zalogowany -WITH CHECK (true); - -CREATE POLICY "Public can view published projects" ON public.projects FOR SELECT -USING (status = 'published'); - --- Uproszczone RLS policies dla tabeli project_translations -DROP POLICY IF EXISTS "Admin can manage project translations" ON public.project_translations; -DROP POLICY IF EXISTS "Public can view project translations" ON public.project_translations; - -CREATE POLICY "Admin can manage project translations" ON public.project_translations FOR ALL -USING (true) -WITH CHECK (true); - -CREATE POLICY "Public can view project translations" ON public.project_translations FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_translations.project_id - AND projects.status = 'published' - ) -); - --- Uproszczone RLS policies dla tabeli project_images -DROP POLICY IF EXISTS "Admin can manage project images" ON public.project_images; -DROP POLICY IF EXISTS "Public can view project images" ON public.project_images; - -CREATE POLICY "Admin can manage project images" ON public.project_images FOR ALL -USING (true) -WITH CHECK (true); - -CREATE POLICY "Public can view project images" ON public.project_images FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_images.project_id - AND projects.status = 'published' - ) -); - --- Uproszczone RLS policies dla tabeli project_technologies -DROP POLICY IF EXISTS "Admin can manage project technologies" ON public.project_technologies; -DROP POLICY IF EXISTS "Public can view project technologies" ON public.project_technologies; - -CREATE POLICY "Admin can manage project technologies" ON public.project_technologies FOR ALL -USING (true) -WITH CHECK (true); - -CREATE POLICY "Public can view project technologies" ON public.project_technologies FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_technologies.project_id - AND projects.status = 'published' - ) -); - --- Uproszczone RLS policies dla tabeli services -DROP POLICY IF EXISTS "Admin can manage services" ON public.services; -DROP POLICY IF EXISTS "Public can view published services" ON public.services; - -CREATE POLICY "Admin can manage services" ON public.services FOR ALL -USING (true) -WITH CHECK (true); - -CREATE POLICY "Public can view published services" ON public.services FOR SELECT -USING (status = 'active'); - --- Uproszczone RLS policies dla tabeli service_translations -DROP POLICY IF EXISTS "Admin can manage service translations" ON public.service_translations; -DROP POLICY IF EXISTS "Public can view service translations" ON public.service_translations; - -CREATE POLICY "Admin can manage service translations" ON public.service_translations FOR ALL -USING (true) -WITH CHECK (true); - -CREATE POLICY "Public can view service translations" ON public.service_translations FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.services - WHERE services.id = service_translations.service_id - AND services.status = 'active' - ) -); - --- Uproszczone RLS policies dla tabeli service_categories -DROP POLICY IF EXISTS "Admin can manage service categories" ON public.service_categories; -DROP POLICY IF EXISTS "Public can view service categories" ON public.service_categories; - -CREATE POLICY "Admin can manage service categories" ON public.service_categories FOR ALL -USING (true) -WITH CHECK (true); - -CREATE POLICY "Public can view service categories" ON public.service_categories FOR SELECT -USING (true); - --- Uproszczone RLS policies dla tabeli service_category_assignments -DROP POLICY IF EXISTS "Admin can manage service category mappings" ON public.service_category_assignments; -DROP POLICY IF EXISTS "Public can view service category mappings" ON public.service_category_assignments; - -CREATE POLICY "Admin can manage service category mappings" ON public.service_category_assignments FOR ALL -USING (true) -WITH CHECK (true); - -CREATE POLICY "Public can view service category mappings" ON public.service_category_assignments FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.services - WHERE services.id = service_category_assignments.service_id - AND services.status = 'active' - ) -); - --- Uproszczone RLS policies dla tabeli blog_posts -DROP POLICY IF EXISTS "Admin can manage blog posts" ON public.blog_posts; -DROP POLICY IF EXISTS "Public can view published blog posts" ON public.blog_posts; - -CREATE POLICY "Admin can manage blog posts" ON public.blog_posts FOR ALL -USING (true) -WITH CHECK (true); - -CREATE POLICY "Public can view published blog posts" ON public.blog_posts FOR SELECT -USING (status = 'published'); - --- Uproszczone RLS policies dla tabeli blog_post_translations -DROP POLICY IF EXISTS "Admin can manage blog post translations" ON public.blog_post_translations; -DROP POLICY IF EXISTS "Public can view blog post translations" ON public.blog_post_translations; - -CREATE POLICY "Admin can manage blog post translations" ON public.blog_post_translations FOR ALL -USING (true) -WITH CHECK (true); - -CREATE POLICY "Public can view blog post translations" ON public.blog_post_translations FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.blog_posts - WHERE blog_posts.id = blog_post_translations.post_id - AND blog_posts.status = 'published' - ) -); - --- Uproszczone RLS policies dla tabeli blog_categories -DROP POLICY IF EXISTS "Admin can manage blog categories" ON public.blog_categories; -DROP POLICY IF EXISTS "Public can view blog categories" ON public.blog_categories; - -CREATE POLICY "Admin can manage blog categories" ON public.blog_categories FOR ALL -USING (true) -WITH CHECK (true); - -CREATE POLICY "Public can view blog categories" ON public.blog_categories FOR SELECT -USING (true); - --- Uproszczone RLS policies dla tabeli blog_post_categories -DROP POLICY IF EXISTS "Admin can manage blog post categories" ON public.blog_post_categories; -DROP POLICY IF EXISTS "Public can view blog post categories" ON public.blog_post_categories; - -CREATE POLICY "Admin can manage blog post categories" ON public.blog_post_categories FOR ALL -USING (true) -WITH CHECK (true); - -CREATE POLICY "Public can view blog post categories" ON public.blog_post_categories FOR SELECT -USING ( - EXISTS ( - SELECT 1 FROM public.blog_posts - WHERE blog_posts.id = blog_post_categories.post_id - AND blog_posts.status = 'published' - ) -); - --- Uprawnienia dla wszystkich tabel -GRANT ALL ON ALL TABLES IN SCHEMA public TO anon, authenticated; -GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO anon, authenticated; \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_insert_default_categories.sql b/supabase/migrations/_archive-pre-202603/20241119_insert_default_categories.sql deleted file mode 100644 index 31f054d..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_insert_default_categories.sql +++ /dev/null @@ -1,60 +0,0 @@ --- MIGRATION TYPE: SEED DATA (historical) --- Inserts default categories into legacy tables. Current schema uses vv_* tables --- seeded through the CMS/admin panel. Kept for migration history continuity. --- Kategorie usług -INSERT INTO public.service_categories (slug, color) VALUES -('web-development', '#3B82F6'), -('mobile-apps', '#10B981'), -('ui-ux-design', '#F59E0B'), -('ecommerce', '#EF4444'), -('consulting', '#8B5CF6'), -('maintenance', '#6B7280'); - --- Tłumaczenia kategorii usług -INSERT INTO public.service_category_translations (category_id, language, name, description) VALUES -((SELECT id FROM public.service_categories WHERE slug = 'web-development'), 'pl', 'Tworzenie Stron WWW', 'Profesjonalne strony internetowe i aplikacje webowe'), -((SELECT id FROM public.service_categories WHERE slug = 'web-development'), 'en', 'Web Development', 'Professional websites and web applications'), - -((SELECT id FROM public.service_categories WHERE slug = 'mobile-apps'), 'pl', 'Aplikacje Mobilne', 'Aplikacje na iOS i Android'), -((SELECT id FROM public.service_categories WHERE slug = 'mobile-apps'), 'en', 'Mobile Apps', 'iOS and Android applications'), - -((SELECT id FROM public.service_categories WHERE slug = 'ui-ux-design'), 'pl', 'UI/UX Design', 'Projektowanie interfejsów i doświadczeń użytkownika'), -((SELECT id FROM public.service_categories WHERE slug = 'ui-ux-design'), 'en', 'UI/UX Design', 'User interface and experience design'), - -((SELECT id FROM public.service_categories WHERE slug = 'ecommerce'), 'pl', 'E-commerce', 'Sklepy internetowe i platformy sprzedażowe'), -((SELECT id FROM public.service_categories WHERE slug = 'ecommerce'), 'en', 'E-commerce', 'Online stores and sales platforms'), - -((SELECT id FROM public.service_categories WHERE slug = 'consulting'), 'pl', 'Konsulting', 'Doradztwo technologiczne i strategiczne'), -((SELECT id FROM public.service_categories WHERE slug = 'consulting'), 'en', 'Consulting', 'Technology and strategic consulting'), - -((SELECT id FROM public.service_categories WHERE slug = 'maintenance'), 'pl', 'Utrzymanie', 'Obsługa i rozwój istniejących systemów'), -((SELECT id FROM public.service_categories WHERE slug = 'maintenance'), 'en', 'Maintenance', 'Support and development of existing systems'); - --- Kategorie bloga -INSERT INTO public.blog_categories (slug, color) VALUES -('technology', '#3B82F6'), -('design', '#EC4899'), -('business', '#10B981'), -('tutorials', '#F59E0B'), -('news', '#EF4444'), -('tips', '#8B5CF6'); - --- Tłumaczenia kategorii bloga -INSERT INTO public.blog_category_translations (category_id, language, name, description) VALUES -((SELECT id FROM public.blog_categories WHERE slug = 'technology'), 'pl', 'Technologia', 'Najnowsze technologie i trendy'), -((SELECT id FROM public.blog_categories WHERE slug = 'technology'), 'en', 'Technology', 'Latest technologies and trends'), - -((SELECT id FROM public.blog_categories WHERE slug = 'design'), 'pl', 'Design', 'Porady i inspiracje projektowe'), -((SELECT id FROM public.blog_categories WHERE slug = 'design'), 'en', 'Design', 'Design tips and inspiration'), - -((SELECT id FROM public.blog_categories WHERE slug = 'business'), 'pl', 'Biznes', 'Wskazówki biznesowe i strategie'), -((SELECT id FROM public.blog_categories WHERE slug = 'business'), 'en', 'Business', 'Business tips and strategies'), - -((SELECT id FROM public.blog_categories WHERE slug = 'tutorials'), 'pl', 'Poradniki', 'Krok po kroku - samouczki'), -((SELECT id FROM public.blog_categories WHERE slug = 'tutorials'), 'en', 'Tutorials', 'Step by step guides'), - -((SELECT id FROM public.blog_categories WHERE slug = 'news'), 'pl', 'Aktualności', 'Najnowsze informacje i nowości'), -((SELECT id FROM public.blog_categories WHERE slug = 'news'), 'en', 'News', 'Latest information and updates'), - -((SELECT id FROM public.blog_categories WHERE slug = 'tips'), 'pl', 'Wskazówki', 'Praktyczne wskazówki i triki'), -((SELECT id FROM public.blog_categories WHERE slug = 'tips'), 'en', 'Tips', 'Practical tips and tricks'); \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_insert_portfolio_test_data.sql b/supabase/migrations/_archive-pre-202603/20241119_insert_portfolio_test_data.sql deleted file mode 100644 index 950a8fe..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_insert_portfolio_test_data.sql +++ /dev/null @@ -1,198 +0,0 @@ --- MIGRATION TYPE: SEED DATA (historical) --- Inserts test portfolio projects into legacy tables. Current schema uses vv_projects. --- Kept for migration history continuity only. --- Insert test portfolio projects with unique slugs - --- Insert first test project -WITH inserted_project AS ( - INSERT INTO public.projects ( - id, slug, category, status, featured, order_index, demo_url, github_url, client_name - ) VALUES ( - gen_random_uuid(), - 'modern-portfolio-website-2024', - 'websites', - 'active', - true, - 1, - 'https://example.com/modern-portfolio', - 'https://github.com/example/modern-portfolio', - 'Creative Agency' - ) RETURNING id -) -INSERT INTO public.project_translations (project_id, locale, title, short_description, description, seo_title, seo_description, seo_keywords) -SELECT - id, - 'pl', - 'Nowoczesna Strona Portfolio', - 'Elegancka i responsywna strona portfolio', - 'Stworzyliśmy nowoczesną, w pełni responsywną stronę portfolio dla Creative Agency. Projekt wyróżnia się czystym designem, intuicyjną nawigacją i optymalizacją pod kątem wyszukiwarek.', - 'Nowoczesna Strona Portfolio | Portfolio', - 'Zobacz nasze portfolio nowoczesnych stron portfolio. Eleganckie designy, responsywne layouty i profesjonalne podejście.', - ARRAY['strony internetowe', 'design', 'responsywność', 'nowoczesne', 'portfolio'] -FROM inserted_project; - --- Insert English translation -WITH inserted_project AS ( - SELECT id FROM public.projects WHERE slug = 'modern-portfolio-website-2024' LIMIT 1 -) -INSERT INTO public.project_translations (project_id, locale, title, short_description, description, seo_title, seo_description, seo_keywords) -SELECT - id, - 'en', - 'Modern Portfolio Website', - 'Elegant and responsive portfolio website', - 'We created a modern, fully responsive portfolio website for Creative Agency. The project features clean design, intuitive navigation, and search engine optimization.', - 'Modern Portfolio Website | Portfolio', - 'Check out our portfolio of modern portfolio websites. Elegant designs, responsive layouts, and professional approach.', - ARRAY['websites', 'design', 'responsive', 'modern', 'portfolio'] -FROM inserted_project; - --- Insert images and set cover for first project -WITH p AS ( - SELECT id FROM public.projects WHERE slug = 'modern-portfolio-website-2024' LIMIT 1 -) -INSERT INTO public.project_images (project_id, path, type, "order", alt_pl, alt_en) -SELECT id, id || '/cover.webp', 'screenshot', 0, 'Okładka', 'Cover' FROM p -UNION ALL -SELECT id, id || '/screen-1.webp', 'screenshot', 1, 'Widok 1', 'Screen 1' FROM p -UNION ALL -SELECT id, id || '/screen-2.webp', 'screenshot', 2, 'Widok 2', 'Screen 2' FROM p; - -UPDATE public.projects pr -SET cover_path = p.id || '/cover.webp' -FROM (SELECT id FROM public.projects WHERE slug = 'modern-portfolio-website-2024' LIMIT 1) p -WHERE pr.id = p.id; - --- Insert technologies for the project -WITH inserted_project AS ( - SELECT id FROM public.projects WHERE slug = 'modern-portfolio-website-2024' LIMIT 1 -) -INSERT INTO public.project_technologies (project_id, name, color, icon, "order") -SELECT - id, - 'React', - '#61DAFB', - 'react', - 1 -FROM inserted_project -UNION ALL -SELECT - id, - 'TypeScript', - '#3178C6', - 'typescript', - 2 -FROM inserted_project -UNION ALL -SELECT - id, - 'Tailwind CSS', - '#38B2AC', - 'tailwind', - 3 -FROM inserted_project -UNION ALL -SELECT - id, - 'Vite', - '#646CFF', - 'vite', - 4 -FROM inserted_project; - --- Insert second test project -WITH inserted_project AS ( - INSERT INTO public.projects ( - id, slug, category, status, featured, order_index, demo_url, github_url, client_name - ) VALUES ( - gen_random_uuid(), - 'ecommerce-solution-platform-2024', - 'web-apps', - 'active', - true, - 2, - 'https://example.com/ecommerce-platform', - 'https://github.com/example/ecommerce-platform', - 'Tech Solutions Inc' - ) RETURNING id -) -INSERT INTO public.project_translations (project_id, locale, title, short_description, description, seo_title, seo_description, seo_keywords) -SELECT - id, - 'pl', - 'Platforma E-commerce', - 'Zaawansowana platforma handlu elektronicznego', - 'Stworzyliśmy zaawansowaną platformę e-commerce z funkcjami zarządzania produktami, koszykiem, płatnościami i systemem zamówień.', - 'Platforma E-commerce | Portfolio', - 'Profesjonalna platforma e-commerce z nowoczesnymi funkcjonalnościami.', - ARRAY['e-commerce', 'platforma', 'sklep internetowy', 'handel elektroniczny'] -FROM inserted_project; - --- Insert images and set cover for second project -WITH p AS ( - SELECT id FROM public.projects WHERE slug = 'ecommerce-solution-platform-2024' LIMIT 1 -) -INSERT INTO public.project_images (project_id, path, type, "order", alt_pl, alt_en) -SELECT id, id || '/cover.webp', 'screenshot', 0, 'Okładka', 'Cover' FROM p -UNION ALL -SELECT id, id || '/screen-1.webp', 'screenshot', 1, 'Widok 1', 'Screen 1' FROM p -UNION ALL -SELECT id, id || '/screen-2.webp', 'screenshot', 2, 'Widok 2', 'Screen 2' FROM p; - -UPDATE public.projects pr -SET cover_path = p.id || '/cover.webp' -FROM (SELECT id FROM public.projects WHERE slug = 'ecommerce-solution-platform-2024' LIMIT 1) p -WHERE pr.id = p.id; - --- Insert English translation for second project -WITH inserted_project AS ( - SELECT id FROM public.projects WHERE slug = 'ecommerce-solution-platform-2024' LIMIT 1 -) -INSERT INTO public.project_translations (project_id, locale, title, short_description, description, seo_title, seo_description, seo_keywords) -SELECT - id, - 'en', - 'E-commerce Platform', - 'Advanced e-commerce platform', - 'We created an advanced e-commerce platform with product management, shopping cart, payments, and order system features.', - 'E-commerce Platform | Portfolio', - 'Professional e-commerce platform with modern functionalities.', - ARRAY['e-commerce', 'platform', 'online store', 'electronic commerce'] -FROM inserted_project; - --- Insert technologies for the e-commerce project -WITH inserted_project AS ( - SELECT id FROM public.projects WHERE slug = 'ecommerce-solution-platform-2024' LIMIT 1 -) -INSERT INTO public.project_technologies (project_id, name, color, icon, "order") -SELECT - id, - 'React', - '#61DAFB', - 'react', - 1 -FROM inserted_project -UNION ALL -SELECT - id, - 'Node.js', - '#339933', - 'nodejs', - 2 -FROM inserted_project -UNION ALL -SELECT - id, - 'PostgreSQL', - '#336791', - 'postgresql', - 3 -FROM inserted_project -UNION ALL -SELECT - id, - 'Stripe', - '#008CDD', - 'stripe', - 4 -FROM inserted_project; diff --git a/supabase/migrations/_archive-pre-202603/20241119_insert_test_images.sql b/supabase/migrations/_archive-pre-202603/20241119_insert_test_images.sql deleted file mode 100644 index 6637488..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_insert_test_images.sql +++ /dev/null @@ -1,22 +0,0 @@ --- MIGRATION TYPE: SEED DATA (historical) --- Inserts sample images for test projects into legacy project_images table. --- Current schema uses vv_project_images. Kept for migration history continuity. --- Insert sample images for test projects using public URLs - --- Modern Portfolio Website -WITH p AS ( - SELECT id FROM public.projects WHERE slug = 'modern-portfolio-website-2024' LIMIT 1 -) -INSERT INTO public.project_images (project_id, path, type, "order", alt_pl, alt_en) -SELECT id, 'https://picsum.photos/seed/modern-portfolio-1/1200/675', 'screenshot', 0, 'Podgląd strony głównej', 'Homepage preview' FROM p -UNION ALL -SELECT id, 'https://picsum.photos/seed/modern-portfolio-2/1200/675', 'screenshot', 1, 'Podgląd sekcji', 'Section preview' FROM p; - --- E-commerce Platform -WITH p AS ( - SELECT id FROM public.projects WHERE slug = 'ecommerce-solution-platform-2024' LIMIT 1 -) -INSERT INTO public.project_images (project_id, path, type, "order", alt_pl, alt_en) -SELECT id, 'https://picsum.photos/seed/ecommerce-1/1200/675', 'screenshot', 0, 'Panel sklepu', 'Shop panel' FROM p -UNION ALL -SELECT id, 'https://picsum.photos/seed/ecommerce-2/1200/675', 'screenshot', 1, 'Strona produktu', 'Product page' FROM p; diff --git a/supabase/migrations/_archive-pre-202603/20241119_insert_test_projects.sql b/supabase/migrations/_archive-pre-202603/20241119_insert_test_projects.sql deleted file mode 100644 index 4606701..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_insert_test_projects.sql +++ /dev/null @@ -1,110 +0,0 @@ --- MIGRATION TYPE: SEED DATA (historical) --- Inserts test projects into legacy tables. Current schema uses vv_projects. --- Kept for migration history continuity only. --- Insert test portfolio project -INSERT INTO public.projects ( - id, slug, category, status, featured, order_index, demo_url, github_url, client_name, cover_path -) VALUES ( - gen_random_uuid(), - 'test-portfolio-project-1', - 'websites', - 'active', - true, - 1, - 'https://example.com/demo', - 'https://github.com/test/project', - 'Test Client', - '/portfolio/test-cover.jpg' -); - --- Insert another test project -WITH inserted_project AS ( - INSERT INTO public.projects ( - id, slug, category, status, featured, order_index, demo_url, github_url, client_name - ) VALUES ( - gen_random_uuid(), - 'ecommerce-platform-demo', - 'web-apps', - 'active', - true, - 2, - 'https://example.com/ecommerce-demo', - 'https://github.com/example/ecommerce', - 'Tech Solutions Inc' - ) RETURNING id -) -INSERT INTO public.project_translations (project_id, locale, title, short_description, description, seo_title, seo_description, seo_keywords) -SELECT - id, - 'pl', - 'Platforma E-commerce', - 'Zaawansowana platforma handlu elektronicznego', - 'Stworzyliśmy zaawansowaną platformę e-commerce z funkcjami zarządzania produktami, koszykiem, płatnościami i systemem zamówień.', - 'Platforma E-commerce | Portfolio', - 'Profesjonalna platforma e-commerce z nowoczesnymi funkcjonalnościami.', - ARRAY['e-commerce', 'platforma', 'sklep internetowy', 'handel elektroniczny'] -FROM inserted_project; - -WITH inserted_project AS ( - INSERT INTO public.projects ( - id, slug, category, status, featured, order_index, demo_url, github_url, client_name - ) VALUES ( - gen_random_uuid(), - 'ecommerce-platform-demo', - 'web-apps', - 'active', - true, - 2, - 'https://example.com/ecommerce-demo', - 'https://github.com/example/ecommerce', - 'Tech Solutions Inc' - ) RETURNING id -) -INSERT INTO public.project_translations (project_id, locale, title, short_description, description, seo_title, seo_description, seo_keywords) -SELECT - id, - 'en', - 'E-commerce Platform', - 'Advanced e-commerce platform', - 'We created an advanced e-commerce platform with product management, shopping cart, payments, and order system features.', - 'E-commerce Platform | Portfolio', - 'Professional e-commerce platform with modern functionalities.', - ARRAY['e-commerce', 'platform', 'online store', 'electronic commerce'] -FROM inserted_project; - --- Insert technologies for the e-commerce project -WITH inserted_project AS ( - SELECT id FROM public.projects WHERE slug = 'ecommerce-platform-demo' LIMIT 1 -) -INSERT INTO public.project_technologies (project_id, name, color, icon, "order") -SELECT - id, - 'React', - '#61DAFB', - 'react', - 1 -FROM inserted_project -UNION ALL -SELECT - id, - 'Node.js', - '#339933', - 'nodejs', - 2 -FROM inserted_project -UNION ALL -SELECT - id, - 'PostgreSQL', - '#336791', - 'postgresql', - 3 -FROM inserted_project -UNION ALL -SELECT - id, - 'Stripe', - '#008CDD', - 'stripe', - 4 -FROM inserted_project; \ No newline at end of file diff --git a/supabase/migrations/_archive-pre-202603/20241119_simplify_portfolio_permissions.sql b/supabase/migrations/_archive-pre-202603/20241119_simplify_portfolio_permissions.sql deleted file mode 100644 index e3f2355..0000000 --- a/supabase/migrations/_archive-pre-202603/20241119_simplify_portfolio_permissions.sql +++ /dev/null @@ -1,135 +0,0 @@ - --- Drop existing complex RLS policies -DROP POLICY IF EXISTS "Public can view active projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can view all projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can insert projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can update projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can delete projects" ON public.projects; -DROP POLICY IF EXISTS "Admin can manage projects" ON public.projects; - -DROP POLICY IF EXISTS "Public can view translations for active projects" ON public.project_translations; -DROP POLICY IF EXISTS "Admin can manage translations" ON public.project_translations; - -DROP POLICY IF EXISTS "Public can view images for active projects" ON public.project_images; -DROP POLICY IF EXISTS "Admin can manage images" ON public.project_images; - -DROP POLICY IF EXISTS "Public can view technologies for active projects" ON public.project_technologies; -DROP POLICY IF EXISTS "Admin can manage technologies" ON public.project_technologies; - --- Create simplified RLS policies --- Allow public read access to active projects -CREATE POLICY "Public can view active projects" ON public.projects FOR SELECT - USING (status = 'active'); - --- Admins can view all projects (including non-active) -CREATE POLICY "Admin can view all projects" ON public.projects FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.user_id = auth.uid() AND admins.is_admin = true - ) - ); - --- Allow authenticated users to manage all projects -CREATE POLICY "Admin can manage projects" ON public.projects FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.user_id = auth.uid() AND admins.is_admin = true - ) - ) - WITH CHECK ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.user_id = auth.uid() AND admins.is_admin = true - ) - ); - --- Allow public read access to translations of active projects -CREATE POLICY "Public can view translations for active projects" ON public.project_translations FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_translations.project_id AND projects.status = 'active' - ) - ); - --- Allow authenticated users to manage all translations -CREATE POLICY "Admin can manage translations" ON public.project_translations FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.user_id = auth.uid() AND admins.is_admin = true - ) - ) - WITH CHECK ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.user_id = auth.uid() AND admins.is_admin = true - ) - ); - --- Allow public read access to images of active projects -CREATE POLICY "Public can view images for active projects" ON public.project_images FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_images.project_id AND projects.status = 'active' - ) - ); - --- Allow authenticated users to manage all images -CREATE POLICY "Admin can manage images" ON public.project_images FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.user_id = auth.uid() AND admins.is_admin = true - ) - ) - WITH CHECK ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.user_id = auth.uid() AND admins.is_admin = true - ) - ); - --- Allow public read access to technologies of active projects -CREATE POLICY "Public can view technologies for active projects" ON public.project_technologies FOR SELECT - USING ( - EXISTS ( - SELECT 1 FROM public.projects - WHERE projects.id = project_technologies.project_id AND projects.status = 'active' - ) - ); - --- Allow authenticated users to manage all technologies -CREATE POLICY "Admin can manage technologies" ON public.project_technologies FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.user_id = auth.uid() AND admins.is_admin = true - ) - ) - WITH CHECK ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.user_id = auth.uid() AND admins.is_admin = true - ) - ); - --- Simplify storage policies for 'portfolio' bucket -DROP POLICY IF EXISTS "Authenticated can manage portfolio files" ON storage.objects; -CREATE POLICY "Admin can manage portfolio files" ON storage.objects - FOR ALL TO authenticated - USING ( - bucket_id = 'portfolio' AND EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.user_id = auth.uid() AND admins.is_admin = true - ) - ) - WITH CHECK ( - bucket_id = 'portfolio' AND EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.user_id = auth.uid() AND admins.is_admin = true - ) - ); diff --git a/supabase/migrations/_archive-pre-202603/20241130_add_faq_permissions_rls.sql b/supabase/migrations/_archive-pre-202603/20241130_add_faq_permissions_rls.sql deleted file mode 100644 index 5571f15..0000000 --- a/supabase/migrations/_archive-pre-202603/20241130_add_faq_permissions_rls.sql +++ /dev/null @@ -1,123 +0,0 @@ --- Create Roles if they don't exist -INSERT INTO public.admin_roles (name, description) VALUES -('Administrator', 'Full access to most features'), -('Editor', 'Can edit content'), -('Viewer', 'Read-only access') -ON CONFLICT (name) DO NOTHING; - --- Add FAQ permissions -INSERT INTO public.admin_permissions (code, description, module) VALUES -('faq.view', 'Can view FAQ items and categories', 'faq'), -('faq.create', 'Can create FAQ items and categories', 'faq'), -('faq.edit', 'Can edit FAQ items and categories', 'faq'), -('faq.delete', 'Can delete FAQ items and categories', 'faq') -ON CONFLICT (code) DO NOTHING; - --- Assign permissions to Roles -DO $$ -DECLARE - super_admin_role_id uuid; - admin_role_id uuid; - editor_role_id uuid; - viewer_role_id uuid; - perm_id uuid; -BEGIN - SELECT id INTO super_admin_role_id FROM public.admin_roles WHERE name = 'Super Admin'; - SELECT id INTO admin_role_id FROM public.admin_roles WHERE name = 'Administrator'; - SELECT id INTO editor_role_id FROM public.admin_roles WHERE name = 'Editor'; - SELECT id INTO viewer_role_id FROM public.admin_roles WHERE name = 'Viewer'; - - -- Super Admin gets all FAQ permissions - FOR perm_id IN SELECT id FROM public.admin_permissions WHERE module = 'faq' LOOP - IF super_admin_role_id IS NOT NULL THEN - INSERT INTO public.admin_role_permissions (role_id, permission_id) - VALUES (super_admin_role_id, perm_id) - ON CONFLICT DO NOTHING; - END IF; - END LOOP; - - -- Administrator gets all FAQ permissions - FOR perm_id IN SELECT id FROM public.admin_permissions WHERE module = 'faq' LOOP - IF admin_role_id IS NOT NULL THEN - INSERT INTO public.admin_role_permissions (role_id, permission_id) - VALUES (admin_role_id, perm_id) - ON CONFLICT DO NOTHING; - END IF; - END LOOP; - - -- Editor gets all FAQ permissions - FOR perm_id IN SELECT id FROM public.admin_permissions WHERE module = 'faq' LOOP - IF editor_role_id IS NOT NULL THEN - INSERT INTO public.admin_role_permissions (role_id, permission_id) - VALUES (editor_role_id, perm_id) - ON CONFLICT DO NOTHING; - END IF; - END LOOP; - - -- Viewer gets only view permission - SELECT id INTO perm_id FROM public.admin_permissions WHERE code = 'faq.view'; - IF viewer_role_id IS NOT NULL AND perm_id IS NOT NULL THEN - INSERT INTO public.admin_role_permissions (role_id, permission_id) - VALUES (viewer_role_id, perm_id) - ON CONFLICT DO NOTHING; - END IF; - -END $$; - --- Enable RLS for FAQ tables -ALTER TABLE public.faq_categories ENABLE ROW LEVEL SECURITY; -ALTER TABLE public.faq_items ENABLE ROW LEVEL SECURITY; -ALTER TABLE public.faq_category_translations ENABLE ROW LEVEL SECURITY; -ALTER TABLE public.faq_item_translations ENABLE ROW LEVEL SECURITY; - --- RLS Policies for FAQ Categories -DROP POLICY IF EXISTS "Public can view faq categories" ON public.faq_categories; -CREATE POLICY "Public can view faq categories" ON public.faq_categories FOR SELECT USING (true); - -DROP POLICY IF EXISTS "Admins can insert faq categories" ON public.faq_categories; -CREATE POLICY "Admins can insert faq categories" ON public.faq_categories FOR INSERT WITH CHECK (public.check_permission('faq.create')); - -DROP POLICY IF EXISTS "Admins can update faq categories" ON public.faq_categories; -CREATE POLICY "Admins can update faq categories" ON public.faq_categories FOR UPDATE USING (public.check_permission('faq.edit')); - -DROP POLICY IF EXISTS "Admins can delete faq categories" ON public.faq_categories; -CREATE POLICY "Admins can delete faq categories" ON public.faq_categories FOR DELETE USING (public.check_permission('faq.delete')); - --- RLS Policies for FAQ Items -DROP POLICY IF EXISTS "Public can view faq items" ON public.faq_items; -CREATE POLICY "Public can view faq items" ON public.faq_items FOR SELECT USING (true); - -DROP POLICY IF EXISTS "Admins can insert faq items" ON public.faq_items; -CREATE POLICY "Admins can insert faq items" ON public.faq_items FOR INSERT WITH CHECK (public.check_permission('faq.create')); - -DROP POLICY IF EXISTS "Admins can update faq items" ON public.faq_items; -CREATE POLICY "Admins can update faq items" ON public.faq_items FOR UPDATE USING (public.check_permission('faq.edit')); - -DROP POLICY IF EXISTS "Admins can delete faq items" ON public.faq_items; -CREATE POLICY "Admins can delete faq items" ON public.faq_items FOR DELETE USING (public.check_permission('faq.delete')); - --- RLS Policies for FAQ Category Translations -DROP POLICY IF EXISTS "Public can view faq category translations" ON public.faq_category_translations; -CREATE POLICY "Public can view faq category translations" ON public.faq_category_translations FOR SELECT USING (true); - -DROP POLICY IF EXISTS "Admins can insert faq category translations" ON public.faq_category_translations; -CREATE POLICY "Admins can insert faq category translations" ON public.faq_category_translations FOR INSERT WITH CHECK (public.check_permission('faq.create')); - -DROP POLICY IF EXISTS "Admins can update faq category translations" ON public.faq_category_translations; -CREATE POLICY "Admins can update faq category translations" ON public.faq_category_translations FOR UPDATE USING (public.check_permission('faq.edit')); - -DROP POLICY IF EXISTS "Admins can delete faq category translations" ON public.faq_category_translations; -CREATE POLICY "Admins can delete faq category translations" ON public.faq_category_translations FOR DELETE USING (public.check_permission('faq.delete')); - --- RLS Policies for FAQ Item Translations -DROP POLICY IF EXISTS "Public can view faq item translations" ON public.faq_item_translations; -CREATE POLICY "Public can view faq item translations" ON public.faq_item_translations FOR SELECT USING (true); - -DROP POLICY IF EXISTS "Admins can insert faq item translations" ON public.faq_item_translations; -CREATE POLICY "Admins can insert faq item translations" ON public.faq_item_translations FOR INSERT WITH CHECK (public.check_permission('faq.create')); - -DROP POLICY IF EXISTS "Admins can update faq item translations" ON public.faq_item_translations; -CREATE POLICY "Admins can update faq item translations" ON public.faq_item_translations FOR UPDATE USING (public.check_permission('faq.edit')); - -DROP POLICY IF EXISTS "Admins can delete faq item translations" ON public.faq_item_translations; -CREATE POLICY "Admins can delete faq item translations" ON public.faq_item_translations FOR DELETE USING (public.check_permission('faq.delete')); diff --git a/supabase/migrations/_archive-pre-202603/20241130_add_newsletter_update_policy.sql b/supabase/migrations/_archive-pre-202603/20241130_add_newsletter_update_policy.sql deleted file mode 100644 index 25a08b5..0000000 --- a/supabase/migrations/_archive-pre-202603/20241130_add_newsletter_update_policy.sql +++ /dev/null @@ -1,6 +0,0 @@ --- Add UPDATE policy for newsletter_subscribers --- Allowing admins with 'users.delete' permission to update subscribers (e.g. unsubscribe/reactivate) - -DROP POLICY IF EXISTS "Admins can update subscribers" ON public.newsletter_subscribers; -CREATE POLICY "Admins can update subscribers" ON public.newsletter_subscribers - FOR UPDATE USING (public.check_permission('users.delete')); diff --git a/supabase/migrations/_archive-pre-202603/20241130_implement_rbac_rls.sql b/supabase/migrations/_archive-pre-202603/20241130_implement_rbac_rls.sql deleted file mode 100644 index 2fd8188..0000000 --- a/supabase/migrations/_archive-pre-202603/20241130_implement_rbac_rls.sql +++ /dev/null @@ -1,189 +0,0 @@ --- Add is_active to admin_users if it doesn't exist -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'admin_users' AND column_name = 'is_active') THEN - ALTER TABLE public.admin_users ADD COLUMN is_active boolean DEFAULT true; - END IF; -END $$; - --- Function to check if user has a specific permission -CREATE OR REPLACE FUNCTION public.check_permission(required_permission text) -RETURNS boolean -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -BEGIN - RETURN EXISTS ( - SELECT 1 - FROM public.admin_users au - JOIN public.admin_roles ar ON au.role_id = ar.id - JOIN public.admin_role_permissions arp ON ar.id = arp.role_id - JOIN public.admin_permissions ap ON arp.permission_id = ap.id - WHERE au.id = auth.uid() - AND au.is_active = true - AND ap.code = required_permission - ); -END; -$$; - --- Update is_admin_user to use the new table -CREATE OR REPLACE FUNCTION public.is_admin_user() -RETURNS boolean -LANGUAGE plpgsql -SECURITY DEFINER -AS $$ -BEGIN - RETURN EXISTS ( - SELECT 1 - FROM public.admin_users - WHERE id = auth.uid() - AND is_active = true - ); -END; -$$; - --- RLS Policies - --- Blog Posts -ALTER TABLE public.blog_posts ENABLE ROW LEVEL SECURITY; - -DROP POLICY IF EXISTS "Public can view published posts" ON public.blog_posts; -CREATE POLICY "Public can view published posts" ON public.blog_posts - FOR SELECT USING (status = 'published'); - -DROP POLICY IF EXISTS "Admins can view all posts" ON public.blog_posts; -CREATE POLICY "Admins can view all posts" ON public.blog_posts - FOR SELECT USING (public.check_permission('blog.view')); - -DROP POLICY IF EXISTS "Admins can insert posts" ON public.blog_posts; -CREATE POLICY "Admins can insert posts" ON public.blog_posts - FOR INSERT WITH CHECK (public.check_permission('blog.create')); - -DROP POLICY IF EXISTS "Admins can update posts" ON public.blog_posts; -CREATE POLICY "Admins can update posts" ON public.blog_posts - FOR UPDATE USING (public.check_permission('blog.edit')); - -DROP POLICY IF EXISTS "Admins can delete posts" ON public.blog_posts; -CREATE POLICY "Admins can delete posts" ON public.blog_posts - FOR DELETE USING (public.check_permission('blog.delete')); - - --- Projects (Portfolio) -ALTER TABLE public.projects ENABLE ROW LEVEL SECURITY; - -DROP POLICY IF EXISTS "Public can view projects" ON public.projects; -CREATE POLICY "Public can view projects" ON public.projects - FOR SELECT USING (true); - -DROP POLICY IF EXISTS "Admins can insert projects" ON public.projects; -CREATE POLICY "Admins can insert projects" ON public.projects - FOR INSERT WITH CHECK (public.check_permission('portfolio.create')); - -DROP POLICY IF EXISTS "Admins can update projects" ON public.projects; -CREATE POLICY "Admins can update projects" ON public.projects - FOR UPDATE USING (public.check_permission('portfolio.edit')); - -DROP POLICY IF EXISTS "Admins can delete projects" ON public.projects; -CREATE POLICY "Admins can delete projects" ON public.projects - FOR DELETE USING (public.check_permission('portfolio.delete')); - - --- Audit Logs -ALTER TABLE public.audit_logs ENABLE ROW LEVEL SECURITY; - -DROP POLICY IF EXISTS "Admins can view audit logs" ON public.audit_logs; -CREATE POLICY "Admins can view audit logs" ON public.audit_logs - FOR SELECT USING (public.check_permission('users.view')); -- Or specific audit.view permission - -DROP POLICY IF EXISTS "System can insert audit logs" ON public.audit_logs; -CREATE POLICY "System can insert audit logs" ON public.audit_logs - FOR INSERT WITH CHECK (true); -- Ideally restricted to authenticated users, but for now true to allow logging from server functions if needed, or auth.uid() is not null - - --- Blocked IPs -ALTER TABLE public.blocked_ips ENABLE ROW LEVEL SECURITY; - -DROP POLICY IF EXISTS "Admins can view blocked ips" ON public.blocked_ips; -CREATE POLICY "Admins can view blocked ips" ON public.blocked_ips - FOR SELECT USING (public.check_permission('users.view')); - -DROP POLICY IF EXISTS "Admins can manage blocked ips" ON public.blocked_ips; -CREATE POLICY "Admins can manage blocked ips" ON public.blocked_ips - FOR ALL USING (public.check_permission('users.manage_roles')); -- Using manage_roles as proxy for high level security - - - --- Services -ALTER TABLE public.services ENABLE ROW LEVEL SECURITY; - -DROP POLICY IF EXISTS "Public can view services" ON public.services; -CREATE POLICY "Public can view services" ON public.services - FOR SELECT USING (true); - -DROP POLICY IF EXISTS "Admins can insert services" ON public.services; -CREATE POLICY "Admins can insert services" ON public.services - FOR INSERT WITH CHECK (public.check_permission('services.create')); - -DROP POLICY IF EXISTS "Admins can update services" ON public.services; -CREATE POLICY "Admins can update services" ON public.services - FOR UPDATE USING (public.check_permission('services.edit')); - -DROP POLICY IF EXISTS "Admins can delete services" ON public.services; -CREATE POLICY "Admins can delete services" ON public.services - FOR DELETE USING (public.check_permission('services.delete')); - - --- Contact Messages -ALTER TABLE public.contact_messages ENABLE ROW LEVEL SECURITY; - -DROP POLICY IF EXISTS "Admins can view messages" ON public.contact_messages; -CREATE POLICY "Admins can view messages" ON public.contact_messages - FOR SELECT USING (public.check_permission('messages.view')); - -DROP POLICY IF EXISTS "Admins can update messages" ON public.contact_messages; -CREATE POLICY "Admins can update messages" ON public.contact_messages - FOR UPDATE USING (public.check_permission('messages.edit')); -- Assuming edit permission covers status update - -DROP POLICY IF EXISTS "Admins can delete messages" ON public.contact_messages; -CREATE POLICY "Admins can delete messages" ON public.contact_messages - FOR DELETE USING (public.check_permission('messages.delete')); - --- Allow anyone to insert messages (contact form) -DROP POLICY IF EXISTS "Anyone can insert messages" ON public.contact_messages; -CREATE POLICY "Anyone can insert messages" ON public.contact_messages - FOR INSERT WITH CHECK (true); - - --- Newsletter Subscribers -ALTER TABLE public.newsletter_subscribers ENABLE ROW LEVEL SECURITY; - -DROP POLICY IF EXISTS "Admins can view subscribers" ON public.newsletter_subscribers; -CREATE POLICY "Admins can view subscribers" ON public.newsletter_subscribers - FOR SELECT USING (public.check_permission('users.view')); -- Using users.view as proxy or create specific permission - -DROP POLICY IF EXISTS "Admins can delete subscribers" ON public.newsletter_subscribers; -CREATE POLICY "Admins can delete subscribers" ON public.newsletter_subscribers - FOR DELETE USING (public.check_permission('users.delete')); - --- Allow anyone to subscribe -DROP POLICY IF EXISTS "Anyone can subscribe" ON public.newsletter_subscribers; -CREATE POLICY "Anyone can subscribe" ON public.newsletter_subscribers - FOR INSERT WITH CHECK (true); - - --- Admin Users (Self-management and Super Admin) -ALTER TABLE public.admin_users ENABLE ROW LEVEL SECURITY; - -DROP POLICY IF EXISTS "Admins can view admin users" ON public.admin_users; -CREATE POLICY "Admins can view admin users" ON public.admin_users - FOR SELECT USING (public.check_permission('users.view')); - -DROP POLICY IF EXISTS "Super Admins can manage admin users" ON public.admin_users; -CREATE POLICY "Super Admins can manage admin users" ON public.admin_users - FOR ALL USING (public.check_permission('users.manage_roles')); - --- Allow users to read their own data -DROP POLICY IF EXISTS "Users can read own data" ON public.admin_users; -CREATE POLICY "Users can read own data" ON public.admin_users - FOR SELECT USING (auth.uid() = id); - diff --git a/supabase/migrations/_archive-pre-202603/20241207220000_crm_expansion.sql b/supabase/migrations/_archive-pre-202603/20241207220000_crm_expansion.sql deleted file mode 100644 index cd51b49..0000000 --- a/supabase/migrations/_archive-pre-202603/20241207220000_crm_expansion.sql +++ /dev/null @@ -1,68 +0,0 @@ --- Enable UUID extension if not already enabled -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - --- 1. Activities Table (History of interactions) -CREATE TABLE IF NOT EXISTS public.crm_activities ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - lead_id UUID REFERENCES public.leads(id) ON DELETE CASCADE, - client_id UUID REFERENCES public.clients(id) ON DELETE CASCADE, - type TEXT NOT NULL CHECK (type IN ('note', 'call_log', 'email_log', 'status_change', 'meeting', 'offer_sent')), - content TEXT, - created_at TIMESTAMPTZ DEFAULT NOW(), - created_by UUID REFERENCES auth.users(id) -); - --- 2. Reminders Table (Follow-ups) -CREATE TABLE IF NOT EXISTS public.crm_reminders ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - lead_id UUID REFERENCES public.leads(id) ON DELETE CASCADE, - client_id UUID REFERENCES public.clients(id) ON DELETE CASCADE, - title TEXT NOT NULL, - description TEXT, - due_date TIMESTAMPTZ NOT NULL, - is_completed BOOLEAN DEFAULT FALSE, - created_at TIMESTAMPTZ DEFAULT NOW(), - created_by UUID REFERENCES auth.users(id) -); - --- 3. Offers Table (PDF Proposals) -CREATE TABLE IF NOT EXISTS public.crm_offers ( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - lead_id UUID REFERENCES public.leads(id) ON DELETE SET NULL, - client_id UUID REFERENCES public.clients(id) ON DELETE SET NULL, - token TEXT UNIQUE NOT NULL DEFAULT encode(gen_random_bytes(16), 'hex'), - status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'sent', 'opened', 'accepted', 'rejected')), - name TEXT NOT NULL, - total_value NUMERIC(15, 2), - currency TEXT DEFAULT 'PLN', - content_json JSONB DEFAULT '[]'::jsonb, -- Stores line items - notes TEXT, - valid_until TIMESTAMPTZ, - created_at TIMESTAMPTZ DEFAULT NOW(), - created_by UUID REFERENCES auth.users(id) -); - --- Enable RLS -ALTER TABLE public.crm_activities ENABLE ROW LEVEL SECURITY; -ALTER TABLE public.crm_reminders ENABLE ROW LEVEL SECURITY; -ALTER TABLE public.crm_offers ENABLE ROW LEVEL SECURITY; - --- Policies (Admin only for now, similar to leads) -CREATE POLICY "Admins can view all activities" ON public.crm_activities FOR SELECT TO authenticated USING (auth.jwt() ->> 'role' = 'service_role' OR EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())); -CREATE POLICY "Admins can insert activities" ON public.crm_activities FOR INSERT TO authenticated WITH CHECK (auth.jwt() ->> 'role' = 'service_role' OR EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())); -CREATE POLICY "Admins can update activities" ON public.crm_activities FOR UPDATE TO authenticated USING (auth.jwt() ->> 'role' = 'service_role' OR EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())); - -CREATE POLICY "Admins can view all reminders" ON public.crm_reminders FOR SELECT TO authenticated USING (auth.jwt() ->> 'role' = 'service_role' OR EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())); -CREATE POLICY "Admins can insert reminders" ON public.crm_reminders FOR INSERT TO authenticated WITH CHECK (auth.jwt() ->> 'role' = 'service_role' OR EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())); -CREATE POLICY "Admins can update reminders" ON public.crm_reminders FOR UPDATE TO authenticated USING (auth.jwt() ->> 'role' = 'service_role' OR EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())); -CREATE POLICY "Admins can delete reminders" ON public.crm_reminders FOR DELETE TO authenticated USING (auth.jwt() ->> 'role' = 'service_role' OR EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())); - -CREATE POLICY "Admins can view all offers" ON public.crm_offers FOR SELECT TO authenticated USING (auth.jwt() ->> 'role' = 'service_role' OR EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())); -CREATE POLICY "Public can view offers by token" ON public.crm_offers FOR SELECT TO anon USING (true); -- Allow public access via token query (logic handled in app/edge function) -CREATE POLICY "Admins can insert offers" ON public.crm_offers FOR INSERT TO authenticated WITH CHECK (auth.jwt() ->> 'role' = 'service_role' OR EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())); -CREATE POLICY "Admins can update offers" ON public.crm_offers FOR UPDATE TO authenticated USING (auth.jwt() ->> 'role' = 'service_role' OR EXISTS (SELECT 1 FROM public.admin_users WHERE id = auth.uid())); - --- Realtime -ALTER PUBLICATION supabase_realtime ADD TABLE public.crm_activities; -ALTER PUBLICATION supabase_realtime ADD TABLE public.crm_reminders; -ALTER PUBLICATION supabase_realtime ADD TABLE public.crm_offers; diff --git a/supabase/migrations/_archive-pre-202603/20241207230000_crm_fix_and_expand.sql b/supabase/migrations/_archive-pre-202603/20241207230000_crm_fix_and_expand.sql deleted file mode 100644 index c3b7427..0000000 --- a/supabase/migrations/_archive-pre-202603/20241207230000_crm_fix_and_expand.sql +++ /dev/null @@ -1,12 +0,0 @@ --- Rename 'company' to 'company_name' in leads table for consistency with clients table -ALTER TABLE leads RENAME COLUMN company TO company_name; - --- Add new columns for "Existing Features Expansion" (address, nip, etc if missing) -ALTER TABLE clients ADD COLUMN IF NOT EXISTS address TEXT; -ALTER TABLE clients ADD COLUMN IF NOT EXISTS city TEXT; -ALTER TABLE clients ADD COLUMN IF NOT EXISTS zip TEXT; -ALTER TABLE clients ADD COLUMN IF NOT EXISTS industry TEXT; -ALTER TABLE clients ADD COLUMN IF NOT EXISTS website TEXT; - -ALTER TABLE contracts ADD COLUMN IF NOT EXISTS payment_terms TEXT; -- e.g. "Netto 14 days" -ALTER TABLE contracts ADD COLUMN IF NOT EXISTS type TEXT DEFAULT 'b2b'; -- 'b2b', 'uop', 'other' diff --git a/supabase/migrations/_archive-pre-202603/20241207_create_crm_tables.sql b/supabase/migrations/_archive-pre-202603/20241207_create_crm_tables.sql deleted file mode 100644 index 06f2dd8..0000000 --- a/supabase/migrations/_archive-pre-202603/20241207_create_crm_tables.sql +++ /dev/null @@ -1,79 +0,0 @@ --- Create LEADS table -CREATE TABLE IF NOT EXISTS leads ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - full_name TEXT NOT NULL, - company_name TEXT, - email TEXT, - phone TEXT, - status TEXT NOT NULL DEFAULT 'new', -- new, contacted, meeting, proposal, negotiation, won, lost - source TEXT, -- website, referral, linkedin, etc. - value NUMERIC(12,2), - currency TEXT DEFAULT 'PLN', - notes TEXT, - position INTEGER DEFAULT 0 -); - --- Create CLIENTS table -CREATE TABLE IF NOT EXISTS clients ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - company_name TEXT NOT NULL, - nip TEXT, - address TEXT, - contact_person TEXT, - email TEXT, - phone TEXT, - status TEXT DEFAULT 'active', - notes TEXT -); - --- Create CONTRACTS table -CREATE TABLE IF NOT EXISTS contracts ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - client_id UUID REFERENCES clients(id) ON DELETE CASCADE, - title TEXT NOT NULL, - start_date DATE NOT NULL, - end_date DATE, - value NUMERIC(12,2), - status TEXT DEFAULT 'draft', -- draft, active, expired, terminated - file_url TEXT -); - --- Enable RLS -ALTER TABLE leads ENABLE ROW LEVEL SECURITY; -ALTER TABLE clients ENABLE ROW LEVEL SECURITY; -ALTER TABLE contracts ENABLE ROW LEVEL SECURITY; - --- Create generic policies (allow all for authenticated users / admins) --- Attempt to drop existing policies first to avoid conflicts if re-running -DROP POLICY IF EXISTS "Enable all for authenticated users" ON leads; -DROP POLICY IF EXISTS "Enable all for authenticated users" ON clients; -DROP POLICY IF EXISTS "Enable all for authenticated users" ON contracts; - -CREATE POLICY "Enable all for authenticated users" ON leads FOR ALL USING (auth.role() = 'authenticated'); -CREATE POLICY "Enable all for authenticated users" ON clients FOR ALL USING (auth.role() = 'authenticated'); -CREATE POLICY "Enable all for authenticated users" ON contracts FOR ALL USING (auth.role() = 'authenticated'); - --- Create updated_at trigger function if it doesn't exist -CREATE OR REPLACE FUNCTION handle_updated_at() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = NOW(); - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- Add Triggers -DROP TRIGGER IF EXISTS handle_leads_updated_at ON leads; -CREATE TRIGGER handle_leads_updated_at BEFORE UPDATE ON leads FOR EACH ROW EXECUTE FUNCTION handle_updated_at(); - -DROP TRIGGER IF EXISTS handle_clients_updated_at ON clients; -CREATE TRIGGER handle_clients_updated_at BEFORE UPDATE ON clients FOR EACH ROW EXECUTE FUNCTION handle_updated_at(); - -DROP TRIGGER IF EXISTS handle_contracts_updated_at ON contracts; -CREATE TRIGGER handle_contracts_updated_at BEFORE UPDATE ON contracts FOR EACH ROW EXECUTE FUNCTION handle_updated_at(); diff --git a/supabase/migrations/_archive-pre-202603/20251209_ai_config.sql b/supabase/migrations/_archive-pre-202603/20251209_ai_config.sql deleted file mode 100644 index bb34dd9..0000000 --- a/supabase/migrations/_archive-pre-202603/20251209_ai_config.sql +++ /dev/null @@ -1,28 +0,0 @@ --- System Configuration -CREATE TABLE IF NOT EXISTS system_config ( - key TEXT PRIMARY KEY, - value TEXT, - "group" TEXT DEFAULT 'general', - is_secret BOOLEAN DEFAULT false, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- RLS -ALTER TABLE system_config ENABLE ROW LEVEL SECURITY; - --- Policies -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE tablename = 'system_config' AND policyname = 'Enable all for authenticated users') THEN - CREATE POLICY "Enable all for authenticated users" ON system_config FOR ALL USING (auth.role() = 'authenticated'); - END IF; -END -$$; - --- Insert default placeholder keys (empty) -INSERT INTO system_config (key, value, "group", is_secret) VALUES -('openai_api_key', '', 'ai', true), -('deepl_api_key', '', 'ai', true), -('stability_api_key', '', 'ai', true) -ON CONFLICT (key) DO NOTHING; diff --git a/supabase/migrations/_archive-pre-202603/20251209_cleanup_audit.sql b/supabase/migrations/_archive-pre-202603/20251209_cleanup_audit.sql deleted file mode 100644 index 0bd5e4d..0000000 --- a/supabase/migrations/_archive-pre-202603/20251209_cleanup_audit.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE IF EXISTS system_audit_logs; diff --git a/supabase/migrations/_archive-pre-202603/20251209_client_portal.sql b/supabase/migrations/_archive-pre-202603/20251209_client_portal.sql deleted file mode 100644 index f9ec6a9..0000000 --- a/supabase/migrations/_archive-pre-202603/20251209_client_portal.sql +++ /dev/null @@ -1,99 +0,0 @@ --- Client Portal & PM 2.0 Schema - --- 1. Ensure projects has client_id -ALTER TABLE projects ADD COLUMN IF NOT EXISTS client_id UUID REFERENCES clients(id) ON DELETE SET NULL; - --- 2. Project Files -CREATE TABLE IF NOT EXISTS project_files ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - project_id UUID REFERENCES projects(id) ON DELETE CASCADE, - uploader_id UUID REFERENCES auth.users(id), -- Admin or Client - name TEXT NOT NULL, - url TEXT NOT NULL, - size BIGINT, -- in bytes - type TEXT, -- mime type - version INTEGER DEFAULT 1, - is_shared_with_client BOOLEAN DEFAULT false, - folder TEXT DEFAULT 'root' -- e.g., 'design', 'contracts', 'briefs' -); - --- 3. Project Briefs (Approved/Rejected by Client) -CREATE TABLE IF NOT EXISTS project_briefs ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - project_id UUID REFERENCES projects(id) ON DELETE CASCADE, - title TEXT NOT NULL, - content TEXT, -- Markdown or JSON content - status TEXT DEFAULT 'draft', -- draft, pending_client, approved, rejected - version INTEGER DEFAULT 1, - feedback TEXT -- Client feedback if rejected -); - --- 4. Time Logs (Internal) -CREATE TABLE IF NOT EXISTS time_logs ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - project_id UUID REFERENCES projects(id) ON DELETE CASCADE, - user_id UUID REFERENCES auth.users(id), - date DATE DEFAULT CURRENT_DATE, - hours NUMERIC(5, 2) NOT NULL, -- e.g. 1.5, 2.0 - description TEXT, - billable BOOLEAN DEFAULT true -); - --- 5. Client Access (Linking auth.users to clients table) -ALTER TABLE clients ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id); - --- Enable RLS -ALTER TABLE project_files ENABLE ROW LEVEL SECURITY; -ALTER TABLE project_briefs ENABLE ROW LEVEL SECURITY; -ALTER TABLE time_logs ENABLE ROW LEVEL SECURITY; - --- Policies (Simplified) - --- Files -CREATE POLICY "Admins All Files" ON project_files FOR ALL USING ( - EXISTS (SELECT 1 FROM admin_users WHERE id = auth.uid()) OR - auth.role() = 'service_role' -); - -CREATE POLICY "Clients View Shared Files" ON project_files FOR SELECT USING ( - is_shared_with_client = true AND - project_id IN ( - SELECT id FROM projects WHERE client_id IN ( - SELECT id FROM clients WHERE user_id = auth.uid() - ) - ) -); - --- Briefs -CREATE POLICY "Admins All Briefs" ON project_briefs FOR ALL USING ( - EXISTS (SELECT 1 FROM admin_users WHERE id = auth.uid()) OR - auth.role() = 'service_role' -); - -CREATE POLICY "Clients View Briefs" ON project_briefs FOR SELECT USING ( - status IN ('pending_client', 'approved', 'rejected') AND - project_id IN ( - SELECT id FROM projects WHERE client_id IN ( - SELECT id FROM clients WHERE user_id = auth.uid() - ) - ) -); - -CREATE POLICY "Clients Update Briefs" ON project_briefs FOR UPDATE USING ( - status = 'pending_client' AND - project_id IN ( - SELECT id FROM projects WHERE client_id IN ( - SELECT id FROM clients WHERE user_id = auth.uid() - ) - ) -); - --- Time Logs -CREATE POLICY "Admins All Time Logs" ON time_logs FOR ALL USING ( - EXISTS (SELECT 1 FROM admin_users WHERE id = auth.uid()) OR - auth.role() = 'service_role' -); diff --git a/supabase/migrations/_archive-pre-202603/20251209_crm_v2.sql b/supabase/migrations/_archive-pre-202603/20251209_crm_v2.sql deleted file mode 100644 index 1961e1a..0000000 --- a/supabase/migrations/_archive-pre-202603/20251209_crm_v2.sql +++ /dev/null @@ -1,47 +0,0 @@ --- Create Customer Events / Timeline table -CREATE TABLE IF NOT EXISTS customer_events ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - client_id UUID REFERENCES clients(id) ON DELETE CASCADE, - lead_id UUID REFERENCES leads(id) ON DELETE CASCADE, - type TEXT NOT NULL, -- 'email', 'call', 'meeting', 'note', 'status_change', 'offer_sent' - title TEXT NOT NULL, - description TEXT, - metadata JSONB DEFAULT '{}'::jsonb, - created_by UUID REFERENCES auth.users(id) -); - --- Email Templates -CREATE TABLE IF NOT EXISTS email_templates ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - name TEXT NOT NULL, - subject TEXT, - body_html TEXT NOT NULL, - category TEXT DEFAULT 'general' -- 'follow_up', 'offer', 'welcome' -); - --- Offer Versions (for detailed versioning) -CREATE TABLE IF NOT EXISTS offer_versions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - offer_id UUID REFERENCES crm_offers(id) ON DELETE CASCADE, - version_number INTEGER NOT NULL, - content_json JSONB, - pdf_url TEXT, - status TEXT DEFAULT 'draft' -- 'draft', 'sent', 'accepted', 'rejected' -); - --- Add public access token to offers for external viewing -ALTER TABLE crm_offers ADD COLUMN IF NOT EXISTS public_token UUID DEFAULT gen_random_uuid(); -ALTER TABLE crm_offers ADD COLUMN IF NOT EXISTS viewed_at TIMESTAMPTZ; -ALTER TABLE crm_offers ADD COLUMN IF NOT EXISTS accepted_at TIMESTAMPTZ; - --- RLS -ALTER TABLE customer_events ENABLE ROW LEVEL SECURITY; -ALTER TABLE email_templates ENABLE ROW LEVEL SECURITY; -ALTER TABLE offer_versions ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "Enable all for authenticated" ON customer_events FOR ALL USING (auth.role() = 'authenticated'); -CREATE POLICY "Enable all for authenticated" ON email_templates FOR ALL USING (auth.role() = 'authenticated'); -CREATE POLICY "Enable all for authenticated" ON offer_versions FOR ALL USING (auth.role() = 'authenticated'); diff --git a/supabase/migrations/_archive-pre-202603/20251209_finance_module.sql b/supabase/migrations/_archive-pre-202603/20251209_finance_module.sql deleted file mode 100644 index 312247b..0000000 --- a/supabase/migrations/_archive-pre-202603/20251209_finance_module.sql +++ /dev/null @@ -1,53 +0,0 @@ --- Create invoices table -CREATE TABLE IF NOT EXISTS invoices ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - client_id UUID REFERENCES clients(id) ON DELETE SET NULL, - contract_id UUID REFERENCES contracts(id) ON DELETE SET NULL, - number TEXT NOT NULL, -- e.g. "FV/2025/12/01" - date_issued DATE NOT NULL DEFAULT CURRENT_DATE, - date_due DATE NOT NULL, - status TEXT NOT NULL DEFAULT 'draft', -- draft, sent, paid, overdue, partial - currency TEXT DEFAULT 'PLN', - net_amount NUMERIC(12,2) DEFAULT 0, - tax_amount NUMERIC(12,2) DEFAULT 0, - total_amount NUMERIC(12,2) DEFAULT 0, - items JSONB DEFAULT '[]'::jsonb, -- Array of { description, quantity, price, vat_rate, total } - notes TEXT, - payment_method TEXT DEFAULT 'transfer', -- transfer, card, cash - seller_details JSONB, -- Snapshot of seller details at time of invoice - buyer_details JSONB -- Snapshot of buyer details at time of invoice -); - --- Create expenses table -CREATE TABLE IF NOT EXISTS expenses ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - category TEXT NOT NULL, -- office, marketing, software, services, salary, tax, other - merchant_name TEXT NOT NULL, - date DATE NOT NULL DEFAULT CURRENT_DATE, - amount NUMERIC(12,2) NOT NULL DEFAULT 0, - currency TEXT DEFAULT 'PLN', - description TEXT, - receipt_scan_url TEXT, - is_verified BOOLEAN DEFAULT FALSE, - ocr_data JSONB -- Data extracted via OCR (mock or real) -); - --- Add RLS policies (Open for verified admins) -ALTER TABLE invoices ENABLE ROW LEVEL SECURITY; -ALTER TABLE expenses ENABLE ROW LEVEL SECURITY; - -CREATE POLICY "Allow read access for authenticated users" ON invoices FOR SELECT USING (auth.role() = 'authenticated'); -CREATE POLICY "Allow write access for authenticated users" ON invoices FOR INSERT WITH CHECK (auth.role() = 'authenticated'); -CREATE POLICY "Allow update access for authenticated users" ON invoices FOR UPDATE USING (auth.role() = 'authenticated'); -CREATE POLICY "Allow delete access for authenticated users" ON invoices FOR DELETE USING (auth.role() = 'authenticated'); - -CREATE POLICY "Allow read access for authenticated users" ON expenses FOR SELECT USING (auth.role() = 'authenticated'); -CREATE POLICY "Allow write access for authenticated users" ON expenses FOR INSERT WITH CHECK (auth.role() = 'authenticated'); -CREATE POLICY "Allow update access for authenticated users" ON expenses FOR UPDATE USING (auth.role() = 'authenticated'); -CREATE POLICY "Allow delete access for authenticated users" ON expenses FOR DELETE USING (auth.role() = 'authenticated'); - --- Triggers for updated_at -CREATE TRIGGER update_invoices_updated_at BEFORE UPDATE ON invoices FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); diff --git a/supabase/migrations/_archive-pre-202603/20251209_hr_module.sql b/supabase/migrations/_archive-pre-202603/20251209_hr_module.sql deleted file mode 100644 index 7e1f120..0000000 --- a/supabase/migrations/_archive-pre-202603/20251209_hr_module.sql +++ /dev/null @@ -1,87 +0,0 @@ --- HR Module Schema - --- Employees Table (extends auth.users or standalone) -CREATE TABLE IF NOT EXISTS employees ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - full_name TEXT NOT NULL, - email TEXT NOT NULL UNIQUE, - position TEXT, - department TEXT, -- Marketing, IT, Sales, HR - hire_date DATE, - avatar_url TEXT, - status TEXT DEFAULT 'active' -- active, onboarding, terminated -); - --- Leaves (Absences) -CREATE TABLE IF NOT EXISTS leaves ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - employee_id UUID REFERENCES employees(id) ON DELETE CASCADE, - type TEXT NOT NULL, -- vacation, sick, remote, unpaid - start_date DATE NOT NULL, - end_date DATE NOT NULL, - status TEXT DEFAULT 'pending', -- pending, approved, rejected - reason TEXT -); - --- Onboarding Tasks Templates -CREATE TABLE IF NOT EXISTS onboarding_task_templates ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - title TEXT NOT NULL, - description TEXT, - department TEXT, -- if null, applies to all - is_required BOOLEAN DEFAULT true -); - --- Employee Onboarding Progress -CREATE TABLE IF NOT EXISTS employee_onboarding ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - employee_id UUID REFERENCES employees(id) ON DELETE CASCADE, - task_template_id UUID REFERENCES onboarding_task_templates(id), - status TEXT DEFAULT 'pending', -- pending, completed - completed_at TIMESTAMPTZ -); - --- Evaluations (Reviews) -CREATE TABLE IF NOT EXISTS evaluations ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - employee_id UUID REFERENCES employees(id) ON DELETE CASCADE, - reviewer_id UUID, -- could reference auth.users - review_date DATE DEFAULT CURRENT_DATE, - rating INTEGER, -- 1-5 or 1-10 - content TEXT, - period TEXT -- Q1 2025, etc. -); - --- Insert Permissions in Roles (if roles table exists) --- This assumes the roles table from previous step exists. --- We won't insert blindly to avoid errors, but this is logically where it belongs. - --- Enable RLS -ALTER TABLE employees ENABLE ROW LEVEL SECURITY; -ALTER TABLE leaves ENABLE ROW LEVEL SECURITY; -ALTER TABLE onboarding_task_templates ENABLE ROW LEVEL SECURITY; -ALTER TABLE employee_onboarding ENABLE ROW LEVEL SECURITY; -ALTER TABLE evaluations ENABLE ROW LEVEL SECURITY; - --- Policies (Simplified for admin access) -CREATE POLICY "Admin All Access Employees" ON employees FOR ALL USING (auth.role() = 'authenticated'); -CREATE POLICY "Admin All Access Leaves" ON leaves FOR ALL USING (auth.role() = 'authenticated'); -CREATE POLICY "Admin All Access Onboarding Templates" ON onboarding_task_templates FOR ALL USING (auth.role() = 'authenticated'); -CREATE POLICY "Admin All Access Employee Onboarding" ON employee_onboarding FOR ALL USING (auth.role() = 'authenticated'); -CREATE POLICY "Admin All Access Evaluations" ON evaluations FOR ALL USING (auth.role() = 'authenticated'); - --- Insert Sample Data -INSERT INTO employees (full_name, email, position, department, hire_date, status) VALUES -('Anna Kowalska', 'anna@vezvision.com', 'Senior Designer', 'Design', '2024-01-15', 'active'), -('Piotr Nowak', 'piotr@vezvision.com', 'Frontend Developer', 'IT', '2024-03-01', 'active'), -('Marek Zając', 'marek@vezvision.com', 'Sales Manager', 'Sales', '2024-06-01', 'active'); - -INSERT INTO onboarding_task_templates (title, department) VALUES -('Konfiguracja konta email', NULL), -('Podpisanie umowy NDA', NULL), -('Szkolenie BHP', NULL), -('Konfiguracja środowiska dev', 'IT'), -('Zapoznanie z Brand Bookiem', 'Design'); diff --git a/supabase/migrations/_archive-pre-202603/20251209_hr_schema.sql b/supabase/migrations/_archive-pre-202603/20251209_hr_schema.sql deleted file mode 100644 index 17e854e..0000000 --- a/supabase/migrations/_archive-pre-202603/20251209_hr_schema.sql +++ /dev/null @@ -1,51 +0,0 @@ --- Job Offers -CREATE TABLE IF NOT EXISTS job_offers ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - title TEXT NOT NULL, - department TEXT, - location TEXT, - type TEXT, -- full-time, b2b, etc. - salary_range TEXT, - description TEXT, - requirements JSONB DEFAULT '[]'::jsonb, - status TEXT DEFAULT 'draft' -- draft, published, closed -); - --- Candidates -CREATE TABLE IF NOT EXISTS candidates ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - job_offer_id UUID REFERENCES job_offers(id) ON DELETE SET NULL, - first_name TEXT NOT NULL, - last_name TEXT NOT NULL, - email TEXT, - phone TEXT, - cv_url TEXT, - status TEXT DEFAULT 'new', -- new, screening, interview, offer, hired, rejected - notes TEXT, - rating INTEGER DEFAULT 0 -); - --- Update Team Members (Employees) -ALTER TABLE team_members ADD COLUMN IF NOT EXISTS department TEXT; -ALTER TABLE team_members ADD COLUMN IF NOT EXISTS position TEXT; -ALTER TABLE team_members ADD COLUMN IF NOT EXISTS start_date DATE; -ALTER TABLE team_members ADD COLUMN IF NOT EXISTS phone TEXT; -ALTER TABLE team_members ADD COLUMN IF NOT EXISTS status TEXT DEFAULT 'active'; -- active, on_leave, terminated - --- Enable RLS -ALTER TABLE job_offers ENABLE ROW LEVEL SECURITY; -ALTER TABLE candidates ENABLE ROW LEVEL SECURITY; - --- Create generic policies -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE tablename = 'job_offers' AND policyname = 'Enable all for authenticated users') THEN - CREATE POLICY "Enable all for authenticated users" ON job_offers FOR ALL USING (auth.role() = 'authenticated'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE tablename = 'candidates' AND policyname = 'Enable all for authenticated users') THEN - CREATE POLICY "Enable all for authenticated users" ON candidates FOR ALL USING (auth.role() = 'authenticated'); - END IF; -END -$$; diff --git a/supabase/migrations/_archive-pre-202603/20251209_project_management_schema.sql b/supabase/migrations/_archive-pre-202603/20251209_project_management_schema.sql deleted file mode 100644 index b84c201..0000000 --- a/supabase/migrations/_archive-pre-202603/20251209_project_management_schema.sql +++ /dev/null @@ -1,75 +0,0 @@ --- Internal Projects -CREATE TABLE IF NOT EXISTS internal_projects ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - client_id UUID REFERENCES clients(id) ON DELETE SET NULL, - name TEXT NOT NULL, - status TEXT NOT NULL DEFAULT 'planned', -- planned, active, paused, completed, cancelled - budget_total NUMERIC(12,2), - deadline DATE, - description TEXT -); - --- Project Phases (Gantt) -CREATE TABLE IF NOT EXISTS project_phases ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - project_id UUID REFERENCES internal_projects(id) ON DELETE CASCADE, - name TEXT NOT NULL, - start_date DATE, - end_date DATE, - status TEXT DEFAULT 'pending', - order_index INTEGER DEFAULT 0 -); - --- Team Members -CREATE TABLE IF NOT EXISTS team_members ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES auth.users(id), - full_name TEXT NOT NULL, - role TEXT, -- developer, designer, pm - hourly_rate NUMERIC(10,2), - avatar_url TEXT, - email TEXT -); - --- Time Entries -CREATE TABLE IF NOT EXISTS time_entries ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - project_id UUID REFERENCES internal_projects(id) ON DELETE CASCADE, - team_member_id UUID REFERENCES team_members(id), - description TEXT, - start_time TIMESTAMPTZ, - end_time TIMESTAMPTZ, - duration_minutes INTEGER, - billable BOOLEAN DEFAULT true -); - --- Briefs -CREATE TABLE IF NOT EXISTS briefs ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - client_id UUID REFERENCES clients(id) ON DELETE SET NULL, - status TEXT DEFAULT 'new', -- new, reviewed, accepted - content_json JSONB, - template_type TEXT -- 'website', 'branding', 'marketing' -); - --- Enable RLS -ALTER TABLE internal_projects ENABLE ROW LEVEL SECURITY; -ALTER TABLE project_phases ENABLE ROW LEVEL SECURITY; -ALTER TABLE team_members ENABLE ROW LEVEL SECURITY; -ALTER TABLE time_entries ENABLE ROW LEVEL SECURITY; -ALTER TABLE briefs ENABLE ROW LEVEL SECURITY; - --- Create generic policies (allow all for authenticated users) --- For a real production app we might want stricter policies, but for this admin panel, auth users are admins. -CREATE POLICY "Enable all for authenticated users" ON internal_projects FOR ALL USING (auth.role() = 'authenticated'); -CREATE POLICY "Enable all for authenticated users" ON project_phases FOR ALL USING (auth.role() = 'authenticated'); -CREATE POLICY "Enable all for authenticated users" ON team_members FOR ALL USING (auth.role() = 'authenticated'); -CREATE POLICY "Enable all for authenticated users" ON time_entries FOR ALL USING (auth.role() = 'authenticated'); -CREATE POLICY "Enable all for authenticated users" ON briefs FOR ALL USING (auth.role() = 'authenticated'); - --- Triggers for updated_at -CREATE TRIGGER handle_internal_projects_updated_at BEFORE UPDATE ON internal_projects FOR EACH ROW EXECUTE FUNCTION handle_updated_at(); diff --git a/supabase/migrations/_archive-pre-202603/20251209_rbac_schema.sql b/supabase/migrations/_archive-pre-202603/20251209_rbac_schema.sql deleted file mode 100644 index b32b7e9..0000000 --- a/supabase/migrations/_archive-pre-202603/20251209_rbac_schema.sql +++ /dev/null @@ -1,39 +0,0 @@ --- Create roles table -CREATE TABLE IF NOT EXISTS roles ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - name TEXT NOT NULL UNIQUE, - description TEXT, - permissions JSONB DEFAULT '[]'::jsonb -- Array of permission strings e.g. ["crm.view", "crm.edit"] -); - --- Create user_roles table -CREATE TABLE IF NOT EXISTS user_roles ( - user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, - role_id UUID REFERENCES roles(id) ON DELETE CASCADE, - created_at TIMESTAMPTZ DEFAULT NOW(), - PRIMARY KEY (user_id, role_id) -); - --- Insert default roles -INSERT INTO roles (name, description, permissions) -VALUES - ('Administrator', 'Pełny dostęp do wszystkich funkcji systemu', '["*"]'::jsonb), - ('Manager', 'Dostęp do zarządzania, ale bez ustawień systemowych', '["crm.view", "crm.edit", "projects.view", "projects.edit", "marketing.view", "finance.view"]'::jsonb), - ('Sprzedawca', 'Dostęp tylko do CRM i Leadów', '["crm.view", "crm.edit", "leads.view", "leads.edit", "offers.view", "offers.edit"]'::jsonb), - ('Redaktor', 'Dostęp do Bloga i Portfolio', '["blog.view", "blog.edit", "portfolio.view", "portfolio.edit"]'::jsonb) -ON CONFLICT (name) DO NOTHING; - --- Enable RLS -ALTER TABLE roles ENABLE ROW LEVEL SECURITY; -ALTER TABLE user_roles ENABLE ROW LEVEL SECURITY; - --- Policies (simplified for now, strictly admin managed in practice) -CREATE POLICY "Allow read access for authenticated users" ON roles FOR SELECT USING (auth.role() = 'authenticated'); -CREATE POLICY "Allow write access for admins" ON roles FOR ALL USING (auth.role() = 'authenticated'); -- Logic would be stricter in prod - -CREATE POLICY "Allow read access for authenticated users" ON user_roles FOR SELECT USING (auth.role() = 'authenticated'); -CREATE POLICY "Allow write access for admins" ON user_roles FOR ALL USING (auth.role() = 'authenticated'); - --- Verify admin_audit_logs structure for extended logging --- Ensuring details is JSONB is enough, we will store { before: ..., after: ... } there. diff --git a/supabase/migrations/_archive-pre-202603/20251209_system_settings.sql b/supabase/migrations/_archive-pre-202603/20251209_system_settings.sql deleted file mode 100644 index 54781ba..0000000 --- a/supabase/migrations/_archive-pre-202603/20251209_system_settings.sql +++ /dev/null @@ -1,65 +0,0 @@ --- System Redirects -CREATE TABLE IF NOT EXISTS system_redirects ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - source_path TEXT NOT NULL UNIQUE, - target_url TEXT NOT NULL, - status_code INTEGER DEFAULT 301, - is_active BOOLEAN DEFAULT true -); - --- System Feature Flags -CREATE TABLE IF NOT EXISTS system_feature_flags ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - key TEXT NOT NULL UNIQUE, - description TEXT, - is_enabled BOOLEAN DEFAULT false, - rules JSONB DEFAULT '[]'::jsonb -); - --- System Webhooks -CREATE TABLE IF NOT EXISTS system_webhooks ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - name TEXT NOT NULL, - url TEXT NOT NULL, - events JSONB DEFAULT '[]'::jsonb, - secret TEXT, - is_active BOOLEAN DEFAULT true, - last_triggered_at TIMESTAMPTZ -); - --- System Audit Logs (if not exists) -CREATE TABLE IF NOT EXISTS system_audit_logs ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - user_id UUID REFERENCES auth.users(id), - action TEXT NOT NULL, - details JSONB DEFAULT '{}'::jsonb, - ip_address TEXT -); - --- Enable RLS -ALTER TABLE system_redirects ENABLE ROW LEVEL SECURITY; -ALTER TABLE system_feature_flags ENABLE ROW LEVEL SECURITY; -ALTER TABLE system_webhooks ENABLE ROW LEVEL SECURITY; -ALTER TABLE system_audit_logs ENABLE ROW LEVEL SECURITY; - --- Create generic policies (Authenticated users can manage these system settings) -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE tablename = 'system_redirects' AND policyname = 'Enable all for authenticated users') THEN - CREATE POLICY "Enable all for authenticated users" ON system_redirects FOR ALL USING (auth.role() = 'authenticated'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE tablename = 'system_feature_flags' AND policyname = 'Enable all for authenticated users') THEN - CREATE POLICY "Enable all for authenticated users" ON system_feature_flags FOR ALL USING (auth.role() = 'authenticated'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE tablename = 'system_webhooks' AND policyname = 'Enable all for authenticated users') THEN - CREATE POLICY "Enable all for authenticated users" ON system_webhooks FOR ALL USING (auth.role() = 'authenticated'); - END IF; - IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE tablename = 'system_audit_logs' AND policyname = 'Enable all for authenticated users') THEN - CREATE POLICY "Enable all for authenticated users" ON system_audit_logs FOR ALL USING (auth.role() = 'authenticated'); - END IF; -END -$$; diff --git a/supabase/migrations/_archive-pre-202603/20251210_analytics_and_calendar.sql b/supabase/migrations/_archive-pre-202603/20251210_analytics_and_calendar.sql deleted file mode 100644 index 1db1418..0000000 --- a/supabase/migrations/_archive-pre-202603/20251210_analytics_and_calendar.sql +++ /dev/null @@ -1,59 +0,0 @@ --- Analytics Visits Table (page_views) matches usePageTracker hook -create table if not exists public.page_views ( - id uuid default gen_random_uuid() primary key, - path text not null, - user_agent text, - referrer text, - device_type text, - visitor_id text, - created_at timestamp with time zone default timezone('utc'::text, now()) not null -); - --- RLS for page_views -alter table public.page_views enable row level security; - --- Allow ANYONE (anon) to insert visits (tracking) -create policy "Allow public insert for analytics" - on public.page_views for insert - to anon, authenticated - with check (true); - --- Allow admins to view analytics -create policy "Allow admins to view analytics" - on public.page_views for select - to authenticated - using ( - exists ( - select 1 from public.users - where users.id = auth.uid() - and users.role in ('admin', 'super_admin') - ) - ); - - --- Unified Calendar Events Table (for manual events) -create table if not exists public.calendar_events ( - id uuid default gen_random_uuid() primary key, - title text not null, - description text, - start_date timestamp with time zone not null, - end_date timestamp with time zone, - type text default 'event', -- 'event', 'meeting', 'deadline' - color text default '#3B82F6', - created_by uuid references auth.users(id), - created_at timestamp with time zone default timezone('utc'::text, now()) not null -); - --- RLS for calendar_events -alter table public.calendar_events enable row level security; - -create policy "Admins can manage calendar events" - on public.calendar_events for all - to authenticated - using ( - exists ( - select 1 from public.users - where users.id = auth.uid() - and users.role in ('admin', 'super_admin') - ) - ); diff --git a/supabase/migrations/_archive-pre-202603/20251210_create_project_tasks.sql b/supabase/migrations/_archive-pre-202603/20251210_create_project_tasks.sql deleted file mode 100644 index 46cbe3e..0000000 --- a/supabase/migrations/_archive-pre-202603/20251210_create_project_tasks.sql +++ /dev/null @@ -1,21 +0,0 @@ --- Create project_tasks table -CREATE TABLE IF NOT EXISTS project_tasks ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW(), - project_id UUID REFERENCES internal_projects(id) ON DELETE CASCADE, - title TEXT NOT NULL, - description TEXT, - estimated_hours NUMERIC(10,2), - priority TEXT DEFAULT 'medium', -- low, medium, high - status TEXT DEFAULT 'todo' -- todo, in_progress, review, done -); - --- Enable RLS -ALTER TABLE project_tasks ENABLE ROW LEVEL SECURITY; - --- Create generic policies (allow all for authenticated users) -CREATE POLICY "Enable all for authenticated users" ON project_tasks FOR ALL USING (auth.role() = 'authenticated'); - --- Handle updated_at -CREATE TRIGGER handle_project_tasks_updated_at BEFORE UPDATE ON project_tasks FOR EACH ROW EXECUTE FUNCTION handle_updated_at(); diff --git a/supabase/migrations/_archive-pre-202603/20251210_crm_intelligence.sql b/supabase/migrations/_archive-pre-202603/20251210_crm_intelligence.sql deleted file mode 100644 index 414c998..0000000 --- a/supabase/migrations/_archive-pre-202603/20251210_crm_intelligence.sql +++ /dev/null @@ -1,13 +0,0 @@ --- Add Lead Intelligence columns to leads table -ALTER TABLE leads -ADD COLUMN IF NOT EXISTS website TEXT, -ADD COLUMN IF NOT EXISTS revenue TEXT, -ADD COLUMN IF NOT EXISTS employees_count TEXT, -ADD COLUMN IF NOT EXISTS tech_stack JSONB DEFAULT '[]'::jsonb, -ADD COLUMN IF NOT EXISTS decision_makers JSONB DEFAULT '[]'::jsonb, -ADD COLUMN IF NOT EXISTS warmup_score INTEGER DEFAULT 0, -ADD COLUMN IF NOT EXISTS competitors_following JSONB DEFAULT '[]'::jsonb, -ADD COLUMN IF NOT EXISTS last_activity_at TIMESTAMPTZ DEFAULT NOW(); - --- Create index for warmup score for sorting -CREATE INDEX IF NOT EXISTS idx_leads_warmup_score ON leads(warmup_score DESC); diff --git a/supabase/migrations/_archive-pre-202603/20251210_features.sql b/supabase/migrations/_archive-pre-202603/20251210_features.sql deleted file mode 100644 index 25448d5..0000000 --- a/supabase/migrations/_archive-pre-202603/20251210_features.sql +++ /dev/null @@ -1,121 +0,0 @@ --- Create tenants table -create table if not exists tenants ( - id uuid primary key default gen_random_uuid(), - domain text unique not null, - name text not null, - logo_url text, - primary_color text, - secondary_color text, - created_at timestamptz default now() -); - --- Enable RLS for tenants -alter table tenants enable row level security; - --- Create policy for reading tenants (public read for white-labeling) -drop policy if exists "Tenants are viewable by everyone" on tenants; -create policy "Tenants are viewable by everyone" on tenants - for select using (true); - --- Create mockups table -create table if not exists mockups ( - id uuid primary key default gen_random_uuid(), - project_id uuid references projects(id) on delete cascade, - name text not null, - image_url text not null, - created_at timestamptz default now() -); - --- Enable RLS for mockups -alter table mockups enable row level security; - -drop policy if exists "Mockups are viewable by authenticated users" on mockups; -create policy "Mockups are viewable by authenticated users" on mockups - for select using (auth.role() = 'authenticated'); - -drop policy if exists "Mockups are insertable by authenticated users" on mockups; -create policy "Mockups are insertable by authenticated users" on mockups - for insert with check (auth.role() = 'authenticated'); - - --- Create mockup_comments table -create table if not exists mockup_comments ( - id uuid primary key default gen_random_uuid(), - mockup_id uuid references mockups(id) on delete cascade, - x float not null, - y float not null, - content text not null, - is_resolved boolean default false, - author_id uuid references auth.users(id), - created_at timestamptz default now() -); - --- Enable RLS for mockup_comments -alter table mockup_comments enable row level security; - -drop policy if exists "Mockup comments are viewable by authenticated users" on mockup_comments; -create policy "Mockup comments are viewable by authenticated users" on mockup_comments - for select using (auth.role() = 'authenticated'); - -drop policy if exists "Mockup comments are insertable by authenticated users" on mockup_comments; -create policy "Mockup comments are insertable by authenticated users" on mockup_comments - for insert with check (auth.role() = 'authenticated'); - -drop policy if exists "Mockup comments are updateable by authenticated users" on mockup_comments; -create policy "Mockup comments are updateable by authenticated users" on mockup_comments - for update using (auth.role() = 'authenticated'); - - --- Create bug_reports table -create table if not exists bug_reports ( - id uuid primary key default gen_random_uuid(), - description text, - video_url text, - reporter_id uuid references auth.users(id), - status text default 'new', - created_at timestamptz default now() -); - --- Enable RLS for bug_reports -alter table bug_reports enable row level security; - -drop policy if exists "Bug reports are viewable by authenticated users" on bug_reports; -create policy "Bug reports are viewable by authenticated users" on bug_reports - for select using (auth.role() = 'authenticated'); - -drop policy if exists "Bug reports are insertable by authenticated users" on bug_reports; -create policy "Bug reports are insertable by authenticated users" on bug_reports - for insert with check (auth.role() = 'authenticated'); - --- Create storage bucket for mockups if it doesn't exist -insert into storage.buckets (id, name, public) -values ('feature-assets', 'feature-assets', true) -on conflict (id) do nothing; - --- Storage policies --- We cannot drop policies on storage.objects easily without exact names which might vary or be system wide. --- Instead, we will wrap in DO blocks to avoid errors if they exist. - -do $$ -begin - if not exists ( - select 1 from pg_policies - where tablename = 'objects' - and policyname = 'Public Access' - and schemaname = 'storage' - ) then - create policy "Public Access" on storage.objects for select using ( bucket_id = 'feature-assets' ); - end if; -end $$; - -do $$ -begin - if not exists ( - select 1 from pg_policies - where tablename = 'objects' - and policyname = 'Authenticated Upload' - and schemaname = 'storage' - ) then - create policy "Authenticated Upload" on storage.objects for insert with check ( bucket_id = 'feature-assets' and auth.role() = 'authenticated' ); - end if; -end $$; diff --git a/supabase/migrations/_archive-pre-202603/20251210_finance_analytics.sql b/supabase/migrations/_archive-pre-202603/20251210_finance_analytics.sql deleted file mode 100644 index 7a58742..0000000 --- a/supabase/migrations/_archive-pre-202603/20251210_finance_analytics.sql +++ /dev/null @@ -1,58 +0,0 @@ --- Add additional_costs to internal_projects -ALTER TABLE internal_projects -ADD COLUMN IF NOT EXISTS additional_costs JSONB DEFAULT '{}'::jsonb; --- Example structure: {"servers": 100, "commissions": 500, "licenses": 200} - --- View: Project Financials (Live Margin) -CREATE OR REPLACE VIEW project_financials AS -WITH labor_costs AS ( - SELECT - p.id AS project_id, - COALESCE(SUM(te.duration_minutes::numeric / 60.0 * tm.hourly_rate), 0) AS total_labor_cost - FROM internal_projects p - LEFT JOIN time_entries te ON p.id = te.project_id - LEFT JOIN team_members tm ON te.team_member_id = tm.id - GROUP BY p.id -), -additional_costs_calc AS ( - -- Summing up numeric values from the jsonb map (simple key-value assumption) - SELECT - id AS project_id, - COALESCE(( - SELECT SUM(value::numeric) - FROM jsonb_each_text(additional_costs) - ), 0) AS total_additional_cost - FROM internal_projects -) -SELECT - p.id AS project_id, - p.name, - p.status, - p.deadline, - p.budget_total, - lc.total_labor_cost, - ac.total_additional_cost, - (lc.total_labor_cost + ac.total_additional_cost) AS total_spent, - (p.budget_total - (lc.total_labor_cost + ac.total_additional_cost)) AS net_margin, - CASE - WHEN p.budget_total IS NULL OR p.budget_total = 0 THEN 0 - ELSE ROUND(((p.budget_total - (lc.total_labor_cost + ac.total_additional_cost)) / p.budget_total * 100), 2) - END AS margin_percentage -FROM internal_projects p -LEFT JOIN labor_costs lc ON p.id = lc.project_id -LEFT JOIN additional_costs_calc ac ON p.id = ac.project_id; - --- View: Employee Profitability --- (Simplified: Revenue Generated = Billable Hours * Hourly Rate) -CREATE OR REPLACE VIEW employee_stats AS -SELECT - tm.id AS team_member_id, - tm.full_name, - tm.role, - tm.hourly_rate, - COALESCE(SUM(te.duration_minutes::numeric / 60.0), 0) AS total_hours_logged, - COALESCE(SUM(CASE WHEN te.billable THEN te.duration_minutes::numeric / 60.0 ELSE 0 END), 0) AS total_billable_hours, - COALESCE(SUM(CASE WHEN te.billable THEN (te.duration_minutes::numeric / 60.0 * tm.hourly_rate) ELSE 0 END), 0) AS estimated_revenue_generated -FROM team_members tm -LEFT JOIN time_entries te ON tm.id = te.team_member_id -GROUP BY tm.id, tm.full_name, tm.role, tm.hourly_rate; diff --git a/supabase/migrations/_archive-pre-202603/20251210_ip_check_function.sql b/supabase/migrations/_archive-pre-202603/20251210_ip_check_function.sql deleted file mode 100644 index c532198..0000000 --- a/supabase/migrations/_archive-pre-202603/20251210_ip_check_function.sql +++ /dev/null @@ -1,23 +0,0 @@ -create or replace function public.check_ip_allowed(check_ip text) -returns boolean -language plpgsql -security definer -as $$ -declare - enabled boolean; - allowed boolean; -begin - -- Check if restriction is enabled - select ip_restriction_enabled into enabled from public.security_settings limit 1; - - -- If settings not found or disabled, allow - if enabled is null or enabled = false then - return true; - end if; - - -- Check if IP is in whitelist - select exists(select 1 from public.whitelisted_ips where ip_address = check_ip) into allowed; - - return allowed; -end; -$$; diff --git a/supabase/migrations/_archive-pre-202603/20251210_newsletter_engine.sql b/supabase/migrations/_archive-pre-202603/20251210_newsletter_engine.sql deleted file mode 100644 index 3cfd958..0000000 --- a/supabase/migrations/_archive-pre-202603/20251210_newsletter_engine.sql +++ /dev/null @@ -1,43 +0,0 @@ --- Create newsletter_campaigns table -create table if not exists public.newsletter_campaigns ( - id uuid primary key default uuid_generate_v4(), - subject text not null, - content text not null, -- HTML content - status text check (status in ('draft', 'sending', 'sent', 'failed')) default 'draft', - sent_count integer default 0, - total_recipients integer default 0, - scheduled_for timestamptz, - sent_at timestamptz, - created_at timestamptz default now(), - updated_at timestamptz default now() -); - --- Create newsletter_logs (optional, for individual tracking) -create table if not exists public.newsletter_logs ( - id uuid primary key default uuid_generate_v4(), - campaign_id uuid references public.newsletter_campaigns(id) on delete cascade, - subscriber_email text not null, - status text check (status in ('sent', 'failed')), - error_message text, - sent_at timestamptz default now() -); - --- Enable RLS -alter table public.newsletter_campaigns enable row level security; -alter table public.newsletter_logs enable row level security; - --- RLS Policies (Admin only) -create policy "Admins can manage campaigns" on public.newsletter_campaigns - for all using ( - public.check_permission('marketing.edit') - ); - -create policy "Admins can view logs" on public.newsletter_logs - for select using ( - public.check_permission('marketing.view') - ); - --- Trigger -create trigger update_newsletter_campaigns_updated_at - before update on public.newsletter_campaigns - for each row execute function public.update_updated_at_column(); diff --git a/supabase/migrations/_archive-pre-202603/20251210_proposal_engine.sql b/supabase/migrations/_archive-pre-202603/20251210_proposal_engine.sql deleted file mode 100644 index c244c45..0000000 --- a/supabase/migrations/_archive-pre-202603/20251210_proposal_engine.sql +++ /dev/null @@ -1,10 +0,0 @@ --- Add Proposal Engine columns to crm_offers table -ALTER TABLE crm_offers -ADD COLUMN IF NOT EXISTS video_url TEXT, -ADD COLUMN IF NOT EXISTS roi_calculator_config JSONB DEFAULT '{}'::jsonb, -ADD COLUMN IF NOT EXISTS ab_test_id UUID, -ADD COLUMN IF NOT EXISTS variant_label TEXT, -- 'A', 'B', etc. -ADD COLUMN IF NOT EXISTS views_count INTEGER DEFAULT 0; - --- Create index for A/B test grouping -CREATE INDEX IF NOT EXISTS idx_crm_offers_ab_test_id ON crm_offers(ab_test_id); diff --git a/supabase/migrations/_archive-pre-202603/20251210_security_compliance.sql b/supabase/migrations/_archive-pre-202603/20251210_security_compliance.sql deleted file mode 100644 index 7c5600d..0000000 --- a/supabase/migrations/_archive-pre-202603/20251210_security_compliance.sql +++ /dev/null @@ -1,64 +0,0 @@ --- 1. IP Whitelisting System - -create table if not exists public.security_settings ( - id integer primary key generated always as identity, - ip_restriction_enabled boolean default false, - updated_at timestamptz default now() -); - --- Initialize settings if empty -insert into public.security_settings (ip_restriction_enabled) -select false where not exists (select 1 from public.security_settings); - -create table if not exists public.whitelisted_ips ( - id uuid primary key default uuid_generate_v4(), - ip_address text not null, -- Store as text to allow flexible formats or CIDR specific types - description text, - added_by uuid references auth.users(id), - created_at timestamptz default now() -); - -alter table public.security_settings enable row level security; -alter table public.whitelisted_ips enable row level security; - -create policy "Admins can manage security settings" on public.security_settings - for all using ( - public.check_permission('users.manage_roles') - ); - -create policy "Admins can manage whitelist" on public.whitelisted_ips - for all using ( - public.check_permission('users.manage_roles') - ); - --- Function to check IP (To be used in RLS or Edge Functions) --- Note: Getting the request IP in Postgres directly is tricky safely without extensions or specific Supabase headers. --- For now, we will enforce this via the Application Layer (Admin Layout) or Edge Functions using this table. --- RLS approach: `auth.jwt() -> 'app_metadata'` doesn't always contain IP. --- We will implement the check in `src/components/layout/AdminLayout.tsx` and sensitive Edge Functions by querying this table. - - --- 2. Data Retention (RODO/GDPR) - --- Function to anonymize old data -create or replace function public.anonymize_old_data() -returns void -language plpgsql -security definer -as $$ -begin - -- Anonymize leads older than 5 years - update public.leads - set - name = 'ANONYMIZED', - email = 'anonymized_' || id || '@example.com', - phone = null, - linkedin_url = null, - company = 'ANONYMIZED' - where created_at < (now() - interval '5 years') - and name != 'ANONYMIZED'; - - -- Add other tables here if needed (e.g. clients) - -- RAISE NOTICE 'Anonymization complete'; -end; -$$; diff --git a/supabase/migrations/_archive-pre-202603/20251210_social_newsletter_casestudy.sql b/supabase/migrations/_archive-pre-202603/20251210_social_newsletter_casestudy.sql deleted file mode 100644 index 52e092e..0000000 --- a/supabase/migrations/_archive-pre-202603/20251210_social_newsletter_casestudy.sql +++ /dev/null @@ -1,114 +0,0 @@ --- Social Media Scheduler Tables - -create type social_platform as enum ('linkedin', 'twitter', 'facebook', 'instagram'); -create type social_post_status as enum ('draft', 'scheduled', 'published', 'failed'); - -create table if not exists social_posts ( - id uuid primary key default gen_random_uuid(), - content text not null, - platforms social_platform[] not null, - scheduled_for timestamptz, - published_at timestamptz, - status social_post_status default 'draft', - media_urls text[], -- Array of image/video URLs - created_by uuid references auth.users(id), - created_at timestamptz default now(), - updated_at timestamptz default now() -); - -alter table social_posts enable row level security; - -create policy "Users can view their own posts" - on social_posts for select - using (auth.uid() = created_by); - -create policy "Users can insert their own posts" - on social_posts for insert - with check (auth.uid() = created_by); - -create policy "Users can update their own posts" - on social_posts for update - using (auth.uid() = created_by); - -create policy "Users can delete their own posts" - on social_posts for delete - using (auth.uid() = created_by); - - --- Newsletter Engine Tables - -create type newsletter_status as enum ('draft', 'scheduled', 'sending', 'sent', 'failed'); - -create table if not exists newsletter_campaigns ( - id uuid primary key default gen_random_uuid(), - subject text not null, - content text not null, -- HTML content - status newsletter_status default 'draft', - scheduled_for timestamptz, - sent_at timestamptz, - recipient_count int default 0, - created_by uuid references auth.users(id), - created_at timestamptz default now(), - updated_at timestamptz default now() -); - -alter table newsletter_campaigns enable row level security; - -create policy "Users can view campaigns" - on newsletter_campaigns for select - using (auth.role() = 'authenticated'); -- Assuming internal team tool - -create policy "Users can insert campaigns" - on newsletter_campaigns for insert - with check (auth.role() = 'authenticated'); - -create policy "Users can update campaigns" - on newsletter_campaigns for update - using (auth.role() = 'authenticated'); - -create table if not exists newsletter_subscribers ( - id uuid primary key default gen_random_uuid(), - email text unique not null, - is_active boolean default true, - subscribed_at timestamptz default now() -); - -alter table newsletter_subscribers enable row level security; - -create policy "Anyone can subscribe" - on newsletter_subscribers for insert - with check (true); - -create policy "Admins can view subscribers" - on newsletter_subscribers for select - using (auth.role() = 'authenticated'); - -- Note: Ideally this should be restricted to admin roles only - --- Case Study Generator Tables - -create type case_study_status as enum ('draft', 'generated', 'published'); - -create table if not exists case_studies ( - id uuid primary key default gen_random_uuid(), - title text not null, - client_name text, - project_description text not null, -- Raw input for AI - generated_content text, -- Markdown output from AI - status case_study_status default 'draft', - image_url text, - created_by uuid references auth.users(id), - created_at timestamptz default now(), - updated_at timestamptz default now() -); - -alter table case_studies enable row level security; - -create policy "Users can view case studies" - on case_studies for select - using (true); -- Publicly viewable if published (logic handled in app usually, but RLS here for simplicity) - --- Actually, for 'draft' ones, maybe we want to restrict? --- Let's make it authenticated for now for editing: -create policy "Authenticated users can manage case studies" - on case_studies for all - using (auth.role() = 'authenticated'); diff --git a/supabase/migrations/_archive-pre-202603/20251210_social_scheduler.sql b/supabase/migrations/_archive-pre-202603/20251210_social_scheduler.sql deleted file mode 100644 index c3eb549..0000000 --- a/supabase/migrations/_archive-pre-202603/20251210_social_scheduler.sql +++ /dev/null @@ -1,27 +0,0 @@ --- Create social_posts table -create table if not exists public.social_posts ( - id uuid primary key default uuid_generate_v4(), - content text not null, - platforms text[] not null check (cardinality(platforms) > 0), -- e.g. ARRAY['linkedin', 'twitter'] - media_urls text[], -- Optional images - scheduled_for timestamptz not null, - status text check (status in ('draft', 'scheduled', 'published', 'failed')) default 'draft', - published_at timestamptz, - error_message text, - created_at timestamptz default now(), - updated_at timestamptz default now() -); - --- Enable RLS -alter table public.social_posts enable row level security; - --- RLS Policies (Admin only) -create policy "Admins can manage social posts" on public.social_posts - for all using ( - public.check_permission('marketing.edit') - ); - --- Trigger for updated_at -create trigger update_social_posts_updated_at - before update on public.social_posts - for each row execute function public.update_updated_at_column(); diff --git a/supabase/migrations/_archive-pre-202603/20251211_enhance_click_tracking.sql b/supabase/migrations/_archive-pre-202603/20251211_enhance_click_tracking.sql deleted file mode 100644 index 054f221..0000000 --- a/supabase/migrations/_archive-pre-202603/20251211_enhance_click_tracking.sql +++ /dev/null @@ -1,8 +0,0 @@ --- Add context columns to click_events -ALTER TABLE click_events -ADD COLUMN IF NOT EXISTS element_text text, -ADD COLUMN IF NOT EXISTS element_class text, -ADD COLUMN IF NOT EXISTS element_id text; - --- Optional: Create an index on element_text for potential future filtering -CREATE INDEX IF NOT EXISTS idx_click_events_element_text ON click_events(element_text); diff --git a/supabase/migrations/_archive-pre-202603/20251217_crm_offer_presets.sql b/supabase/migrations/_archive-pre-202603/20251217_crm_offer_presets.sql deleted file mode 100644 index 75978ff..0000000 --- a/supabase/migrations/_archive-pre-202603/20251217_crm_offer_presets.sql +++ /dev/null @@ -1,29 +0,0 @@ -CREATE TABLE IF NOT EXISTS crm_offer_presets ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - label TEXT NOT NULL, - description TEXT, - price NUMERIC NOT NULL DEFAULT 0, - created_at TIMESTAMPTZ DEFAULT now(), - updated_at TIMESTAMPTZ DEFAULT now() -); - -ALTER TABLE crm_offer_presets ENABLE ROW LEVEL SECURITY; - --- Allow full access to authenticated users (admins) -CREATE POLICY "Enable all for authenticated users" ON crm_offer_presets - FOR ALL - USING (auth.role() = 'authenticated') - WITH CHECK (auth.role() = 'authenticated'); - --- Allow read access to anon if needed (though offers are usually admin-only) -CREATE POLICY "Enable read for anon" ON crm_offer_presets - FOR SELECT - USING (true); - --- Insert default presets -INSERT INTO crm_offer_presets (label, price, description) VALUES - ('Strona WWW (Standard)', 4500, 'Projekt i wdrożenie strony wizytówki (One Page)'), - ('Sklep Internetowy', 8500, 'Wdrożenie sklepu na platformie WooCommerce/Shopify'), - ('Aplikacja Webowa (MVP)', 15000, 'Budowa prototypu aplikacji w technologii React/Node.js'), - ('Audyt SEO/UX', 2000, 'Analiza użyteczności i widoczności strony w sieci'), - ('Konsultacje (1h)', 350, 'Godzina konsultacji technicznych'); diff --git a/supabase/migrations/_archive-pre-202603/20251218_audit_cleanup.sql b/supabase/migrations/_archive-pre-202603/20251218_audit_cleanup.sql deleted file mode 100644 index ad9a3e2..0000000 --- a/supabase/migrations/_archive-pre-202603/20251218_audit_cleanup.sql +++ /dev/null @@ -1,83 +0,0 @@ --- Database Audit Cleanup Migration - --- 1. Consolidate Trigger Functions -CREATE OR REPLACE FUNCTION handle_updated_at() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = NOW(); - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - --- 2. Add missing updated_at columns -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'project_phases' AND COLUMN_NAME = 'updated_at') THEN - ALTER TABLE project_phases ADD COLUMN updated_at TIMESTAMPTZ DEFAULT NOW(); - END IF; - - IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'time_entries' AND COLUMN_NAME = 'updated_at') THEN - ALTER TABLE time_entries ADD COLUMN updated_at TIMESTAMPTZ DEFAULT NOW(); - END IF; - - IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'briefs' AND COLUMN_NAME = 'updated_at') THEN - ALTER TABLE briefs ADD COLUMN updated_at TIMESTAMPTZ DEFAULT NOW(); - END IF; - - IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'customer_events' AND COLUMN_NAME = 'updated_at') THEN - ALTER TABLE customer_events ADD COLUMN updated_at TIMESTAMPTZ DEFAULT NOW(); - END IF; - - IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'email_templates' AND COLUMN_NAME = 'updated_at') THEN - ALTER TABLE email_templates ADD COLUMN updated_at TIMESTAMPTZ DEFAULT NOW(); - END IF; - - IF NOT EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'offer_versions' AND COLUMN_NAME = 'updated_at') THEN - ALTER TABLE offer_versions ADD COLUMN updated_at TIMESTAMPTZ DEFAULT NOW(); - END IF; -END $$; - --- 3. Apply standard triggers (Cleanup redundant trigger names if any) --- Function handle_updated_at is now the standard. - -DROP TRIGGER IF EXISTS handle_project_phases_updated_at ON project_phases; -CREATE TRIGGER handle_project_phases_updated_at BEFORE UPDATE ON project_phases FOR EACH ROW EXECUTE FUNCTION handle_updated_at(); - -DROP TRIGGER IF EXISTS handle_time_entries_updated_at ON time_entries; -CREATE TRIGGER handle_time_entries_updated_at BEFORE UPDATE ON time_entries FOR EACH ROW EXECUTE FUNCTION handle_updated_at(); - -DROP TRIGGER IF EXISTS handle_briefs_updated_at ON briefs; -CREATE TRIGGER handle_briefs_updated_at BEFORE UPDATE ON briefs FOR EACH ROW EXECUTE FUNCTION handle_updated_at(); - -DROP TRIGGER IF EXISTS handle_customer_events_updated_at ON customer_events; -CREATE TRIGGER handle_customer_events_updated_at BEFORE UPDATE ON customer_events FOR EACH ROW EXECUTE FUNCTION handle_updated_at(); - -DROP TRIGGER IF EXISTS handle_email_templates_updated_at ON email_templates; -CREATE TRIGGER handle_email_templates_updated_at BEFORE UPDATE ON email_templates FOR EACH ROW EXECUTE FUNCTION handle_updated_at(); - -DROP TRIGGER IF EXISTS handle_offer_versions_updated_at ON offer_versions; -CREATE TRIGGER handle_offer_versions_updated_at BEFORE UPDATE ON offer_versions FOR EACH ROW EXECUTE FUNCTION handle_updated_at(); - --- Fix invoices trigger (it used update_updated_at_column which might not exist or be different) -DROP TRIGGER IF EXISTS handle_invoices_updated_at ON invoices; -CREATE TRIGGER handle_invoices_updated_at BEFORE UPDATE ON invoices FOR EACH ROW EXECUTE FUNCTION handle_updated_at(); - --- 4. Standardize Storage Buckets -INSERT INTO storage.buckets (id, name, public) VALUES -('contracts', 'contracts', false), -('media', 'media', true) -ON CONFLICT (id) DO UPDATE SET - public = EXCLUDED.public; - --- 5. Storage Policies for Contracts (Private) -CREATE POLICY "Admin manage contracts" ON storage.objects FOR ALL -USING (bucket_id = 'contracts') -WITH CHECK (bucket_id = 'contracts'); - --- 6. Storage Policies for Media (Publicly readable, Admin manageable) -CREATE POLICY "Public access to media" ON storage.objects FOR SELECT -USING (bucket_id = 'media'); - -CREATE POLICY "Admin manage media" ON storage.objects FOR ALL -USING (bucket_id = 'media') -WITH CHECK (bucket_id = 'media'); diff --git a/supabase/migrations/_archive-pre-202603/20251218_task_revolution.sql b/supabase/migrations/_archive-pre-202603/20251218_task_revolution.sql deleted file mode 100644 index 20acb56..0000000 --- a/supabase/migrations/_archive-pre-202603/20251218_task_revolution.sql +++ /dev/null @@ -1,65 +0,0 @@ --- Migration: Task Management Revolution --- Date: 2025-12-18 --- Author: Antigravity - --- 1. Extend project_tasks table -ALTER TABLE project_tasks -ADD COLUMN IF NOT EXISTS tags text[] DEFAULT '{}', -ADD COLUMN IF NOT EXISTS actual_hours numeric DEFAULT 0; - --- 2. Create Comments Table -CREATE TABLE IF NOT EXISTS project_task_comments ( - id uuid DEFAULT gen_random_uuid() PRIMARY KEY, - task_id uuid REFERENCES project_tasks(id) ON DELETE CASCADE, - user_id uuid REFERENCES auth.users(id) ON DELETE SET NULL, -- Use auth.users for security context - content text NOT NULL, - created_at timestamp with time zone DEFAULT now(), - updated_at timestamp with time zone DEFAULT now() -); - --- 3. Create Checklists Table -CREATE TABLE IF NOT EXISTS project_task_checklists ( - id uuid DEFAULT gen_random_uuid() PRIMARY KEY, - task_id uuid REFERENCES project_tasks(id) ON DELETE CASCADE, - title text NOT NULL, - is_completed boolean DEFAULT false, - order_index integer DEFAULT 0, - created_at timestamp with time zone DEFAULT now() -); - --- 4. Create Attachments Table -CREATE TABLE IF NOT EXISTS project_task_attachments ( - id uuid DEFAULT gen_random_uuid() PRIMARY KEY, - task_id uuid REFERENCES project_tasks(id) ON DELETE CASCADE, - file_url text NOT NULL, - file_type text, - file_size integer, - uploaded_by uuid REFERENCES auth.users(id) ON DELETE SET NULL, - created_at timestamp with time zone DEFAULT now() -); - --- 5. Enable RLS -ALTER TABLE project_task_comments ENABLE ROW LEVEL SECURITY; -ALTER TABLE project_task_checklists ENABLE ROW LEVEL SECURITY; -ALTER TABLE project_task_attachments ENABLE ROW LEVEL SECURITY; - --- 6. Add Policies (Open for now, can be restricted later based on team membership) --- Comments -CREATE POLICY "Enable read access for all authenticated users" ON project_task_comments FOR SELECT TO authenticated USING (true); -CREATE POLICY "Enable insert access for all authenticated users" ON project_task_comments FOR INSERT TO authenticated WITH CHECK (true); -CREATE POLICY "Enable update for own comments" ON project_task_comments FOR UPDATE TO authenticated USING (auth.uid() = user_id); -CREATE POLICY "Enable delete for own comments" ON project_task_comments FOR DELETE TO authenticated USING (auth.uid() = user_id); - --- Checklists -CREATE POLICY "Enable read access for checkboxes" ON project_task_checklists FOR SELECT TO authenticated USING (true); -CREATE POLICY "Enable all access for checkboxes" ON project_task_checklists FOR ALL TO authenticated USING (true); - --- Attachments -CREATE POLICY "Enable read access for attachments" ON project_task_attachments FOR SELECT TO authenticated USING (true); -CREATE POLICY "Enable insert access for attachments" ON project_task_attachments FOR INSERT TO authenticated WITH CHECK (true); -CREATE POLICY "Enable delete for own attachments" ON project_task_attachments FOR DELETE TO authenticated USING (auth.uid() = uploaded_by); - --- 7. Add Realtime -ALTER PUBLICATION supabase_realtime ADD TABLE project_task_comments; -ALTER PUBLICATION supabase_realtime ADD TABLE project_task_checklists; -ALTER PUBLICATION supabase_realtime ADD TABLE project_tasks; diff --git a/supabase/migrations/_archive-pre-202603/20251228_add_services_variants.sql b/supabase/migrations/_archive-pre-202603/20251228_add_services_variants.sql deleted file mode 100644 index f0ff38a..0000000 --- a/supabase/migrations/_archive-pre-202603/20251228_add_services_variants.sql +++ /dev/null @@ -1,25 +0,0 @@ --- Add card_variant column to services table -ALTER TABLE public.services -ADD COLUMN IF NOT EXISTS card_variant text DEFAULT 'tech-inspire' CHECK (card_variant IN ('tech-inspire', 'synergy', 'schema-break', 'idea-effect')); - --- Add storage bucket for services if it doesn't exist -INSERT INTO storage.buckets (id, name, public) -VALUES ('services', 'services', true) -ON CONFLICT (id) DO NOTHING; - --- Policies for services bucket -CREATE POLICY "Public Access" -ON storage.objects FOR SELECT -USING ( bucket_id = 'services' ); - -CREATE POLICY "Admin Upload Access" -ON storage.objects FOR INSERT -WITH CHECK ( bucket_id = 'services' AND (auth.role() = 'authenticated') ); - -CREATE POLICY "Admin Update Access" -ON storage.objects FOR UPDATE -USING ( bucket_id = 'services' AND (auth.role() = 'authenticated') ); - -CREATE POLICY "Admin Delete Access" -ON storage.objects FOR DELETE -USING ( bucket_id = 'services' AND (auth.role() = 'authenticated') ); diff --git a/supabase/migrations/_archive-pre-202603/20251228_fix_comments_schema.sql b/supabase/migrations/_archive-pre-202603/20251228_fix_comments_schema.sql deleted file mode 100644 index 2790cf5..0000000 --- a/supabase/migrations/_archive-pre-202603/20251228_fix_comments_schema.sql +++ /dev/null @@ -1,66 +0,0 @@ --- Migration to fix blog_comments schema: is_approved -> status - --- 1. Add status column with default 'pending' -ALTER TABLE public.blog_comments -ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')); - --- 2. Migrate existing data -UPDATE public.blog_comments -SET status = 'approved' -WHERE is_approved = TRUE; - -UPDATE public.blog_comments -SET status = 'pending' -WHERE is_approved = FALSE OR is_approved IS NULL; - --- 3. Drop is_approved column -ALTER TABLE public.blog_comments -DROP COLUMN IF EXISTS is_approved; - --- 4. Drop old RLS policies -DROP POLICY IF EXISTS "Public can view approved comments" ON public.blog_comments; -DROP POLICY IF EXISTS "Public can add comments" ON public.blog_comments; -DROP POLICY IF EXISTS "Admin can manage all comments" ON public.blog_comments; -DROP POLICY IF EXISTS "Admin can manage blog comments" ON public.blog_comments; -- Possible duplicate from other migrations -DROP POLICY IF EXISTS "Public can view approved blog comments" ON public.blog_comments; -- Possible duplicate -DROP POLICY IF EXISTS "Users can create blog comments" ON public.blog_comments; -- Possible duplicate - --- 5. Create new RLS policies using status - --- Admin can manage everything -CREATE POLICY "Admin can manage all comments" -ON public.blog_comments -FOR ALL -USING (is_admin_user()); - --- Public can view approved comments -CREATE POLICY "Public can view approved comments" -ON public.blog_comments -FOR SELECT -USING ( - status = 'approved' - AND EXISTS ( - SELECT 1 FROM public.blog_posts - WHERE blog_posts.id = blog_comments.post_id - AND blog_posts.status = 'published' - AND blog_posts.allow_comments = true - ) -); - --- Public can add comments (status will be pending by default) -CREATE POLICY "Public can add comments" -ON public.blog_comments -FOR INSERT -WITH CHECK ( - EXISTS ( - SELECT 1 FROM public.blog_posts - WHERE blog_posts.id = blog_comments.post_id - AND blog_posts.status = 'published' -- Only on published posts - AND blog_posts.allow_comments = true - ) -); - --- Grant permissions (just in case) -GRANT SELECT, INSERT ON public.blog_comments TO anon; -GRANT SELECT, INSERT ON public.blog_comments TO authenticated; -GRANT ALL ON public.blog_comments TO authenticated; -- Admin permissions handled by RLS, but authenticated need basic access diff --git a/supabase/migrations/_archive-pre-202603/20251228_fix_portfolio_schema.sql b/supabase/migrations/_archive-pre-202603/20251228_fix_portfolio_schema.sql deleted file mode 100644 index dee9103..0000000 --- a/supabase/migrations/_archive-pre-202603/20251228_fix_portfolio_schema.sql +++ /dev/null @@ -1,41 +0,0 @@ --- Add Case Study columns to project_translations -ALTER TABLE public.project_translations -ADD COLUMN IF NOT EXISTS challenge text, -ADD COLUMN IF NOT EXISTS solution text, -ADD COLUMN IF NOT EXISTS result text; - --- Remove restrictive CHECK constraint on category column in projects table -ALTER TABLE public.projects DROP CONSTRAINT IF EXISTS projects_category_check; - --- Create project_categories table if it doesn't exist -CREATE TABLE IF NOT EXISTS public.project_categories ( - id uuid DEFAULT gen_random_uuid() PRIMARY KEY, - slug text NOT NULL UNIQUE, - name_pl text NOT NULL, - name_en text NOT NULL, - order_index integer DEFAULT 0, - created_at timestamptz DEFAULT now() -); - --- Enable RLS for project_categories -ALTER TABLE public.project_categories ENABLE ROW LEVEL SECURITY; - --- Create policies for project_categories --- Public read access -CREATE POLICY "Public can view project categories" ON public.project_categories - FOR SELECT - USING (true); - --- Admin full access -CREATE POLICY "Admins can manage project categories" ON public.project_categories - FOR ALL - USING ( - EXISTS ( - SELECT 1 FROM public.admins - WHERE admins.username = auth.jwt() ->> 'email' AND admins.is_active = true - ) - ); - --- Grant permissions -GRANT SELECT ON public.project_categories TO anon, authenticated; -GRANT ALL ON public.project_categories TO authenticated; diff --git a/supabase/migrations/_archive-pre-202603/20251229_media_library_security.sql b/supabase/migrations/_archive-pre-202603/20251229_media_library_security.sql deleted file mode 100644 index 0912ab7..0000000 --- a/supabase/migrations/_archive-pre-202603/20251229_media_library_security.sql +++ /dev/null @@ -1,74 +0,0 @@ --- Migration: Secure Media Library Buckets (2025-12-29) - --- 1. Ensure 'offer-assets' bucket exists and is PRIVATE -INSERT INTO storage.buckets (id, name, public) -VALUES ('offer-assets', 'offer-assets', false) -ON CONFLICT (id) DO UPDATE SET public = false; - --- 2. Ensure 'contracts' bucket exists and is PRIVATE -INSERT INTO storage.buckets (id, name, public) -VALUES ('contracts', 'contracts', false) -ON CONFLICT (id) DO UPDATE SET public = false; - --- 3. Ensure public buckets are PUBLIC -INSERT INTO storage.buckets (id, name, public) -VALUES - ('media', 'media', true), - ('portfolio', 'portfolio', true), - ('blog', 'blog', true), - ('services', 'services', true) -ON CONFLICT (id) DO UPDATE SET public = true; - - --- 4. RLS Policies for 'offer-assets' (STRICT PRIVATE: Admin Only) -DROP POLICY IF EXISTS "Public Access offer-assets" ON storage.objects; -DROP POLICY IF EXISTS "Admin Manage offer-assets" ON storage.objects; - -CREATE POLICY "Admin Manage offer-assets" ON storage.objects -FOR ALL -TO authenticated -USING (bucket_id = 'offer-assets') -WITH CHECK (bucket_id = 'offer-assets'); - - --- 5. RLS Policies for 'contracts' (STRICT PRIVATE: Admin Only) -DROP POLICY IF EXISTS "Public Access contracts" ON storage.objects; -DROP POLICY IF EXISTS "Admin Manage contracts" ON storage.objects; - -CREATE POLICY "Admin Manage contracts" ON storage.objects -FOR ALL -TO authenticated -USING (bucket_id = 'contracts') -WITH CHECK (bucket_id = 'contracts'); - - --- 6. Ensure Public Access for 'media' (READ ONLY for Public, WRITE for Admin) -DROP POLICY IF EXISTS "Public Read media" ON storage.objects; -DROP POLICY IF EXISTS "Admin Manage media" ON storage.objects; - -CREATE POLICY "Public Read media" ON storage.objects -FOR SELECT -TO public -USING (bucket_id = 'media'); - -CREATE POLICY "Admin Manage media" ON storage.objects -FOR ALL -TO authenticated -USING (bucket_id = 'media') -WITH CHECK (bucket_id = 'media'); - - --- 7. Ensure Public Access for 'services' (READ ONLY for Public, WRITE for Admin) -DROP POLICY IF EXISTS "Public Read services" ON storage.objects; -DROP POLICY IF EXISTS "Admin Manage services" ON storage.objects; - -CREATE POLICY "Public Read services" ON storage.objects -FOR SELECT -TO public -USING (bucket_id = 'services'); - -CREATE POLICY "Admin Manage services" ON storage.objects -FOR ALL -TO authenticated -USING (bucket_id = 'services') -WITH CHECK (bucket_id = 'services'); diff --git a/supabase/migrations/_archive-pre-202603/20251230_employee_details.sql b/supabase/migrations/_archive-pre-202603/20251230_employee_details.sql deleted file mode 100644 index 9df1a2d..0000000 --- a/supabase/migrations/_archive-pre-202603/20251230_employee_details.sql +++ /dev/null @@ -1,14 +0,0 @@ --- Add new columns to the employees table -ALTER TABLE employees -ADD COLUMN IF NOT EXISTS salary_type text DEFAULT 'monthly', -ADD COLUMN IF NOT EXISTS contract_url text, -ADD COLUMN IF NOT EXISTS employment_end_date date; - --- Add check constraint for salary_type -DO $$ -BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'employees_salary_type_check') THEN - ALTER TABLE employees - ADD CONSTRAINT employees_salary_type_check CHECK (salary_type IN ('monthly', 'hourly', 'project', 'task')); - END IF; -END $$; diff --git a/supabase/migrations/_archive-pre-202603/20251230_faq_enhancements.sql b/supabase/migrations/_archive-pre-202603/20251230_faq_enhancements.sql deleted file mode 100644 index 7b47d4a..0000000 --- a/supabase/migrations/_archive-pre-202603/20251230_faq_enhancements.sql +++ /dev/null @@ -1,40 +0,0 @@ --- Migration: FAQ Enhancements (2025-12-30) --- 1. Add is_active column to faq_categories -ALTER TABLE public.faq_categories -ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT true; - --- 2. Add is_active column to faq_items -ALTER TABLE public.faq_items -ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT true; - --- 3. Update Public RLS for Categories (Only Active) -DROP POLICY IF EXISTS "Public can view faq categories" ON public.faq_categories; -CREATE POLICY "Public can view faq categories" -ON public.faq_categories -FOR SELECT -USING (is_active = true); - --- 4. Update Public RLS for Items (Only Active) -DROP POLICY IF EXISTS "Public can view faq items" ON public.faq_items; -CREATE POLICY "Public can view faq items" -ON public.faq_items -FOR SELECT -USING (is_active = true); - --- 5. Admin RLS (View All) - Already covered by existing admin policies or logic? --- Let's ensure Admins can see EVERYTHING (active or inactive). --- The previous migration had: --- CREATE POLICY "Admins can insert/update/delete..." using check_permission. --- We usually need a "Admins can view all" policy if the "Public" one is restrictive. - -DROP POLICY IF EXISTS "Admins can view all faq categories" ON public.faq_categories; -CREATE POLICY "Admins can view all faq categories" -ON public.faq_categories -FOR SELECT -USING (public.check_permission('faq.view')); - -DROP POLICY IF EXISTS "Admins can view all faq items" ON public.faq_items; -CREATE POLICY "Admins can view all faq items" -ON public.faq_items -FOR SELECT -USING (public.check_permission('faq.view')); diff --git a/supabase/migrations/_archive-pre-202603/20251230_finance_bucket.sql b/supabase/migrations/_archive-pre-202603/20251230_finance_bucket.sql deleted file mode 100644 index e348a84..0000000 --- a/supabase/migrations/_archive-pre-202603/20251230_finance_bucket.sql +++ /dev/null @@ -1,25 +0,0 @@ --- Create a private 'finance' bucket if it doesn't exist -INSERT INTO storage.buckets (id, name, public) -VALUES ('finance', 'finance', false) -ON CONFLICT (id) DO NOTHING; - --- Policy: Admin can do everything in 'finance' bucket -CREATE POLICY "Admin Access to Finance Bucket" -ON storage.objects -FOR ALL -TO authenticated -USING ( - bucket_id = 'finance' - AND public.check_permission('finance.view') = true -) -WITH CHECK ( - bucket_id = 'finance' - AND public.check_permission('finance.view') = true -); - --- Ensure transactions table has a receipt_url column just in case -ALTER TABLE public.transactions -ADD COLUMN IF NOT EXISTS receipt_url TEXT; - --- Update RLS for transactions to be super safe (already done in previous migration, but double check) -ALTER TABLE public.transactions ENABLE ROW LEVEL SECURITY; diff --git a/supabase/migrations/_archive-pre-202603/20251230_finance_enhancements.sql b/supabase/migrations/_archive-pre-202603/20251230_finance_enhancements.sql deleted file mode 100644 index 31ffc2a..0000000 --- a/supabase/migrations/_archive-pre-202603/20251230_finance_enhancements.sql +++ /dev/null @@ -1,6 +0,0 @@ --- Add payment_deadline to transactions for tracking payables -ALTER TABLE transactions ADD COLUMN IF NOT EXISTS payment_deadline DATE; - --- Ensure status check includes pending -ALTER TABLE transactions DROP CONSTRAINT IF EXISTS transactions_status_check; -ALTER TABLE transactions ADD CONSTRAINT transactions_status_check CHECK (status IN ('pending', 'completed', 'cancelled')); diff --git a/supabase/migrations/_archive-pre-202603/20251230_finance_security_fix.sql b/supabase/migrations/_archive-pre-202603/20251230_finance_security_fix.sql deleted file mode 100644 index 7f3920e..0000000 --- a/supabase/migrations/_archive-pre-202603/20251230_finance_security_fix.sql +++ /dev/null @@ -1,19 +0,0 @@ --- Migration: Secure Invoices & Cleanup (2025-12-30) - --- 1. Drop obsolete expenses table (replaced by transactions) -DROP TABLE IF EXISTS public.expenses; - --- 2. Secure INVOICES table (Upgrade from generic 'authenticated' to 'finance.view') --- Drop existing weak policies -DROP POLICY IF EXISTS "Allow read access for authenticated users" ON public.invoices; -DROP POLICY IF EXISTS "Allow write access for authenticated users" ON public.invoices; -DROP POLICY IF EXISTS "Allow update access for authenticated users" ON public.invoices; -DROP POLICY IF EXISTS "Allow delete access for authenticated users" ON public.invoices; - --- Add strict RBAC policy -CREATE POLICY "Admins can manage invoices" ON public.invoices - FOR ALL - USING (public.check_permission('finance.view')); - --- 3. Ensure transactions has correct policy (already done in previous migration, but good to be safe/consistent if re-running) --- (Skipping as 20251230_hr_finance_schema.sql handles it perfectly) diff --git a/supabase/migrations/_archive-pre-202603/20251230_hr_finance_schema.sql b/supabase/migrations/_archive-pre-202603/20251230_hr_finance_schema.sql deleted file mode 100644 index 6f01afa..0000000 --- a/supabase/migrations/_archive-pre-202603/20251230_hr_finance_schema.sql +++ /dev/null @@ -1,82 +0,0 @@ --- Migration: HR & Finance Schema (2025-12-30) - --- 1. Create EMPLOYEES table -CREATE TABLE IF NOT EXISTS public.employees ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - user_id UUID REFERENCES auth.users(id), -- Optional link to auth user - first_name TEXT NOT NULL, - last_name TEXT NOT NULL, - email TEXT NOT NULL, - position TEXT, - department TEXT, - salary NUMERIC(10, 2), - currency TEXT DEFAULT 'PLN', - employment_type TEXT DEFAULT 'B2B', -- B2B, UoP, Uzl - status TEXT DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'on_leave')), - hired_at DATE DEFAULT CURRENT_DATE, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- 2. Create LEAVE_REQUESTS table -CREATE TABLE IF NOT EXISTS public.leave_requests ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - employee_id UUID REFERENCES public.employees(id) ON DELETE CASCADE, - type TEXT NOT NULL CHECK (type IN ('vacation', 'sick', 'on_demand', 'other')), - start_date DATE NOT NULL, - end_date DATE NOT NULL, - days INTEGER NOT NULL DEFAULT 1, - status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')), - reason TEXT, - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- 3. Create TRANSACTIONS table -CREATE TABLE IF NOT EXISTS public.transactions ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - description TEXT NOT NULL, - amount NUMERIC(12, 2) NOT NULL, - type TEXT NOT NULL CHECK (type IN ('income', 'expense')), - category TEXT DEFAULT 'general', - date DATE DEFAULT CURRENT_DATE, - invoice_id UUID REFERENCES public.invoices(id) ON DELETE SET NULL, -- Link to invoice if applicable - status TEXT DEFAULT 'completed' CHECK (status IN ('pending', 'completed', 'cancelled')), - created_at TIMESTAMPTZ DEFAULT NOW(), - updated_at TIMESTAMPTZ DEFAULT NOW() -); - --- Enable RLS -ALTER TABLE public.employees ENABLE ROW LEVEL SECURITY; -ALTER TABLE public.leave_requests ENABLE ROW LEVEL SECURITY; -ALTER TABLE public.transactions ENABLE ROW LEVEL SECURITY; - --- RLS Policies: EMPLOYEES -DROP POLICY IF EXISTS "Admins can manage employees" ON public.employees; -CREATE POLICY "Admins can manage employees" ON public.employees - FOR ALL - USING (public.check_permission('hr.view')); -- Using general permission check or create specific hr permissions - --- RLS Policies: LEAVE_REQUESTS -DROP POLICY IF EXISTS "Admins can manage leave requests" ON public.leave_requests; -CREATE POLICY "Admins can manage leave requests" ON public.leave_requests - FOR ALL - USING (public.check_permission('hr.view')); - --- RLS Policies: TRANSACTIONS -DROP POLICY IF EXISTS "Admins can manage transactions" ON public.transactions; -CREATE POLICY "Admins can manage transactions" ON public.transactions - FOR ALL - USING (public.check_permission('finance.view')); - --- Create Triggers for updated_at -DROP TRIGGER IF EXISTS on_auth_user_created ON public.employees; --- Reuse existing handle_updated_at function -CREATE TRIGGER handle_updated_at BEFORE UPDATE ON public.employees - FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at(); - -CREATE TRIGGER handle_updated_at BEFORE UPDATE ON public.leave_requests - FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at(); - -CREATE TRIGGER handle_updated_at BEFORE UPDATE ON public.transactions - FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at(); diff --git a/supabase/migrations/_archive-pre-202603/20260106_contact_replies.sql b/supabase/migrations/_archive-pre-202603/20260106_contact_replies.sql deleted file mode 100644 index a383f61..0000000 --- a/supabase/migrations/_archive-pre-202603/20260106_contact_replies.sql +++ /dev/null @@ -1,38 +0,0 @@ --- Create contact_replies table to track admin responses -CREATE TABLE IF NOT EXISTS public.contact_replies ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - message_id UUID NOT NULL REFERENCES public.contact_messages(id) ON DELETE CASCADE, - admin_id UUID NOT NULL REFERENCES auth.users(id), - subject TEXT NOT NULL, - body TEXT NOT NULL, - sent_at TIMESTAMPTZ DEFAULT now() -); - --- Enable RLS -ALTER TABLE public.contact_replies ENABLE ROW LEVEL SECURITY; - --- Policies for contact_replies -CREATE POLICY "Admins can view replies" ON public.contact_replies - FOR SELECT TO authenticated - USING ((select auth.uid()) IN (SELECT id FROM admin_users)); - -CREATE POLICY "Admins can insert replies" ON public.contact_replies - FOR INSERT TO authenticated - WITH CHECK ((select auth.uid()) IN (SELECT id FROM admin_users)); - --- Add trigger to update parent message status on reply -CREATE OR REPLACE FUNCTION public.update_message_status_on_reply() -RETURNS TRIGGER AS $$ -BEGIN - UPDATE public.contact_messages - SET status = 'replied' - WHERE id = NEW.message_id - AND status != 'replied'; - RETURN NEW; -END; -$$ LANGUAGE plpgsql SECURITY DEFINER; - -CREATE TRIGGER on_reply_sent - AFTER INSERT ON public.contact_replies - FOR EACH ROW - EXECUTE FUNCTION public.update_message_status_on_reply(); diff --git a/supabase/migrations/_archive-pre-202603/20260106_final_rls_optimization.sql b/supabase/migrations/_archive-pre-202603/20260106_final_rls_optimization.sql deleted file mode 100644 index 0a679b2..0000000 --- a/supabase/migrations/_archive-pre-202603/20260106_final_rls_optimization.sql +++ /dev/null @@ -1,161 +0,0 @@ --- FINAL COMPREHENSIVE RLS OPTIMIZATION --- Resolve ALL 111 warnings (auth_rls_initplan & multiple_permissive_policies) - --- ============================================================================ --- 1. AUTH RLS INITPLAN OPTIMIZATIONS (select auth.function()) --- ============================================================================ - --- Table: crm_offers -ALTER POLICY "Admins can view all offers" ON public.crm_offers USING ( - (EXISTS ( SELECT 1 FROM admin_users WHERE admin_users.id = (select auth.uid()))) OR - ((select auth.role()) = 'service_role') -); -ALTER POLICY "Admins can insert offers" ON public.crm_offers WITH CHECK ( - (EXISTS ( SELECT 1 FROM admin_users WHERE admin_users.id = (select auth.uid()))) OR - ((select auth.role()) = 'service_role') -); -ALTER POLICY "Admins can update offers" ON public.crm_offers USING ( - (EXISTS ( SELECT 1 FROM admin_users WHERE admin_users.id = (select auth.uid()))) OR - ((select auth.role()) = 'service_role') -); -ALTER POLICY "Admins can delete offers" ON public.crm_offers USING ( - (EXISTS ( SELECT 1 FROM admin_users WHERE admin_users.id = (select auth.uid()))) OR - ((select auth.role()) = 'service_role') -); - --- Table: project_task_comments -ALTER POLICY "Enable update for own comments" ON public.project_task_comments USING ((select auth.uid()) = user_id); -ALTER POLICY "Enable delete for own comments" ON public.project_task_comments USING ((select auth.uid()) = user_id); - --- Table: social_posts -ALTER POLICY "Users can view their own posts" ON public.social_posts USING ((select auth.uid()) = created_by); -ALTER POLICY "Users can insert their own posts" ON public.social_posts WITH CHECK ((select auth.uid()) = created_by); -ALTER POLICY "Users can update their own posts" ON public.social_posts USING ((select auth.uid()) = created_by); -ALTER POLICY "Users can delete their own posts" ON public.social_posts USING ((select auth.uid()) = created_by); - --- Table: project_files -ALTER POLICY "Clients View Shared Files" ON public.project_files -USING ( - (is_shared_with_client = true) AND - (project_id IN ( - SELECT projects.id FROM projects - WHERE projects.client_id IN ( - SELECT clients.id FROM clients - WHERE clients.user_id = (select auth.uid()) - ) - )) -); - --- Table: admin_role_permissions -ALTER POLICY "Enable all access for admins" ON public.admin_role_permissions -USING ((select auth.uid()) IN ( SELECT id FROM admin_users)); - --- Table: system_settings -ALTER POLICY "Admins can manage system settings" ON public.system_settings -USING ( - ((select auth.uid()) IN ( SELECT id FROM admin_users)) OR - ((select auth.role()) = 'service_role'::text) -); - --- ============================================================================ --- 2. MULTIPLE PERMISSIVE POLICIES CONSOLIDATION --- ============================================================================ - --- Table: case_studies -DROP POLICY IF EXISTS "Anyone can view case studies" ON public.case_studies; -DROP POLICY IF EXISTS "Authenticated users can manage case studies" ON public.case_studies; -CREATE POLICY "Public view published" ON public.case_studies FOR SELECT TO public USING (status = 'published'); -CREATE POLICY "Admins manage" ON public.case_studies FOR ALL TO authenticated USING ((select auth.role()) = 'authenticated'); - --- Table: crm_offer_presets -DROP POLICY IF EXISTS "Authenticated users manage presets" ON public.crm_offer_presets; -DROP POLICY IF EXISTS "Enable read for anon" ON public.crm_offer_presets; -CREATE POLICY "Public view offer presets" ON public.crm_offer_presets FOR SELECT TO public USING (true); -CREATE POLICY "Admins manage offer presets" ON public.crm_offer_presets FOR ALL TO authenticated USING ((select auth.role()) = 'authenticated'); - --- Table: faq_categories -DROP POLICY IF EXISTS "Admins can view all faq categories" ON public.faq_categories; -DROP POLICY IF EXISTS "Public can view faq categories" ON public.faq_categories; -DROP POLICY IF EXISTS "Public view" ON public.faq_categories; -CREATE POLICY "FAQ Categories Select" ON public.faq_categories FOR SELECT TO public USING (true); -CREATE POLICY "FAQ Categories Manage" ON public.faq_categories FOR ALL TO authenticated USING ((select auth.role()) = 'authenticated'); - --- Table: faq_items -DROP POLICY IF EXISTS "Admins can view all faq items" ON public.faq_items; -DROP POLICY IF EXISTS "Public can view faq items" ON public.faq_items; -DROP POLICY IF EXISTS "Public view" ON public.faq_items; -CREATE POLICY "FAQ Items Select" ON public.faq_items FOR SELECT TO public USING (true); -CREATE POLICY "FAQ Items Manage" ON public.faq_items FOR ALL TO authenticated USING ((select auth.role()) = 'authenticated'); - --- Table: invoices -DROP POLICY IF EXISTS "Admin access invoices" ON public.invoices; -DROP POLICY IF EXISTS "Admins can manage invoices" ON public.invoices; -DROP POLICY IF EXISTS "Enable all access for authenticated users" ON public.invoices; -CREATE POLICY "Invoices Manage All" ON public.invoices FOR ALL TO authenticated USING ((select auth.role()) = 'authenticated'); - --- Table: leads -DROP POLICY IF EXISTS "Allow full access for admins" ON public.leads; -DROP POLICY IF EXISTS "Enable all for authenticated users" ON public.leads; -CREATE POLICY "Leads Manage All" ON public.leads FOR ALL TO authenticated USING ((select auth.role()) = 'authenticated'); - --- Table: newsletter_campaigns -DROP POLICY IF EXISTS "Admins can manage all campaigns" ON public.newsletter_campaigns; -DROP POLICY IF EXISTS "Users can insert campaigns" ON public.newsletter_campaigns; -DROP POLICY IF EXISTS "Users can update campaigns" ON public.newsletter_campaigns; -DROP POLICY IF EXISTS "Users can view campaigns" ON public.newsletter_campaigns; -CREATE POLICY "Newsletter Campaigns Manage All" ON public.newsletter_campaigns FOR ALL TO authenticated USING ((select auth.role()) = 'authenticated'); - --- Table: project_briefs -DROP POLICY IF EXISTS "Admins All Briefs" ON public.project_briefs; -DROP POLICY IF EXISTS "Clients View Briefs" ON public.project_briefs; -DROP POLICY IF EXISTS "Clients Update Briefs" ON public.project_briefs; -CREATE POLICY "Project Briefs Admin Manage" ON public.project_briefs FOR ALL TO authenticated USING ((select auth.role()) = 'authenticated'); -CREATE POLICY "Project Briefs Client Access" ON public.project_briefs FOR SELECT TO authenticated -USING (project_id IN (SELECT id FROM projects WHERE client_id IN (SELECT id FROM clients WHERE user_id = (select auth.uid())))); - --- Table: project_categories -DROP POLICY IF EXISTS "Admin delete" ON public.project_categories; -DROP POLICY IF EXISTS "Admin insert" ON public.project_categories; -DROP POLICY IF EXISTS "Admin update" ON public.project_categories; -DROP POLICY IF EXISTS "Admins can manage project categories" ON public.project_categories; -DROP POLICY IF EXISTS "Public can view project categories" ON public.project_categories; -DROP POLICY IF EXISTS "Public view" ON public.project_categories; -CREATE POLICY "Project Categories Select" ON public.project_categories FOR SELECT TO public USING (true); -CREATE POLICY "Project Categories Manage" ON public.project_categories FOR ALL TO authenticated USING ((select auth.role()) = 'authenticated'); - --- Table: project_files -DROP POLICY IF EXISTS "Admins All Files" ON public.project_files; --- Note: "Clients View Shared Files" is kept & optimized in section 1 -CREATE POLICY "Project Files Admin Manage" ON public.project_files FOR ALL TO authenticated USING ((select auth.role()) = 'authenticated'); - --- Table: project_task_checklists -DROP POLICY IF EXISTS "Enable all access for checkboxes" ON public.project_task_checklists; -DROP POLICY IF EXISTS "Enable read access for checkboxes" ON public.project_task_checklists; -CREATE POLICY "Project Task Checklists Manage" ON public.project_task_checklists FOR ALL TO authenticated USING ((select auth.role()) = 'authenticated'); - --- Table: project_tasks -DROP POLICY IF EXISTS "Admins can manage tasks" ON public.project_tasks; -DROP POLICY IF EXISTS "Clients can view tasks for their projects" ON public.project_tasks; -CREATE POLICY "Project Tasks Admin Manage" ON public.project_tasks FOR ALL TO authenticated USING ((select auth.role()) = 'authenticated'); -CREATE POLICY "Project Tasks Client View" ON public.project_tasks FOR SELECT TO authenticated -USING (project_id IN (SELECT id FROM projects WHERE client_id IN (SELECT id FROM clients WHERE user_id = (select auth.uid())))); - --- Table: projects -DROP POLICY IF EXISTS "Clients View Own Projects" ON public.projects; -DROP POLICY IF EXISTS "Consolidated view" ON public.projects; -CREATE POLICY "Projects Admin Manage" ON public.projects FOR ALL TO authenticated USING ((select auth.role()) = 'authenticated'); -CREATE POLICY "Projects Client View" ON public.projects FOR SELECT TO authenticated -USING (client_id IN (SELECT id FROM clients WHERE user_id = (select auth.uid()))); - --- Table: social_posts (Cleanup of multiple permissive) -DROP POLICY IF EXISTS "Admin manage social posts" ON public.social_posts; -DROP POLICY IF EXISTS "Admin manage all social posts" ON public.social_posts; --- Individual owner policies are in Section 1. Adding Admin access to them: -ALTER POLICY "Users can view their own posts" ON public.social_posts USING ((select auth.uid()) = created_by OR (select auth.role()) = 'authenticated'); -ALTER POLICY "Users can update their own posts" ON public.social_posts USING ((select auth.uid()) = created_by OR (select auth.role()) = 'authenticated'); -ALTER POLICY "Users can delete their own posts" ON public.social_posts USING ((select auth.uid()) = created_by OR (select auth.role()) = 'authenticated'); - --- Table: tenants -DROP POLICY IF EXISTS "Authenticated and above read tenants" ON public.tenants; -CREATE POLICY "Tenants Public Read" ON public.tenants FOR SELECT TO public USING (true); - diff --git a/supabase/migrations/_archive-pre-202603/20260107_email_sync_schema.sql b/supabase/migrations/_archive-pre-202603/20260107_email_sync_schema.sql deleted file mode 100644 index 32bdb48..0000000 --- a/supabase/migrations/_archive-pre-202603/20260107_email_sync_schema.sql +++ /dev/null @@ -1,45 +0,0 @@ --- Migration: Add support for Email Synchronization (IMAP) --- Date: 2026-01-07 - --- 1. Add columns to 'contact_replies' for threading and direction -ALTER TABLE public.contact_replies -ADD COLUMN IF NOT EXISTS external_id TEXT UNIQUE, -- Stores Message-ID header -ADD COLUMN IF NOT EXISTS direction TEXT CHECK (direction IN ('inbound', 'outbound')) DEFAULT 'outbound', -ADD COLUMN IF NOT EXISTS raw_data JSONB; -- Stores debugging info - --- 2. Add columns to 'contact_messages' for threading initial emails -ALTER TABLE public.contact_messages -ADD COLUMN IF NOT EXISTS external_id TEXT UNIQUE, -- Stores Message-ID of the original email -ADD COLUMN IF NOT EXISTS last_message_at TIMESTAMPTZ DEFAULT now(); - --- 3. Index for performance on identifying threads -CREATE INDEX IF NOT EXISTS idx_contact_replies_external_id ON public.contact_replies(external_id); -CREATE INDEX IF NOT EXISTS idx_contact_messages_external_id ON public.contact_messages(external_id); - --- 4. Update the trigger to update last_message_at on new reply -CREATE OR REPLACE FUNCTION public.update_message_timestamp_on_reply() -RETURNS TRIGGER AS $$ -BEGIN - UPDATE public.contact_messages - SET last_message_at = NEW.sent_at, - status = CASE - WHEN NEW.direction = 'outbound' THEN 'replied' - WHEN NEW.direction = 'inbound' THEN 'new' -- Re-open ticket if client replies - ELSE status - END - WHERE id = NEW.message_id; - RETURN NEW; -END; -$$ LANGUAGE plpgsql SECURITY DEFINER; - --- Re-attach or create trigger (drop old one if needed to replace logic) -DROP TRIGGER IF EXISTS on_reply_timestamp_update ON public.contact_replies; -CREATE TRIGGER on_reply_timestamp_update -AFTER INSERT ON public.contact_replies -FOR EACH ROW -EXECUTE FUNCTION public.update_message_timestamp_on_reply(); - --- Also ensure the old trigger 'on_reply_sent' doesn't conflict or is removed if this replaces it. --- The old function 'update_message_status_on_reply' only set status to 'replied'. --- We can drop the old trigger to avoid double updates. -DROP TRIGGER IF EXISTS on_reply_sent ON public.contact_replies; diff --git a/supabase/migrations/_archive-pre-202603/20260120_fix_storage_security.sql b/supabase/migrations/_archive-pre-202603/20260120_fix_storage_security.sql deleted file mode 100644 index c6157a6..0000000 --- a/supabase/migrations/_archive-pre-202603/20260120_fix_storage_security.sql +++ /dev/null @@ -1,126 +0,0 @@ --- Migration: Strict Media Library Clean Up (2026-01-20) --- Purpose: Fix security loophole where authenticated users (clients) could list all contracts. - --- ============================================================================ --- 1. CONTRACTS BUCKET (STRICT PRIVATE: ADMIN ONLY) --- ============================================================================ --- Problem: Previous policy 'TO authenticated' allowed any logged-in user to view contracts. --- Fix: Restrict to Admins or Service Role only. Clients must use Signed URLs generated by backend. - -DROP POLICY IF EXISTS "Admin Manage contracts" ON storage.objects; -DROP POLICY IF EXISTS "Authenticated users can upload contracts" ON storage.objects; -DROP POLICY IF EXISTS "Public Access contracts" ON storage.objects; - -CREATE POLICY "Strict Admin Manage contracts" ON storage.objects -FOR ALL -TO authenticated -USING ( - bucket_id = 'contracts' - AND ( - (SELECT auth.role()) = 'service_role' - OR - EXISTS (SELECT 1 FROM public.admin_users WHERE id = (select auth.uid())) - ) -) -WITH CHECK ( - bucket_id = 'contracts' - AND ( - (SELECT auth.role()) = 'service_role' - OR - EXISTS (SELECT 1 FROM public.admin_users WHERE id = (select auth.uid())) - ) -); - --- ============================================================================ --- 2. OFFER ASSETS BUCKET (STRICT PRIVATE: ADMIN ONLY) --- ============================================================================ - -DROP POLICY IF EXISTS "Admin Manage offer-assets" ON storage.objects; -DROP POLICY IF EXISTS "Public Access offer-assets" ON storage.objects; - -CREATE POLICY "Strict Admin Manage offer-assets" ON storage.objects -FOR ALL -TO authenticated -USING ( - bucket_id = 'offer-assets' - AND ( - (SELECT auth.role()) = 'service_role' - OR - EXISTS (SELECT 1 FROM public.admin_users WHERE id = (select auth.uid())) - ) -) -WITH CHECK ( - bucket_id = 'offer-assets' - AND ( - (SELECT auth.role()) = 'service_role' - OR - EXISTS (SELECT 1 FROM public.admin_users WHERE id = (select auth.uid())) - ) -); - --- ============================================================================ --- 3. PUBLIC BUCKETS (MEDIA, PORTFOLIO, BLOG, SERVICES) --- ============================================================================ --- Ensure Public Read, but Admin Write Only. - --- Function to apply policies for public buckets to avoid repetition -DO $$ -DECLARE - b text; -BEGIN - FOREACH b IN ARRAY ARRAY['media', 'portfolio', 'blog', 'services'] LOOP - - -- Drop potentially loose policies - EXECUTE format('DROP POLICY IF EXISTS "Admin Manage %I" ON storage.objects', b); - EXECUTE format('DROP POLICY IF EXISTS "Public Read %I" ON storage.objects', b); - - -- Create Public Read Policy - EXECUTE format(' - CREATE POLICY "Public Read %I" ON storage.objects - FOR SELECT - TO public - USING (bucket_id = %L)', b, b); - - -- Create Strict Admin Write Policy - EXECUTE format(' - CREATE POLICY "Strict Admin Write %I" ON storage.objects - FOR INSERT - TO authenticated - WITH CHECK ( - bucket_id = %L - AND ( - (SELECT auth.role()) = ''service_role'' - OR - EXISTS (SELECT 1 FROM public.admin_users WHERE id = (select auth.uid())) - ) - )', b, b); - - -- Create Strict Admin Update/Delete Policy - EXECUTE format(' - CREATE POLICY "Strict Admin Manage %I" ON storage.objects - FOR UPDATE - TO authenticated - USING ( - bucket_id = %L - AND ( - (SELECT auth.role()) = ''service_role'' - OR - EXISTS (SELECT 1 FROM public.admin_users WHERE id = (select auth.uid())) - ) - )', b, b); - - EXECUTE format(' - CREATE POLICY "Strict Admin Delete %I" ON storage.objects - FOR DELETE - TO authenticated - USING ( - bucket_id = %L - AND ( - (SELECT auth.role()) = ''service_role'' - OR - EXISTS (SELECT 1 FROM public.admin_users WHERE id = (select auth.uid())) - ) - )', b, b); - - END LOOP; -END$$; diff --git a/supabase/migrations/_archive-pre-202603/20260203_security_hardening.sql b/supabase/migrations/_archive-pre-202603/20260203_security_hardening.sql deleted file mode 100644 index db97704..0000000 --- a/supabase/migrations/_archive-pre-202603/20260203_security_hardening.sql +++ /dev/null @@ -1,176 +0,0 @@ --- 20260203 - Security hardening for RLS and admin tables - --- ============================================================================ --- 1. Tighten RLS for admin_roles (only real admins can mutate) --- ============================================================================ - -ALTER TABLE IF EXISTS public.admin_roles ENABLE ROW LEVEL SECURITY; - -DROP POLICY IF EXISTS "Enable delete for authenticated users only" ON public.admin_roles; -DROP POLICY IF EXISTS "Enable insert for authenticated users only" ON public.admin_roles; -DROP POLICY IF EXISTS "Enable update for authenticated users only" ON public.admin_roles; - -CREATE POLICY "Admin manage roles" ON public.admin_roles - FOR ALL TO authenticated - USING ( - (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ) - WITH CHECK ( - (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ); - --- ============================================================================ --- 2. Tighten RLS for audit_logs (system/admin only) --- ============================================================================ - -ALTER TABLE IF EXISTS public.audit_logs ENABLE ROW LEVEL SECURITY; - -DROP POLICY IF EXISTS "System can insert audit logs" ON public.audit_logs; - -CREATE POLICY "System or admin insert audit logs" ON public.audit_logs - FOR INSERT TO authenticated - WITH CHECK ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ); - --- ============================================================================ --- 3. Project task attachments/comments - only admins/service_role can insert --- ============================================================================ - -ALTER TABLE IF EXISTS public.project_task_attachments ENABLE ROW LEVEL SECURITY; -ALTER TABLE IF EXISTS public.project_task_comments ENABLE ROW LEVEL SECURITY; - -DROP POLICY IF EXISTS "Enable insert access for attachments" ON public.project_task_attachments; -DROP POLICY IF EXISTS "Enable insert access for all authenticated users" ON public.project_task_comments; - -CREATE POLICY "Project task attachments insert (admin only)" ON public.project_task_attachments - FOR INSERT TO authenticated - WITH CHECK ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ); - -CREATE POLICY "Project task comments insert (admin only)" ON public.project_task_comments - FOR INSERT TO authenticated - WITH CHECK ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ); - --- ============================================================================ --- 4. Tighten broad *Manage All* policies on core CRM/PM tables --- Only admins (admin_users) or service_role should have full CRUD. --- Client-facing read policies (Client View / Client Access) are kept. --- ============================================================================ - --- Invoices -DROP POLICY IF EXISTS "Invoices Manage All" ON public.invoices; - -CREATE POLICY "Invoices Admin Manage" ON public.invoices - FOR ALL TO authenticated - USING ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ) - WITH CHECK ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ); - --- Leads -DROP POLICY IF EXISTS "Leads Manage All" ON public.leads; - -CREATE POLICY "Leads Admin Manage" ON public.leads - FOR ALL TO authenticated - USING ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ) - WITH CHECK ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ); - --- Newsletter campaigns -DROP POLICY IF EXISTS "Newsletter Campaigns Manage All" ON public.newsletter_campaigns; - -CREATE POLICY "Newsletter Campaigns Admin Manage" ON public.newsletter_campaigns - FOR ALL TO authenticated - USING ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ) - WITH CHECK ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ); - --- Project briefs -DROP POLICY IF EXISTS "Project Briefs Admin Manage" ON public.project_briefs; - -CREATE POLICY "Project Briefs Admin Manage" ON public.project_briefs - FOR ALL TO authenticated - USING ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ) - WITH CHECK ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ); - --- Project files -DROP POLICY IF EXISTS "Project Files Admin Manage" ON public.project_files; - -CREATE POLICY "Project Files Admin Manage" ON public.project_files - FOR ALL TO authenticated - USING ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ) - WITH CHECK ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ); - --- Project tasks -DROP POLICY IF EXISTS "Project Tasks Admin Manage" ON public.project_tasks; - -CREATE POLICY "Project Tasks Admin Manage" ON public.project_tasks - FOR ALL TO authenticated - USING ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ) - WITH CHECK ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ); - --- Projects -DROP POLICY IF EXISTS "Projects Admin Manage" ON public.projects; - -CREATE POLICY "Projects Admin Manage" ON public.projects - FOR ALL TO authenticated - USING ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ) - WITH CHECK ( - (select auth.role()) = 'service_role' - OR (SELECT COUNT(*) FROM public.admin_users WHERE id = (select auth.uid())) = 1 - ); - --- ============================================================================ --- 5. Minor RLS performance improvement (auth_rls_initplan) for blocked_ips --- Use (select auth.uid()) form as recommended. --- ============================================================================ - -DROP POLICY IF EXISTS "Enable all access for authenticated users" ON public.blocked_ips; - -CREATE POLICY "Enable all access for authenticated users" ON public.blocked_ips - FOR ALL TO authenticated - USING ((select auth.uid()) IS NOT NULL) - WITH CHECK ((select auth.uid()) IS NOT NULL); -