Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
# Supabase service-role keys are not required for Prisma's PostgreSQL connection.

# Web
NEXT_PUBLIC_API_URL=http://localhost:3001
NEXT_PUBLIC_API_BASE_URL=http://localhost:3001
NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-supabase-publishable-key

# API
NODE_ENV=development
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ permissions:

env:
CI: true
NEXT_PUBLIC_API_URL: http://localhost:3001
NEXT_PUBLIC_API_BASE_URL: http://localhost:3001
NEXT_PUBLIC_SUPABASE_URL: https://example.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY: ci-placeholder
DATABASE_URL: postgresql://USER:PASSWORD@POOLER_HOST:6543/postgres?pgbouncer=true
DIRECT_URL: postgresql://USER:PASSWORD@DIRECT_HOST:5432/postgres

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -664,7 +664,7 @@ Directory roles:
- CI secrets belong in GitHub Actions secrets.
- Hive private keys must never be collected, stored, transmitted, or logged.
- `NEXT_PUBLIC_` variables are public browser-exposed values and must never contain secrets.
- The frontend reads `NEXT_PUBLIC_API_URL`; it is a placeholder until the Fastify API has a production deployment.
- The frontend reads `NEXT_PUBLIC_API_BASE_URL`; it is a placeholder until the Fastify API has a production deployment.

## Vercel Setup

Expand All @@ -679,7 +679,7 @@ Create a Vercel project for the web app with these monorepo settings:
Required Vercel environment variable:

```env
NEXT_PUBLIC_API_URL=<deployed-api-url-placeholder>
NEXT_PUBLIC_API_BASE_URL=<deployed-api-url-placeholder>
```

There is no production API deployment in this scaffold. Replace the placeholder with the deployed Fastify API URL when that later infrastructure exists.
Expand Down
14 changes: 7 additions & 7 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@
},
"dependencies": {
"@fastify/cors": "^11.0.1",
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"@supabase/server": "^1.4.0",
"@prisma/adapter-pg": "^7.10.0",
"@prisma/client": "^7.10.0",
"@supabase/server": "^1.7.0",
"dotenv": "^17.2.0",
"fastify": "^5.4.0",
"pg": "^8.22.0",
"zod": "^4.0.5"
"fastify": "^5.12.5",
"pg": "^8.23.0",
"zod": "^4.6.5"
},
"devDependencies": {
"@types/pg": "^8.20.0",
"prisma": "^7.8.0",
"prisma": "^7.10.0",
"tsx": "^4.20.3",
"typescript": "^5.8.3"
}
Expand Down
10 changes: 5 additions & 5 deletions apps/api/src/generated/prisma/internal/class.ts

Large diffs are not rendered by default.

90 changes: 72 additions & 18 deletions apps/api/src/generated/prisma/internal/prismaNamespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ export type PrismaVersion = {
}

/**
* Prisma Client JS version: 7.8.0
* Query Engine version: 3c6e192761c0362d496ed980de936e2f3cebcd3a
* Prisma Client JS version: 7.10.0
* Query Engine version: 0edf323efd1d98336f3f0a68684b56f689b900d3
*/
export const prismaVersion: PrismaVersion = {
client: "7.8.0",
engine: "3c6e192761c0362d496ed980de936e2f3cebcd3a"
client: "7.10.0",
engine: "0edf323efd1d98336f3f0a68684b56f689b900d3"
}

/**
Expand Down Expand Up @@ -155,6 +155,19 @@ export type Subset<T, U> = {
[key in keyof T]: key extends keyof U ? T[key] : never;
};

/**
* Resolved type of the argument passed to the `PrismaClient` constructor.
*
* When called without a narrower options type (the common case), this resolves
* to `PrismaClientOptions` directly, which produces a clear TypeScript error
* message (`not assignable to parameter of type 'PrismaClientOptions'`) when
* the argument is missing or incomplete. When the user supplies a narrower
* options type (e.g. via a literal), it falls back to `Subset` to keep
* filtering out unknown properties.
*/
export type PrismaClientConstructorArgs<Options extends PrismaClientOptions> =
[PrismaClientOptions] extends [Options] ? PrismaClientOptions : Subset<Options, PrismaClientOptions>;

/**
* SelectSubset
* @desc From `T` pick properties that exist in `U`. Simple version of Intersection.
Expand Down Expand Up @@ -187,7 +200,7 @@ type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
export type XOR<T, U> =
T extends object ?
U extends object ?
(Without<T, U> & U) | (Without<U, T> & T)
((Without<T, U> & U) | (Without<U, T> & T)) & object
: U : T


Expand Down Expand Up @@ -2141,19 +2154,10 @@ export type BatchPayload = {
export const defineExtension = runtime.Extensions.defineExtension as unknown as runtime.Types.Extensions.ExtendsHook<"define", TypeMapCb, runtime.Types.Extensions.DefaultArgs>
export type DefaultPrismaClient = PrismaClient
export type ErrorFormat = 'pretty' | 'colorless' | 'minimal'
export type PrismaClientOptions = ({
/**
* Instance of a Driver Adapter, e.g., like one provided by `@prisma/adapter-pg`.
*/
adapter: runtime.SqlDriverAdapterFactory
accelerateUrl?: never
} | {
/**
* Prisma Accelerate URL allowing the client to connect through Accelerate instead of a direct database.
*/
accelerateUrl: string
adapter?: never
}) & {
/**
* Options common to all variants of `PrismaClientOptions`, regardless of whether you connect to your database through a driver adapter or through Prisma Accelerate.
*/
export interface PrismaClientBaseOptions {
/**
* @default "colorless"
*/
Expand Down Expand Up @@ -2240,6 +2244,56 @@ export type PrismaClientOptions = ({
*/
queryPlanCacheMaxSize?: number
}

/**
* `PrismaClient` options for connecting to your database through Prisma Accelerate instead of a driver adapter.
*
* Learn more: https://pris.ly/d/accelerate
*/
export interface PrismaClientOptionsWithAccelerateUrl extends PrismaClientBaseOptions {
/**
* The Prisma Accelerate connection URL. Use this option to connect to your database through Prisma Accelerate instead of using a driver adapter to connect directly.
*
* Learn more: https://pris.ly/d/accelerate
*/
accelerateUrl: string
adapter?: never
}

/**
* `PrismaClient` options for connecting to your database through a driver adapter. This is the common case in Prisma 7.
*
* Learn more: https://pris.ly/d/driver-adapters
*/
export interface PrismaClientOptionsWithAdapter extends PrismaClientBaseOptions {
/**
* A driver adapter that PrismaClient uses to connect to your database, such as the ones provided by `@prisma/adapter-pg`, `@prisma/adapter-libsql`, `@prisma/adapter-planetscale`, etc.
*
* A driver adapter is **required** unless you connect to your database through Prisma Accelerate (in which case use `accelerateUrl` instead).
*
* Learn more: https://pris.ly/d/driver-adapters
*
* @example
* ```ts
* import { PrismaPg } from '@prisma/adapter-pg'
* import { PrismaClient } from './generated/prisma/client'
*
* const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL })
* const prisma = new PrismaClient({ adapter })
* ```
*/
adapter: runtime.SqlDriverAdapterFactory
accelerateUrl?: never
}

/**
* Options passed to the `PrismaClient` constructor.
*
* A driver adapter (or, alternatively, a Prisma Accelerate URL) is **required**. See {@link PrismaClientOptionsWithAdapter} and {@link PrismaClientOptionsWithAccelerateUrl} for the two variants. All other properties live in {@link PrismaClientBaseOptions} and are optional.
*
* Learn more about driver adapters: https://pris.ly/d/driver-adapters
*/
export type PrismaClientOptions = PrismaClientOptionsWithAccelerateUrl | PrismaClientOptionsWithAdapter
export type GlobalOmitConfig = {
user?: Prisma.UserOmit
world?: Prisma.WorldOmit
Expand Down
2 changes: 1 addition & 1 deletion apps/web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
# CI secrets belong in GitHub Actions secrets.
# Hive private keys must never be collected, stored, transmitted, or logged.

NEXT_PUBLIC_API_URL=http://localhost:3001
NEXT_PUBLIC_API_BASE_URL=http://localhost:3001
NEXT_PUBLIC_SUPABASE_URL=https://your-project-ref.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=your-supabase-publishable-key
6 changes: 3 additions & 3 deletions apps/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ There is currently no configured `test` script.
`apps/web/.env.example` documents frontend-safe variables.

```env
NEXT_PUBLIC_API_URL=http://localhost:3001
NEXT_PUBLIC_API_BASE_URL=http://localhost:3001
```

`NEXT_PUBLIC_API_URL` is public browser configuration for the API client. Do not put secrets in frontend environment variables.
`NEXT_PUBLIC_API_BASE_URL` is public browser configuration for the API client. Do not put secrets in frontend environment variables.

## Structure

Expand Down Expand Up @@ -91,7 +91,7 @@ Use `LoadingState` for page or contained waiting surfaces, `EmptyState` for no-c

TanStack Query is configured in `src/providers/query-provider.tsx` using `src/lib/query/query-client.ts`. The provider is included once by `src/providers/app-providers.tsx`.

The API client lives in `src/lib/api/client.ts`, with safe API error helpers in `src/lib/api/errors.ts`. It reads `NEXT_PUBLIC_API_URL` from `src/lib/env.ts`. No fake API endpoints were added for the UI foundation.
The API client lives in `src/lib/api/client.ts`, with safe API error helpers in `src/lib/api/errors.ts`. It reads `NEXT_PUBLIC_API_BASE_URL` from `src/lib/env.ts`. No fake API endpoints were added for the UI foundation.

## TipTap Editor

Expand Down
16 changes: 8 additions & 8 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
"clean": "node -e \"fs.rmSync('.next', { recursive: true, force: true }); fs.rmSync('dist', { recursive: true, force: true })\""
},
"dependencies": {
"@supabase/ssr": "^0.12.3",
"@supabase/supabase-js": "^2.110.7",
"@tanstack/react-query": "^5.101.2",
"@tiptap/extension-placeholder": "^3.28.0",
"@tiptap/react": "^3.28.0",
"@tiptap/starter-kit": "^3.28.0",
"next": "^15.4.2",
"@supabase/ssr": "^0.12.7",
"@supabase/supabase-js": "^2.116.0",
"@tanstack/react-query": "^5.103.1",
"@tiptap/extension-placeholder": "^3.31.3",
"@tiptap/react": "^3.31.3",
"@tiptap/starter-kit": "^3.31.3",
"next": "^15.5.25",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
Expand All @@ -27,7 +27,7 @@
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "^15.4.2",
"eslint-config-next": "^15.5.25",
"tailwindcss": "^4",
"typescript": "^5"
},
Expand Down
13 changes: 2 additions & 11 deletions apps/web/src/lib/api/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ApiError, type ApiErrorBody } from '@/lib/api/errors';
import { env } from '@/lib/env';

type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
type JsonBody = Record<string, unknown> | Array<unknown> | string | number | boolean | null;
Expand All @@ -9,18 +10,8 @@ type RequestOptions<TBody = JsonBody> = {
signal?: AbortSignal | null;
};

const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL;

function getApiBaseUrl() {
if (!API_BASE_URL) {
throw new Error('NEXT_PUBLIC_API_BASE_URL is not configured.');
}

return API_BASE_URL;
}

function buildUrl(path: string) {
return new URL(path, getApiBaseUrl()).toString();
return new URL(path, env.apiBaseUrl).toString();
}

function hasJsonBody(body: unknown): body is JsonBody {
Expand Down
12 changes: 8 additions & 4 deletions apps/web/src/lib/env.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
const rawApiUrl = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3001';
const rawApiUrl = process.env.NEXT_PUBLIC_API_BASE_URL;

export function parsePublicApiUrl(value: string | undefined): string {
if (!value) {
throw new Error('NEXT_PUBLIC_API_BASE_URL is not configured.');
}

export function parsePublicApiUrl(value: string): string {
try {
const url = new URL(value);

if (!['http:', 'https:'].includes(url.protocol)) {
throw new Error('NEXT_PUBLIC_API_URL must use http or https.');
throw new Error('NEXT_PUBLIC_API_BASE_URL must use http or https.');
}

return url.origin;
} catch (error) {
const message = error instanceof Error ? error.message : 'Invalid URL.';
throw new Error(`Invalid NEXT_PUBLIC_API_URL: ${message}`);
throw new Error(`Invalid NEXT_PUBLIC_API_BASE_URL: ${message}`);
}
}

Expand Down
Loading
Loading