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
16 changes: 16 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,21 @@ GOOGLE_CALENDAR_INTERNAL_ID=seu_calendar_id_interno_aqui
# Analytics
GOOGLE_ANALYTICS_ID=G-PQNS7S7FD3

# Banco de dados (Postgres no Azure)
#
# Duas conexoes diferentes para o mesmo banco:
#
# 1. DATABASE_URL — conexao direta, usada SO pelo drizzle-kit (migrations,
# studio) rodando na sua maquina ou na CI. O Azure exige TLS.
# Em dev local, use o Postgres do docker-compose.yml (`docker compose up -d`).
DATABASE_URL=postgres://ameciclo:ameciclo@localhost:5433/ameciclo
#
# 2. Hyperdrive — usado pelo Worker em runtime. Em producao vem do binding
# (wrangler.jsonc); em dev local aponta para o mesmo Postgres local.
WRANGLER_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE=postgres://ameciclo:ameciclo@localhost:5433/ameciclo
#
# Producao (Azure exige TLS) — nao commitar, usar no .env local ou na CI:
# DATABASE_URL=postgres://user:senha@servidor.postgres.database.azure.com:5432/ameciclo?sslmode=require

# Ambiente
NODE_ENV=development
15 changes: 15 additions & 0 deletions app/db/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Drizzle table definitions. Everything exported here is picked up by
* drizzle-kit (see drizzle.config.ts) and by the `db` instance in
* app/lib/db.server.ts, which is created with `{ schema }` so relational
* queries (`db.query.*`) work.
*
* Derive Zod schemas from these tables with drizzle-zod rather than
* hand-writing them, so validation cannot drift from the column types:
*
* import { createInsertSchema } from "drizzle-zod";
* export const insertExampleSchema = createInsertSchema(example);
*/

// Primeira tabela entra aqui. Depois: `pnpm db:generate && pnpm db:migrate`.
export {};
51 changes: 51 additions & 0 deletions app/lib/db.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Server-only Postgres access, reached only from server contexts
* (createServerFn handlers, loaders, server routes) — same contract as
* app/utils/env.server.ts.
*
* Connection goes through the Hyperdrive binding, not a plain DATABASE_URL.
* Hyperdrive keeps a warm pool next to the database, so a Worker invocation
* skips the TCP + TLS + auth handshake (~7 round-trips to Azure) and gets
* caching of non-mutating queries for free.
*
* `DATABASE_URL` is a different thing and is NOT used here: it is the direct
* Azure connection used by drizzle-kit for migrations from your machine or
* CI. Hyperdrive is a Workers runtime binding and drizzle-kit cannot dial it.
*
* Local dev still goes through the same binding — point it at a local or
* remote Postgres by setting this in .env:
* WRANGLER_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE=postgres://...
*/

import { env } from "cloudflare:workers";
import { drizzle, type NodePgDatabase } from "drizzle-orm/node-postgres";
import { Client } from "pg";

import * as schema from "~/db/schema";

export type Db = NodePgDatabase<typeof schema>;

/**
* Runs `fn` with a connected client and always closes it.
*
* Workers allow at most 6 concurrent connections per invocation, and a leaked
* connection is charged against that budget until it idles out — so the
* `finally` matters. Server functions do not expose the Worker's
* `ExecutionContext`, so `ctx.waitUntil(client.end())` is not available to us
* and the close is awaited inline.
*
* Prefer one `withDb` call per handler doing several queries over several
* calls doing one query each; each call is a fresh connection checkout.
*
* const rows = await withDb((db) => db.select().from(example));
*/
export async function withDb<T>(fn: (db: Db) => Promise<T> | T): Promise<T> {
const client = new Client({ connectionString: env.HYPERDRIVE.connectionString });
await client.connect();

try {
return await fn(drizzle(client, { schema }));
} finally {
await client.end();
}
}
24 changes: 24 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
services:
postgres:
image: postgres:18-alpine
container_name: ameciclo-postgres
restart: unless-stopped
environment:
POSTGRES_USER: ameciclo
POSTGRES_PASSWORD: ameciclo
POSTGRES_DB: ameciclo
ports:
# 5433 no host: a 5432 costuma estar ocupada por outro projeto.
- "5433:5432"
volumes:
# Postgres 18 moved PGDATA to /var/lib/postgresql/18/docker. Mounting the
# parent keeps the volume valid across future major versions.
- postgres-data:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ameciclo -d ameciclo"]
interval: 5s
timeout: 5s
retries: 10

volumes:
postgres-data:
24 changes: 24 additions & 0 deletions drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { defineConfig } from "drizzle-kit";

/**
* drizzle-kit runs in Node on your machine (or in CI), never inside the
* Worker — so it connects straight to Azure with DATABASE_URL and does not
* and cannot go through the Hyperdrive binding.
*
* Set DATABASE_URL in .env (gitignored). Azure enforces TLS, so it needs
* `?sslmode=require`.
*
* pnpm db:generate # schema change -> versioned SQL in ./drizzle
* pnpm db:migrate # apply pending migrations
* pnpm db:studio # browse the database
*/
export default defineConfig({
schema: "./app/db/schema.ts",
out: "./drizzle",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
verbose: true,
strict: true,
});
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@
"deploy:staging": "CLOUDFLARE_ENV=staging vite build && wrangler deploy",
"lint": "oxlint",
"typecheck": "tsc",
"gen:strapi-types": "openapi-typescript ./specification.json -o ./app/types/strapi-api.ts"
"gen:strapi-types": "openapi-typescript ./specification.json -o ./app/types/strapi-api.ts",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio"
},
"dependencies": {
"@fullcalendar/core": "7.0.0-rc.2",
Expand All @@ -27,6 +30,8 @@
"@turf/helpers": "^7.2.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"drizzle-orm": "^0.45.2",
"drizzle-zod": "^0.8.3",
"framer-motion": "^11.18.0",
"fuse.js": "^7.1.0",
"highcharts": "^12.2.0",
Expand All @@ -35,6 +40,7 @@
"lucide-react": "^0.545.0",
"maplibre-gl": "^5.24.0",
"match-sorter": "^8.0.0",
"pg": "^8.22.0",
"radix-ui": "^1.4.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand All @@ -55,10 +61,13 @@
"@rsbuild/core": "^2.0.2",
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.2.4",
"@types/node": "^26.1.2",
"@types/pg": "^8.20.4",
"@types/react": "^18.2.20",
"@types/react-dom": "^18.2.7",
"@types/react-table": "^7.7.20",
"@vitejs/plugin-react": "^4.3.0",
"drizzle-kit": "^0.31.10",
"openapi-typescript": "^7.13.0",
"oxlint": "^1.62.0",
"tailwindcss": "^4.2.4",
Expand Down
Loading
Loading