6 Commits

Author SHA1 Message Date
jeanGaston abd3daea7e chore: bump version to 1.0.3
Publish Docker image / build-and-push (push) Successful in 4m23s
2026-07-30 17:35:59 +02:00
jeanGaston 0da6dc7810 fix(prisma): track migration SQL files in git
migration.sql was gitignored, so CI builds a Docker image from a
fresh clone with empty migration directories. prisma migrate deploy
then has nothing to apply, leaving the database schema-less on first
launch (P2021: table does not exist).
2026-07-30 17:32:40 +02:00
jeanGaston d8d152e249 chore: bump version to 1.0.2
Publish Docker image / build-and-push (push) Successful in 4m46s
2026-07-30 16:40:56 +02:00
jeanGaston 75ea9b7416 fix(docker): fix data volume ownership on fresh bind mounts
/app/data is a bind mount, so the chown baked into the image is
overwritten by the host directory's ownership at runtime. On a fresh
prod deploy Docker creates ./data as root-owned, but the container ran
as the non-root nextjs user, so Prisma couldn't open lovelope.db
("unable to open database file").

Add an entrypoint that starts as root, chowns /app/data, then drops to
nextjs via su-exec before running migrations and starting the server.
2026-07-30 16:40:45 +02:00
jeanGaston cd36fc5723 docs: document the dev/tag release process in GIT_WORKFLOW.md
Spells out the two-channel CI trigger (dev branch for test builds, v*
tags for releases) as an actual step-by-step process: keeping dev in
sync with main/master, bumping package.json, tagging, and the
required Gitea repo secrets/variables.
2026-07-30 16:35:45 +02:00
jeanGaston 3987a4e43e chore: bump version to 1.0.1
Publish Docker image / build-and-push (push) Successful in 3m22s
2026-07-30 15:47:12 +02:00
7 changed files with 132 additions and 20 deletions
-3
View File
@@ -38,8 +38,5 @@ yarn-error.log*
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
# prisma
prisma/migrations/*/migration.sql
# local Claude Code tooling (skills, agent config) - not part of the app # local Claude Code tooling (skills, agent config) - not part of the app
/.claude /.claude
+4 -4
View File
@@ -28,7 +28,7 @@ RUN npm run build
# ── Stage 3: runner ──────────────────────────────────────────────────────────── # ── Stage 3: runner ────────────────────────────────────────────────────────────
FROM node:20-alpine AS runner FROM node:20-alpine AS runner
RUN apk add --no-cache libc6-compat openssl RUN apk add --no-cache libc6-compat openssl su-exec
WORKDIR /app WORKDIR /app
ENV NODE_ENV=production ENV NODE_ENV=production
@@ -48,11 +48,11 @@ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
RUN mkdir -p /app/data && chown nextjs:nodejs /app/data RUN mkdir -p /app/data && chown nextjs:nodejs /app/data
USER nextjs COPY --chmod=755 docker-entrypoint.sh ./docker-entrypoint.sh
EXPOSE 3000 EXPOSE 3000
ENV PORT=3000 ENV PORT=3000
ENV HOSTNAME="0.0.0.0" ENV HOSTNAME="0.0.0.0"
# Run migrations then start the server # Fix ownership of the bind-mounted /app/data volume, then drop to nextjs
CMD ["sh", "-c", "npx prisma migrate deploy && node server.js"] ENTRYPOINT ["./docker-entrypoint.sh"]
+57 -12
View File
@@ -46,23 +46,68 @@ Types: `feat`, `fix`, `refactor`, `style`, `docs`, `chore`, `test`.
- Never force-push `main`. Force-pushing a feature branch you own (after a - Never force-push `main`. Force-pushing a feature branch you own (after a
rebase) is fine; never force-push a branch someone else might be using. rebase) is fine; never force-push a branch someone else might be using.
## Tags / releases ## Releases & Docker images
Tag production releases as `v<major>.<minor>.<patch>` (semver) once this CI (`.gitea/workflows/docker-publish.yml`) never builds an image from a
project has a deployment cadence worth marking. Not required for every merge. plain commit or merge to `main`/`master` — only from two deliberate
triggers, each producing tags on both the Gitea registry and GHCR:
## Docker images | Trigger | Produces | Purpose |
|------------------|--------------------------|-------------------------------------|
| push to `dev` | `:dev` | test a build before releasing |
| push a `v*` tag | `:latest` + `:vX.Y.Z` | ship an actual release |
CI (`.gitea/workflows/docker-publish.yml`) only builds an image on two ### Testing a build (`dev`)
events, not on every commit:
- push to `dev` — builds a rolling `:dev` tag, for testing in-progress work `dev` is a disposable pointer, not a branch with history of its own — it
before it's ready to release. should always match `main`/`master` exactly. Bring it up to date and push
- push a `v*` tag — builds `:latest` plus the version tag, for actual whenever you want a fresh test image:
releases.
Push to `dev` when you want a throwaway build to test; tag a release on ```
`main`/`master` when you want a real one. git checkout dev
git merge main --ff-only
git push origin dev
```
If `dev` has drifted and won't fast-forward (e.g. after a rebase, or a
commit landed only on `dev` by mistake), reset it instead of merging —
`dev` has no unique history worth preserving:
```
git checkout dev
git reset --hard main
git push origin dev --force-with-lease
```
### Shipping a release (tag)
1. Bump the version in `package.json` on `main`/`master` (a plain commit —
builds nothing on its own):
```
# edit package.json: "version": "X.Y.Z"
git add package.json
git commit -m "chore: bump version to X.Y.Z"
git push origin main
```
2. Tag that commit and push the tag — this is what actually triggers the
release build:
```
git tag vX.Y.Z
git push origin vX.Y.Z
```
3. Never move or re-push an existing tag once it's out (it may already be
pulled/deployed). If you need to fix something after tagging, bump to
the next patch version and tag again instead.
Use `v<major>.<minor>.<patch>` (semver) for tags.
### Required repo secrets/variables (Gitea → Settings → Actions)
- `vars.REGISTRY_HOST` — the Gitea instance hostname (no protocol), used to
tag/push to the built-in Gitea container registry.
- `secrets.REGISTRY_TOKEN` — a Gitea PAT with `write:package` scope.
- `secrets.GHCR_TOKEN` — a GitHub PAT with `write:packages` scope, used to
also push to `ghcr.io`.
## What not to commit ## What not to commit
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
set -e
chown -R nextjs:nodejs /app/data
exec su-exec nextjs sh -c "npx prisma migrate deploy && node server.js"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "lovelope", "name": "lovelope",
"version": "1.0.0", "version": "1.0.3",
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@@ -0,0 +1,62 @@
-- CreateTable
CREATE TABLE "Proposal" (
"id" TEXT NOT NULL PRIMARY KEY,
"slug" TEXT NOT NULL,
"manageToken" TEXT NOT NULL,
"senderName" TEXT NOT NULL,
"recipientName" TEXT NOT NULL,
"title" TEXT NOT NULL,
"message" TEXT NOT NULL,
"theme" TEXT NOT NULL DEFAULT 'sunset',
"gradientFrom" TEXT,
"gradientVia" TEXT,
"gradientTo" TEXT,
"gifUrl" TEXT,
"evasiveNo" BOOLEAN NOT NULL DEFAULT false,
"status" TEXT NOT NULL DEFAULT 'draft',
"expiresAt" DATETIME,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- CreateTable
CREATE TABLE "ActivityOption" (
"id" TEXT NOT NULL PRIMARY KEY,
"proposalId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"emoji" TEXT NOT NULL DEFAULT '🎉',
"order" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "ActivityOption_proposalId_fkey" FOREIGN KEY ("proposalId") REFERENCES "Proposal" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "TimeSlot" (
"id" TEXT NOT NULL PRIMARY KEY,
"activityOptionId" TEXT NOT NULL,
"label" TEXT NOT NULL,
"startsAt" TEXT,
CONSTRAINT "TimeSlot_activityOptionId_fkey" FOREIGN KEY ("activityOptionId") REFERENCES "ActivityOption" ("id") ON DELETE CASCADE ON UPDATE CASCADE
);
-- CreateTable
CREATE TABLE "Response" (
"id" TEXT NOT NULL PRIMARY KEY,
"proposalId" TEXT NOT NULL,
"selectedActivityId" TEXT,
"selectedTimeSlotId" TEXT,
"answer" TEXT NOT NULL,
"note" TEXT,
"respondedAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Response_proposalId_fkey" FOREIGN KEY ("proposalId") REFERENCES "Proposal" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT "Response_selectedActivityId_fkey" FOREIGN KEY ("selectedActivityId") REFERENCES "ActivityOption" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT "Response_selectedTimeSlotId_fkey" FOREIGN KEY ("selectedTimeSlotId") REFERENCES "TimeSlot" ("id") ON DELETE SET NULL ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "Proposal_slug_key" ON "Proposal"("slug");
-- CreateIndex
CREATE UNIQUE INDEX "Proposal_manageToken_key" ON "Proposal"("manageToken");
-- CreateIndex
CREATE UNIQUE INDEX "Response_proposalId_key" ON "Response"("proposalId");
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "ActivityOption" ADD COLUMN "location" TEXT;