7 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
jeanGaston 5ca66f3ed7 chore: rename database file from askedout.db to lovelope.db
Publish Docker image / build-and-push (push) Successful in 3m34s
Leftover naming from before the app was renamed to lovelope. Renamed
the local data/askedout.db file to data/lovelope.db to match (data/
is gitignored, so this doesn't touch version control).
2026-07-30 15:46:21 +02:00
10 changed files with 138 additions and 26 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# SQLite database file (mounted on a persistent volume in Docker)
DATABASE_URL="file:/app/data/askedout.db"
DATABASE_URL="file:/app/data/lovelope.db"
# Public base URL (used in QR codes and share links)
APP_URL="http://localhost:3000"
-3
View File
@@ -38,8 +38,5 @@ yarn-error.log*
*.tsbuildinfo
next-env.d.ts
# prisma
prisma/migrations/*/migration.sql
# local Claude Code tooling (skills, agent config) - not part of the app
/.claude
+4 -4
View File
@@ -28,7 +28,7 @@ RUN npm run build
# ── Stage 3: 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
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
USER nextjs
COPY --chmod=755 docker-entrypoint.sh ./docker-entrypoint.sh
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
# Run migrations then start the server
CMD ["sh", "-c", "npx prisma migrate deploy && node server.js"]
# Fix ownership of the bind-mounted /app/data volume, then drop to nextjs
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
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
project has a deployment cadence worth marking. Not required for every merge.
CI (`.gitea/workflows/docker-publish.yml`) never builds an image from a
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
events, not on every commit:
### Testing a build (`dev`)
- push to `dev` — builds a rolling `:dev` tag, for testing in-progress work
before it's ready to release.
- push a `v*` tag — builds `:latest` plus the version tag, for actual
releases.
`dev` is a disposable pointer, not a branch with history of its own — it
should always match `main`/`master` exactly. Bring it up to date and push
whenever you want a fresh test image:
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
+4 -4
View File
@@ -21,7 +21,7 @@ services:
ports:
- "3000:3000"
environment:
DATABASE_URL: "file:/app/data/askedout.db"
DATABASE_URL: "file:/app/data/lovelope.db"
APP_URL: "http://localhost:3000"
GIPHY_API_KEY: "${GIPHY_API_KEY:-}"
volumes:
@@ -35,11 +35,11 @@ Then:
docker compose up -d
```
Open **http://localhost:3000**. Your data lives in `./data/askedout.db` on your machine.
Open **http://localhost:3000**. Your data lives in `./data/lovelope.db` on your machine.
Want GIF search to work? Grab a free key at [developers.giphy.com](https://developers.giphy.com/), export it as `GIPHY_API_KEY` before running `docker compose up` (or drop it in a `.env` file next to `docker-compose.yml`), then restart. Without it, the app still runs fine, GIF search is just disabled.
Pin a specific version instead of always tracking `latest` by using a release tag, e.g. `ghcr.io/jeangaston/lovelope:v0.1.0`.
Pin a specific version instead of always tracking `latest` by using a release tag, e.g. `ghcr.io/jeangaston/lovelope:v1.0.0`.
### Build from source
@@ -83,7 +83,7 @@ Browsers never send the part after `#` to any server, so that key never reaches
| Variable | Required | Description |
|---|---|---|
| `DATABASE_URL` | ✅ | SQLite file path, default `file:/app/data/askedout.db` works out of the box with Docker |
| `DATABASE_URL` | ✅ | SQLite file path, default `file:/app/data/lovelope.db` works out of the box with Docker |
| `APP_URL` | ✅ | Base URL used to build share links, e.g. `http://localhost:3000` |
| `GIPHY_API_KEY` | optional | Enables GIF search. Free key at [developers.giphy.com](https://developers.giphy.com/), leave blank to disable |
+1 -1
View File
@@ -6,7 +6,7 @@ services:
ports:
- "3000:3000"
environment:
DATABASE_URL: "file:/app/data/askedout.db"
DATABASE_URL: "file:/app/data/lovelope.db"
APP_URL: "http://localhost:3000"
NODE_ENV: "development"
GIPHY_API_KEY: "${GIPHY_API_KEY:-}"
+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",
"version": "1.0.0",
"version": "1.0.3",
"private": true,
"license": "MIT",
"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;