From f53e422b457ad3a07e55553d811ce2b4281fe4ed Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Fri, 31 Jul 2026 10:50:12 +0300 Subject: [PATCH 1/9] test seeded development user --- .../Feature/Database/DatabaseSeederTest.php | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 backend/tests/Feature/Database/DatabaseSeederTest.php diff --git a/backend/tests/Feature/Database/DatabaseSeederTest.php b/backend/tests/Feature/Database/DatabaseSeederTest.php new file mode 100644 index 0000000..26b6b06 --- /dev/null +++ b/backend/tests/Feature/Database/DatabaseSeederTest.php @@ -0,0 +1,22 @@ +seed(); + $this->seed(); + + $this->assertDatabaseHas('users', [ + 'email' => 'user@example.com', + ]); + $this->assertDatabaseCount('users', 1); + } +} From 0e3901f05a1679bff233d063a75a17e606714f67 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Fri, 31 Jul 2026 10:51:17 +0300 Subject: [PATCH 2/9] seed development user on startup --- backend/database/seeders/DatabaseSeeder.php | 1 + backend/database/seeders/UserSeeder.php | 16 ++++++++++++++++ process-compose.yaml | 9 ++++++++- 3 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 backend/database/seeders/UserSeeder.php diff --git a/backend/database/seeders/DatabaseSeeder.php b/backend/database/seeders/DatabaseSeeder.php index dc2b6db..def224c 100644 --- a/backend/database/seeders/DatabaseSeeder.php +++ b/backend/database/seeders/DatabaseSeeder.php @@ -11,5 +11,6 @@ class DatabaseSeeder extends Seeder */ public function run(): void { + $this->call(UserSeeder::class); } } diff --git a/backend/database/seeders/UserSeeder.php b/backend/database/seeders/UserSeeder.php new file mode 100644 index 0000000..886e4c9 --- /dev/null +++ b/backend/database/seeders/UserSeeder.php @@ -0,0 +1,16 @@ + 'user@example.com', + ]); + } +} diff --git a/process-compose.yaml b/process-compose.yaml index ad7a160..2a00aea 100644 --- a/process-compose.yaml +++ b/process-compose.yaml @@ -18,6 +18,13 @@ processes: postgres: condition: process_healthy + seed: + command: php artisan db:seed --force + working_dir: ./backend + depends_on: + migrate: + condition: process_completed_successfully + mailpit: command: mailpit --smtp 127.0.0.1:${MAILPIT_SMTP_PORT:-2525} --listen 127.0.0.1:${MAILPIT_UI_PORT:-8025} readiness_probe: @@ -32,7 +39,7 @@ processes: command: php artisan serve --host=127.0.0.1 --port=${BACKEND_PORT:-8001} working_dir: ./backend depends_on: - migrate: + seed: condition: process_completed_successfully mailpit: condition: process_healthy From 89cc8a55e317910fd0fcbfcea7cecd8b44195da3 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Fri, 31 Jul 2026 10:51:33 +0300 Subject: [PATCH 3/9] test browser branding --- frontend/website/cypress/e2e/guest-auth.cy.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frontend/website/cypress/e2e/guest-auth.cy.ts b/frontend/website/cypress/e2e/guest-auth.cy.ts index f4c1ba8..2dea54b 100644 --- a/frontend/website/cypress/e2e/guest-auth.cy.ts +++ b/frontend/website/cypress/e2e/guest-auth.cy.ts @@ -8,6 +8,15 @@ describe('guest authentication pages', () => { cy.contains('a', 'Get started').should('have.attr', 'href', '/signup') }) + it('uses Attainly browser metadata', () => { + cy.visit('/') + + cy.title().should('equal', 'Attainly') + cy.get('link[rel="icon"]') + .should('have.attr', 'type', 'image/svg+xml') + .and('have.attr', 'href', '/favicon.svg') + }) + it('redirects guests away from the protected dashboard', () => { cy.visit('/dashboard') From 9ccb27a8f13e64e67b7a825a37188ea997fc0be1 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Fri, 31 Jul 2026 10:54:12 +0300 Subject: [PATCH 4/9] brand browser metadata --- frontend/website/index.html | 4 ++-- frontend/website/public/favicon.ico | Bin 4286 -> 0 bytes frontend/website/public/favicon.svg | 15 +++++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) delete mode 100644 frontend/website/public/favicon.ico create mode 100644 frontend/website/public/favicon.svg diff --git a/frontend/website/index.html b/frontend/website/index.html index 9e5fc8f..ca72e36 100644 --- a/frontend/website/index.html +++ b/frontend/website/index.html @@ -2,9 +2,9 @@ - + - Vite App + Attainly
diff --git a/frontend/website/public/favicon.ico b/frontend/website/public/favicon.ico deleted file mode 100644 index df36fcfb72584e00488330b560ebcf34a41c64c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmZQzU}RuqP*4ET3Jfa*7#PGD7#K7d7#I{77#JKFAmR)lAOIqU2X5Zs$bgLL=_@3A zjhlBkf-u-E^l}5#e(vTSjvJvE#HNe&P`g3?jcVTE_#KKtY>*hu-2k;;s(FXw$>tr7 z|DhPf28q$seyH6be^xc`aQp|g8{`HM8zcsjqlp`k?AB}E;dmd(Zjk*T3=#v$(Zmf< z`&pZJIL^XiH^_bv2FZccP&Evoc7y!o(Y(X)10MT9av(JzwN!Hh)P8~H9gaKj*bVYO z2!qss)KbNMsNEp{q&4qw{6&QQAT=PhAUzbj0cyWO^A5*L7iKh$o<<{gf0*zB%ZV*ek6akv4b2c(xQH$d$Mg`s)#4##Kc_BU;D{GXL$3C18c zx;#`5NH53?lHCBcpR;*~<1!4hcRKzrpKSX--p3S-L2Mjh0MZLGgCzT*c7xm<)V#y- z3yS?a9sf71bNHW@Xz@SJ!xW4`Y>*fhH-Pkl%mA51v>TxIi#G3YJcMF5D4p$e{9n{+ z^FPkh6a|CCu-Feuiy$*VW)WpS)NYV_3!8U1{z0*Sr{n+HW%mD*!_C3|hP%PT6f6dk z!{P>z86dMjW)gG*)P9ZT9geq9><0OLyW{`dGAmTOVd3Cm3YKf$4zCkIeurU@Ss*j< z+7Gpxxp{}*uwyWnns+ArO_!|@D?-JmqL!|{JXy){Z+gZlMPypL%f2*-Jv z{(*|Y)q(V2GYe`5$S$z`P`g3ysYPp3f$NrjCM-5(c2Q8ptk?oiME5yuZM-3=hU zAT!X-h1vzO6J$So^A5*3=xSPaIsUJhX8S+E&kP=>F!STRO_wBvm~$isnlXSdhz$~h z$-`)nUXU3ev(U|l+6l5d9HUJID&yBX{7+ATmhrH(1){x7pC(=HT=LB0y}A7)UP8)AS^=9*`Lzvp{CT%!kq-J3)5yHScf)ByT?WW^if zV!|8eX^Mgqe9c%vaSpN<8H2>Ya%k#7X5$(!Tt{e|LXt$|6>oq zKji=a2jLI=|NlQ=hu{Ou|Nnz<1LOby3=ClWkAa~cg#R!w*n{v71_t>L3=I4r{D6Uh a9fS`sFffB~17iat17ib}cK|F4vKj!X^7qaF diff --git a/frontend/website/public/favicon.svg b/frontend/website/public/favicon.svg new file mode 100644 index 0000000..3d796bf --- /dev/null +++ b/frontend/website/public/favicon.svg @@ -0,0 +1,15 @@ + + + + + From b9a5a5ea2a425eff1e508f2353e4ac957a213f19 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Fri, 31 Jul 2026 11:05:35 +0300 Subject: [PATCH 5/9] clarify agent process discipline --- ai/shared.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/ai/shared.md b/ai/shared.md index 97f3e33..00a1273 100644 --- a/ai/shared.md +++ b/ai/shared.md @@ -39,14 +39,39 @@ Use judgment for changes that cannot meaningfully be test-driven, such as documentation-only edits or declarative environment configuration. Validate those changes with the most relevant parser, formatter, dry run, or check. +## Approval discipline + +- Treat dependency installation, tests, static analysis, formatting, linting, + type checking, and builds as routine project operations. Run them without + asking for advance approval when they stay within the requested scope and + sandbox boundaries. +- If an essential operation is blocked by sandbox or network policy, batch the + necessary provisioning or validation into one narrowly scoped approval + request. Prefer a reusable command prefix when it can be safely limited to + the required tool and operation. +- Do not request repeated approvals for commands that fit a previously + approved scope. +- Do not prefix routine commands with per-command cache environment overrides. + Configure cache locations once in Codex, direnv, or the project environment. + Use temporary overrides only to diagnose a cache-specific problem. +- Truly destructive actions, external writes, main-stack control, and + operations outside the workspace still require explicit approval. + ## Running processes - The main checkout owns the canonical stack on the default ports. Assume it is the user's stack. Do not start, restart, or stop it unless the user asks. - A worktree owns its own isolated stack. The flake shell hook assigns a deterministic port offset and creates worktree-local PostgreSQL state. -- Start a worktree stack from its root. For non-interactive use, start it - detached and stop it when finished, as shown below. +- Start a worktree stack only when runtime or integration validation requires + it. Start it once from the worktree root, reuse it throughout validation, + and stop it once when finished. +- Do not start any service for PHPUnit, frontend formatting, linting, type + checking, or production builds. +- When authorized worktree stack control is necessary, operate it directly. + Do not ask the user to start or stop worktree services. +- For non-interactive use, start the stack detached and stop it when finished, + as shown below. - Do not use `process-compose -t=false` for a detached stack. It can leave an orphaned PostgreSQL process holding the data directory. - Non-interactive agent shells do not automatically load direnv. Bare project From b705d49aa1b807a0eaaffd808de6e02723598740 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Fri, 31 Jul 2026 10:48:37 +0300 Subject: [PATCH 6/9] test me endpoint --- backend/tests/Feature/Auth/MeEndpointTest.php | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 backend/tests/Feature/Auth/MeEndpointTest.php diff --git a/backend/tests/Feature/Auth/MeEndpointTest.php b/backend/tests/Feature/Auth/MeEndpointTest.php new file mode 100644 index 0000000..ec19fc3 --- /dev/null +++ b/backend/tests/Feature/Auth/MeEndpointTest.php @@ -0,0 +1,74 @@ +create(new CreateUserDto( + email: new EmailAddress('user@example.com'), + )); + app(SessionRepository::class)->create(new CreateSessionDto( + token: 'valid-token', + user: $user, + createdAt: $now, + expiresAt: $now->modify('+7 days'), + )); + + $response = $this->withCredentials() + ->withUnencryptedCookie( + AuthMiddleware::COOKIE_NAME, + 'valid-token', + )->getJson('/api/me'); + + $response->assertOk()->assertExactJson([ + 'user' => [ + 'id' => $user->getId(), + 'email' => 'user@example.com', + ], + ]); + } + + public function test_me_rejects_a_request_without_a_cookie(): void + { + $response = $this->getJson('/api/me'); + + $response + ->assertStatus(401) + ->assertExactJson(['error' => 'unauthenticated']); + } + + public function test_me_allows_credentialed_frontend_requests(): void + { + $response = $this->withHeaders([ + 'Origin' => 'https://localhost:5173', + 'Access-Control-Request-Method' => 'GET', + ])->options('/api/me'); + + $response + ->assertNoContent() + ->assertHeader( + 'Access-Control-Allow-Origin', + 'https://localhost:5173', + ) + ->assertHeader('Access-Control-Allow-Credentials', 'true'); + } +} From c0d0d070a8a483ba0e86d303fca39be724713e80 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Fri, 31 Jul 2026 10:50:06 +0300 Subject: [PATCH 7/9] add me endpoint --- backend/.env.example | 1 + .../app/Http/Controllers/AuthController.php | 23 +++++++++++++++++++ backend/bootstrap/app.php | 1 + backend/config/cors.php | 22 ++++++++++++++++++ backend/routes/api.php | 9 ++++++++ 5 files changed, 56 insertions(+) create mode 100644 backend/app/Http/Controllers/AuthController.php create mode 100644 backend/config/cors.php create mode 100644 backend/routes/api.php diff --git a/backend/.env.example b/backend/.env.example index c3214a6..027ae21 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -3,6 +3,7 @@ APP_ENV=local APP_KEY= APP_DEBUG=true APP_URL=https://localhost:8000 +FRONTEND_URL=https://localhost:5173 APP_LOCALE=en APP_FALLBACK_LOCALE=en diff --git a/backend/app/Http/Controllers/AuthController.php b/backend/app/Http/Controllers/AuthController.php new file mode 100644 index 0000000..de82e1a --- /dev/null +++ b/backend/app/Http/Controllers/AuthController.php @@ -0,0 +1,23 @@ +attributes->get('user'); + + return new JsonResponse([ + 'user' => [ + 'id' => $user->getId(), + 'email' => $user->getEmail()->value(), + ], + ]); + } +} diff --git a/backend/bootstrap/app.php b/backend/bootstrap/app.php index 324b7c2..141b221 100644 --- a/backend/bootstrap/app.php +++ b/backend/bootstrap/app.php @@ -9,6 +9,7 @@ use Illuminate\Http\Request; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', + api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) diff --git a/backend/config/cors.php b/backend/config/cors.php new file mode 100644 index 0000000..5189eb1 --- /dev/null +++ b/backend/config/cors.php @@ -0,0 +1,22 @@ + ['api/*'], + 'allowed_methods' => ['*'], + 'allowed_origins' => $allowedOrigins, + 'allowed_origins_patterns' => [], + 'allowed_headers' => ['*'], + 'exposed_headers' => [], + 'max_age' => 0, + 'supports_credentials' => true, +]; diff --git a/backend/routes/api.php b/backend/routes/api.php new file mode 100644 index 0000000..d8fba04 --- /dev/null +++ b/backend/routes/api.php @@ -0,0 +1,9 @@ +group(function (): void { + Route::get('/me', [AuthController::class, 'me']); +}); From 24f437cac608a8b7ea3b1a9b6ebe6db17eb34553 Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Fri, 31 Jul 2026 10:53:54 +0300 Subject: [PATCH 8/9] test session restoration --- .../website/cypress/e2e/session-auth.cy.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 frontend/website/cypress/e2e/session-auth.cy.ts diff --git a/frontend/website/cypress/e2e/session-auth.cy.ts b/frontend/website/cypress/e2e/session-auth.cy.ts new file mode 100644 index 0000000..d7fb723 --- /dev/null +++ b/frontend/website/cypress/e2e/session-auth.cy.ts @@ -0,0 +1,56 @@ +const authenticatedUser = { + id: 7, + email: 'user@example.com', +} + +describe('session authentication', () => { + it('restores an authenticated session on a protected route', () => { + cy.intercept('GET', '**/api/me', { + statusCode: 200, + body: { user: authenticatedUser }, + }).as('me') + + cy.visit('/dashboard') + cy.wait('@me') + + cy.location('pathname').should('equal', '/dashboard') + cy.get('h1').should('have.text', 'Your next step starts here.') + }) + + it('redirects an unauthenticated protected route to login', () => { + cy.intercept('GET', '**/api/me', { + statusCode: 401, + body: { error: 'unauthenticated' }, + }).as('me') + + cy.visit('/dashboard') + cy.wait('@me') + + cy.location('pathname').should('equal', '/login') + cy.location('search').should('include', 'redirect=/dashboard') + }) + + it('redirects a restored session away from a guest-only route', () => { + cy.intercept('GET', '**/api/me', { + statusCode: 200, + body: { user: authenticatedUser }, + }).as('me') + + cy.visit('/login') + cy.wait('@me') + + cy.location('pathname').should('equal', '/dashboard') + }) + + it('rejects a malformed authenticated-user response', () => { + cy.intercept('GET', '**/api/me', { + statusCode: 200, + body: { user: { id: 7 } }, + }).as('me') + + cy.visit('/dashboard') + cy.wait('@me') + + cy.location('pathname').should('equal', '/login') + }) +}) From bc62a25a8e10d33fc47dd57042706f15d40f3f8a Mon Sep 17 00:00:00 2001 From: Yisroel Baum Date: Fri, 31 Jul 2026 11:05:05 +0300 Subject: [PATCH 9/9] restore frontend session --- backend/phpunit.xml | 1 + frontend/website/cypress.config.ts | 2 +- frontend/website/env.d.ts | 8 ++++ frontend/website/package-lock.json | 12 +++++- frontend/website/package.json | 3 +- frontend/website/src/router/index.ts | 28 ++++++++---- frontend/website/src/stores/auth.ts | 61 ++++++++++++++++++++++++++- frontend/website/src/utils/apiBase.ts | 17 ++++++++ frontend/website/vite.config.ts | 18 ++++++++ nix/shell-hook.sh | 9 ++++ process-compose.yaml | 1 + 11 files changed, 146 insertions(+), 14 deletions(-) create mode 100644 frontend/website/src/utils/apiBase.ts diff --git a/backend/phpunit.xml b/backend/phpunit.xml index e7f0a48..dafc3f1 100644 --- a/backend/phpunit.xml +++ b/backend/phpunit.xml @@ -26,6 +26,7 @@ + diff --git a/frontend/website/cypress.config.ts b/frontend/website/cypress.config.ts index 2697f45..4a2d276 100644 --- a/frontend/website/cypress.config.ts +++ b/frontend/website/cypress.config.ts @@ -5,7 +5,7 @@ const frontendPort = process.env.VITE_PORT ?? '5173' export default defineConfig({ allowCypressEnv: false, e2e: { - baseUrl: `http://127.0.0.1:${frontendPort}`, + baseUrl: `https://localhost:${frontendPort}`, supportFile: false, }, video: false, diff --git a/frontend/website/env.d.ts b/frontend/website/env.d.ts index 11f02fe..b54b4c9 100644 --- a/frontend/website/env.d.ts +++ b/frontend/website/env.d.ts @@ -1 +1,9 @@ /// + +interface ImportMetaEnv { + readonly VITE_API_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/frontend/website/package-lock.json b/frontend/website/package-lock.json index 6cbfc4d..e47680a 100644 --- a/frontend/website/package-lock.json +++ b/frontend/website/package-lock.json @@ -10,7 +10,8 @@ "dependencies": { "pinia": "^4.0.2", "vue": "^3.5.40", - "vue-router": "^5.2.0" + "vue-router": "^5.2.0", + "zod": "^4.4.3" }, "devDependencies": { "@tsconfig/node24": "^24.0.4", @@ -7620,6 +7621,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/frontend/website/package.json b/frontend/website/package.json index ebd0942..e70648e 100644 --- a/frontend/website/package.json +++ b/frontend/website/package.json @@ -18,7 +18,8 @@ "dependencies": { "pinia": "^4.0.2", "vue": "^3.5.40", - "vue-router": "^5.2.0" + "vue-router": "^5.2.0", + "zod": "^4.4.3" }, "devDependencies": { "@tsconfig/node24": "^24.0.4", diff --git a/frontend/website/src/router/index.ts b/frontend/website/src/router/index.ts index b726d17..51e7184 100644 --- a/frontend/website/src/router/index.ts +++ b/frontend/website/src/router/index.ts @@ -37,21 +37,31 @@ const router = createRouter({ ], }) -router.beforeEach((to) => { +router.beforeEach(async (to) => { const authStore = useAuthStore() if (to.meta.requiresAuth && !authStore.isAuthenticated) { - return { - name: 'login', - query: { - redirect: to.fullPath, - }, + const restoredSession = await authStore.fetchMe() + if (!restoredSession) { + return { + name: 'login', + query: { + redirect: to.fullPath, + }, + } } } - if (to.meta.guestOnly && authStore.isAuthenticated) { - return { - name: 'dashboard', + if (to.meta.guestOnly) { + let restoredSession = authStore.isAuthenticated + if (!restoredSession) { + restoredSession = await authStore.fetchMe() + } + + if (restoredSession) { + return { + name: 'dashboard', + } } } }) diff --git a/frontend/website/src/stores/auth.ts b/frontend/website/src/stores/auth.ts index 50688f1..399a301 100644 --- a/frontend/website/src/stores/auth.ts +++ b/frontend/website/src/stores/auth.ts @@ -1,10 +1,67 @@ -import { ref } from 'vue' +import { computed, ref } from 'vue' import { defineStore } from 'pinia' +import { z } from 'zod' + +import { API_BASE } from '@/utils/apiBase' + +export const authUserSchema = z.object({ + id: z.number().int().positive(), + email: z.string().email(), +}) + +const meResponseSchema = z.object({ + user: authUserSchema, +}) + +export type AuthUser = z.infer export const useAuthStore = defineStore('auth', () => { - const isAuthenticated = ref(false) + const user = ref(null) + const loading = ref(false) + const error = ref(null) + const isAuthenticated = computed(() => user.value !== null) + + async function fetchMe(): Promise { + loading.value = true + error.value = null + + try { + const response = await fetch(`${API_BASE}/api/me`, { + method: 'GET', + credentials: 'include', + headers: { + Accept: 'application/json', + }, + }) + + if (response.status === 200) { + const responseBody: unknown = await response.json() + user.value = meResponseSchema.parse(responseBody).user + + return true + } + + user.value = null + if (response.status !== 401) { + error.value = 'Unable to restore session' + } + + return false + } catch { + user.value = null + error.value = 'Unable to restore session' + + return false + } finally { + loading.value = false + } + } return { + user, + loading, + error, isAuthenticated, + fetchMe, } }) diff --git a/frontend/website/src/utils/apiBase.ts b/frontend/website/src/utils/apiBase.ts new file mode 100644 index 0000000..0a1babc --- /dev/null +++ b/frontend/website/src/utils/apiBase.ts @@ -0,0 +1,17 @@ +function resolveApiBase(): string { + const configuredApiUrl = import.meta.env.VITE_API_URL + if (configuredApiUrl === '') { + throw new Error('VITE_API_URL must be configured') + } + + if (!import.meta.env.DEV) { + return configuredApiUrl + } + + const apiUrl = new URL(configuredApiUrl) + apiUrl.hostname = window.location.hostname + + return apiUrl.origin +} + +export const API_BASE = resolveApiBase() diff --git a/frontend/website/vite.config.ts b/frontend/website/vite.config.ts index ace127e..a6fdcaa 100644 --- a/frontend/website/vite.config.ts +++ b/frontend/website/vite.config.ts @@ -1,12 +1,30 @@ +import { existsSync, readFileSync } from 'node:fs' import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' +import type { ServerOptions } from 'vite' import vue from '@vitejs/plugin-vue' import vueJsx from '@vitejs/plugin-vue-jsx' import vueDevTools from 'vite-plugin-vue-devtools' +const certificateKeyPath = fileURLToPath( + new URL('../../.cert/localhost-key.pem', import.meta.url), +) +const certificatePath = fileURLToPath( + new URL('../../.cert/localhost.pem', import.meta.url), +) +const serverOptions: ServerOptions = {} + +if (existsSync(certificateKeyPath) && existsSync(certificatePath)) { + serverOptions.https = { + key: readFileSync(certificateKeyPath), + cert: readFileSync(certificatePath), + } +} + // https://vite.dev/config/ export default defineConfig({ + server: serverOptions, plugins: [ vue(), vueJsx(), diff --git a/nix/shell-hook.sh b/nix/shell-hook.sh index d835131..73d1874 100644 --- a/nix/shell-hook.sh +++ b/nix/shell-hook.sh @@ -38,6 +38,7 @@ export PGUSER="postgres" export PGDATABASE="postgres" DEV_APP_URL="https://localhost:$CADDY_PORT" +DEV_FRONTEND_URL="https://localhost:$VITE_PORT" DEV_DB_CONNECTION="pgsql" DEV_DB_HOST="$PGHOST" DEV_DB_PORT="5432" @@ -99,6 +100,7 @@ set_env_value() { } set_env_value APP_URL "$DEV_APP_URL" +set_env_value FRONTEND_URL "$DEV_FRONTEND_URL" set_env_value DB_CONNECTION "$DEV_DB_CONNECTION" set_env_value DB_HOST "$DEV_DB_HOST" set_env_value DB_PORT "$DEV_DB_PORT" @@ -109,6 +111,13 @@ set_env_value MAIL_MAILER "$DEV_MAIL_MAILER" set_env_value MAIL_HOST "$DEV_MAIL_HOST" set_env_value MAIL_PORT "$DEV_MAIL_PORT" +FRONTEND_ENV_FILE="$REPO_ROOT/frontend/website/.env.local" +FRONTEND_API_URL="VITE_API_URL=$DEV_APP_URL" +if [ ! -f "$FRONTEND_ENV_FILE" ] \ + || [ "$(cat "$FRONTEND_ENV_FILE")" != "$FRONTEND_API_URL" ]; then + printf '%s\n' "$FRONTEND_API_URL" > "$FRONTEND_ENV_FILE" +fi + if [ ! -d "$REPO_ROOT/backend/vendor" ]; then echo "[composer] installing backend dependencies" (cd "$REPO_ROOT/backend" && composer install) diff --git a/process-compose.yaml b/process-compose.yaml index 2a00aea..fcddf50 100644 --- a/process-compose.yaml +++ b/process-compose.yaml @@ -63,6 +63,7 @@ processes: host: 127.0.0.1 port: ${VITE_PORT:-5173} path: / + scheme: https initial_delay_seconds: 1 period_seconds: 2