diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..591f594 --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +PUBLIC_WS_URL=ws://localhost:3000/ws +PUBLIC_BOX_DRAWING_CHARS=─│┌┐└┘├┤ +HOST=localhost +FE_PORT=5173 +BE_PORT=3000 +JWT_SECRET=your-secret-here +SIGNUP_SECRET=your-signup-secret-here +ADMIN_EMAIL=admin@localhost +DB_PATH=packages/be/db.sqlite +DATA_DIR=./data diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml new file mode 100644 index 0000000..b71928b --- /dev/null +++ b/.gitea/workflows/deploy.yaml @@ -0,0 +1,47 @@ +name: Build and Deploy + +on: + push: + branches: [master] + +jobs: + build-deploy: + runs-on: ubuntu-latest + container: + image: docker:latest + volumes: + - /opt/todo3:/deploy + steps: + - name: Install dependencies + run: apk add --no-cache git nodejs + + - uses: actions/checkout@v4 + + - name: Copy source to deploy directory + run: | + rm -rf /deploy/build + cp -r . /deploy/build + + - name: Create .env file + working-directory: /deploy/build + run: | + cat > .env << 'EOF' + PUBLIC_WS_URL=${{ vars.PUBLIC_WS_URL }} + PUBLIC_BOX_DRAWING_CHARS=${{ vars.PUBLIC_BOX_DRAWING_CHARS }} + HOST=${{ vars.HOST }} + FE_PORT=${{ vars.FE_PORT }} + BE_PORT=${{ vars.BE_PORT }} + JWT_SECRET=${{ secrets.JWT_SECRET }} + SIGNUP_SECRET=${{ secrets.SIGNUP_SECRET }} + ADMIN_EMAIL=${{ vars.ADMIN_EMAIL }} + DATA_DIR=/opt/todo3/data + EOF + + - name: Build and deploy + working-directory: /deploy/build + run: | + docker compose build + docker compose up -d + + - name: Cleanup + run: rm -rf /deploy/build diff --git a/.gitignore b/.gitignore index b71d731..b20dccd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules +.env *.local packages/be/db.sqlite up.sh \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3ffdbb5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +FROM oven/bun:latest AS base +WORKDIR /app + +FROM base AS deps +COPY package.json bun.lockb ./ +COPY packages/fe/package.json packages/fe/ +COPY packages/be/package.json packages/be/ +RUN bun install --frozen-lockfile + +FROM deps AS fe-build +COPY packages/fe packages/fe +COPY packages/be/src packages/be/src +COPY tsconfig.json . +ARG PUBLIC_WS_URL +ARG PUBLIC_BOX_DRAWING_CHARS +ENV PUBLIC_WS_URL=$PUBLIC_WS_URL +ENV PUBLIC_BOX_DRAWING_CHARS=$PUBLIC_BOX_DRAWING_CHARS +RUN bun run --filter fe build + +FROM base AS frontend +WORKDIR /app/packages/fe +COPY --from=deps /app/node_modules /app/node_modules +COPY --from=fe-build /app/packages/fe/dist ./dist +COPY packages/fe/package.json packages/fe/vite.config.ts ./ +CMD ["bun", "run", "start"] + +FROM base AS backend +WORKDIR /app/packages/be +COPY --from=deps /app/node_modules /app/node_modules +COPY packages/be ./ +CMD ["bun", "run", "start"] diff --git a/README.md b/README.md index bebf061..d4469a1 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,50 @@ -# Elysia with Bun runtime +# Todo3 -## Getting Started + -To get started with this template, simply paste this command into your terminal: +Visually, this is an attempt at replicating lovely TUI aesthetics on the web. It isn't actually rendered in text, but everything is based around the `ch` unit. -```bash -bun create elysia ./elysia-example -``` +Functionally, it's a real-time collaborative todo list, exclusively via WebSockets with optimistic updates. No REST here, not even for authentication. Whether or not this is a good idea remains to be seen. + +## Tech Stack + +- **Frontend**: React, Vite +- **Backend**: Elysia, Bun, SQLite +- **Real-time**: WebSockets ## Development -To start the development server run: - ```bash -bun run dev +cp .env.example .env +# fill in your values ``` -Open http://localhost:3000/ with your browser to see the result. +### With Docker (recommended) + +```bash +bun install +docker compose -f docker-compose.dev.yml up +``` + +Frontend: http://localhost:5173 +Backend: http://localhost:3000 + +## Production + +```bash +docker compose up --build +``` + +## Environment Variables + +| Variable | Description | +| -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `PUBLIC_WS_URL` | WebSocket URL for frontend | +| `PUBLIC_BOX_DRAWING_CHARS` | The 8 characters for box drawing. The default value `─│┌┐└┘├┤` results in boxes that are a bit to nice, you can also try `–\|++++++` for a simpler style | +| `HOST` | Allowed host for Vite | +| `FE_PORT` | Frontend port (external) | +| `BE_PORT` | Backend port (external) | +| `JWT_SECRET` | Secret for JWT signing | +| `SIGNUP_SECRET` | Required for user registration | +| `ADMIN_EMAIL` | Contact email for signup errors | +| `DB_PATH` | Path to SQLite database | diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..6387181 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,29 @@ +name: todo3 + +services: + fe: + image: oven/bun:latest + working_dir: /app + command: bun run --filter fe dev -- --host + ports: + - '${FE_PORT:-5173}:5173' + volumes: + - .:/app + environment: + HOST: ${HOST:-localhost} + PUBLIC_WS_URL: ${PUBLIC_WS_URL:-ws://localhost:3000/ws} + PUBLIC_BOX_DRAWING_CHARS: ${PUBLIC_BOX_DRAWING_CHARS:-─│┌┐└┘├┤} + + be: + image: oven/bun:latest + working_dir: /app + command: bun run --filter be dev + ports: + - '${BE_PORT:-3000}:3000' + volumes: + - .:/app + environment: + JWT_SECRET: ${JWT_SECRET} + SIGNUP_SECRET: ${SIGNUP_SECRET} + ADMIN_EMAIL: ${ADMIN_EMAIL:-admin@localhost} + DB_PATH: /app/${DB_PATH} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7a7dce0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,30 @@ +name: todo3 + +services: + fe: + build: + context: . + target: frontend + args: + PUBLIC_WS_URL: ${PUBLIC_WS_URL} + PUBLIC_BOX_DRAWING_CHARS: ${PUBLIC_BOX_DRAWING_CHARS:-─│┌┐└┘├┤} + ports: + - '${FE_PORT}:4173' + environment: + HOST: ${HOST} + restart: unless-stopped + + be: + build: + context: . + target: backend + ports: + - '${BE_PORT}:3000' + environment: + JWT_SECRET: ${JWT_SECRET} + SIGNUP_SECRET: ${SIGNUP_SECRET} + ADMIN_EMAIL: ${ADMIN_EMAIL} + DB_PATH: /data/db.sqlite + volumes: + - ${DATA_DIR:-./data}:/data + restart: unless-stopped diff --git a/packages/be/.env b/packages/be/.env deleted file mode 100644 index 927c31d..0000000 --- a/packages/be/.env +++ /dev/null @@ -1,4 +0,0 @@ -JWT_SECRET=FOOBAR -SIGNUP_SECRET=FOOBAR -ADMIN_EMAIL=foo@bar.com -PORT=3000 \ No newline at end of file diff --git a/packages/be/src/db/index.ts b/packages/be/src/db/index.ts index 0845e2b..3e752d8 100644 --- a/packages/be/src/db/index.ts +++ b/packages/be/src/db/index.ts @@ -1,3 +1,3 @@ import { Database } from 'bun:sqlite'; -export const db = new Database('db.sqlite'); +export const db = new Database(process.env.DB_PATH ?? 'db.sqlite'); diff --git a/packages/fe/.env b/packages/fe/.env deleted file mode 100644 index b2aac59..0000000 --- a/packages/fe/.env +++ /dev/null @@ -1,5 +0,0 @@ -#PUBLIC_BOX_DRAWING_CHARS=─│┌┐└┘├┤ -PUBLIC_BOX_DRAWING_CHARS=-|++++++ -PUBLIC_WS_URL=wss://echo.websocket.org -HOST=example.org -PORT=5173 \ No newline at end of file diff --git a/packages/fe/index.html b/packages/fe/index.html index 6345637..74eba93 100644 --- a/packages/fe/index.html +++ b/packages/fe/index.html @@ -3,17 +3,6 @@
- - - -