chore: initialize SkyMoney (api + prisma + docker-compose) ( still in progress
This commit is contained in:
17
README.md
Normal file
17
README.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
TODO:
|
||||||
|
|
||||||
|
Better the logo
|
||||||
|
add caddy files
|
||||||
|
add db routes and set up db
|
||||||
|
test api with docker
|
||||||
|
-Test DB
|
||||||
|
-test functionality
|
||||||
|
-simulate results in scheduling
|
||||||
|
-ensure security
|
||||||
|
build UI and UX
|
||||||
|
add new logo
|
||||||
|
build theme
|
||||||
|
interactivity with tailwind
|
||||||
|
make better Read me
|
||||||
|
add comments to code
|
||||||
|
get ben to post it
|
||||||
4
api/.dockerignore
Normal file
4
api/.dockerignore
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
npm-debug.log
|
||||||
|
.DS_Store
|
||||||
71
api/.gitignore
vendored
Normal file
71
api/.gitignore
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# --- OS / Editors ---
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
.idea/
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
!.vscode/settings.json
|
||||||
|
|
||||||
|
# --- Node / Package Managers ---
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# --- Builds / Caches ---
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.out/
|
||||||
|
.next/
|
||||||
|
.cache/
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# --- Vite / React (web) ---
|
||||||
|
apps/web/dist/
|
||||||
|
web/dist/
|
||||||
|
**/dist/
|
||||||
|
**/.vite/
|
||||||
|
|
||||||
|
# --- TypeScript ---
|
||||||
|
# (tsc output goes to dist/; keep sources)
|
||||||
|
# tsconfig.tsbuildinfo is already ignored via *.tsbuildinfo
|
||||||
|
|
||||||
|
# --- Env / Secrets ---
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# --- Prisma ---
|
||||||
|
# Keep migrations (commit them), ignore local SQLite files if ever used
|
||||||
|
prisma/*.db
|
||||||
|
prisma/*.db-journal
|
||||||
|
**/prisma/*.db
|
||||||
|
**/prisma/*.db-journal
|
||||||
|
# Prisma client is generated into node_modules; no ignore needed
|
||||||
|
|
||||||
|
# --- Logs / PIDs ---
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
*.pid
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# --- Docker / Compose ---
|
||||||
|
# Local overrides and artifacts
|
||||||
|
docker-compose.override.yml
|
||||||
|
# If you keep any local bind-mounted data dirs, ignore them here:
|
||||||
|
data/
|
||||||
|
tmp/
|
||||||
|
|
||||||
|
# --- Caddy (if you check these into repo accidentally) ---
|
||||||
|
caddydata/
|
||||||
|
caddyconfig/
|
||||||
|
|
||||||
|
# --- Misc ---
|
||||||
|
.sass-cache/
|
||||||
|
.eslintcache
|
||||||
|
.stylelintcache
|
||||||
27
api/Dockerfile
Normal file
27
api/Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
FROM node:20-alpine AS deps
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
FROM node:20-alpine AS build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY tsconfig.json ./
|
||||||
|
COPY src ./src
|
||||||
|
COPY prisma ./prisma
|
||||||
|
RUN npx prisma generate
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:20-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
# runtime files
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY --from=build /app/dist ./dist
|
||||||
|
COPY --from=build /app/prisma ./prisma
|
||||||
|
# entrypoint does migrate deploy + start
|
||||||
|
COPY entrypoint.sh ./entrypoint.sh
|
||||||
|
RUN chmod +x /app/entrypoint.sh
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ["/app/entrypoint.sh"]
|
||||||
4
api/entrypoint.sh
Normal file
4
api/entrypoint.sh
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
set -e
|
||||||
|
|
||||||
|
npx prisma migrate deploy
|
||||||
|
node dist/server.js
|
||||||
1651
api/package-lock.json
generated
Normal file
1651
api/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
26
api/package.json
Normal file
26
api/package.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "skymoney-api",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsx watch src/server.ts",
|
||||||
|
"build": "tsc -p tsconfig.json",
|
||||||
|
"start": "node dist/server.js",
|
||||||
|
"generate": "prisma generate",
|
||||||
|
"migrate": "prisma migrate dev",
|
||||||
|
"seed": "tsx src/scripts/seed.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/cors": "^10.0.0",
|
||||||
|
"@prisma/client": "^5.20.0",
|
||||||
|
"fastify": "^4.26.2",
|
||||||
|
"zod": "^3.23.8"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.30",
|
||||||
|
"prisma": "^5.20.0",
|
||||||
|
"tsx": "^4.19.0",
|
||||||
|
"typescript": "^5.6.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
api/prisma.config.ts
Normal file
13
api/prisma.config.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { defineConfig, env } from "prisma/config";
|
||||||
|
import "dotenv/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: "prisma/schema.prisma",
|
||||||
|
migrations: {
|
||||||
|
path: "prisma/migrations",
|
||||||
|
},
|
||||||
|
engine: "classic",
|
||||||
|
datasource: {
|
||||||
|
url: env("DATABASE_URL"),
|
||||||
|
},
|
||||||
|
});
|
||||||
87
api/prisma/schema.prisma
Normal file
87
api/prisma/schema.prisma
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// prisma/schema.prisma
|
||||||
|
generator client { provider = "prisma-client-js" }
|
||||||
|
datasource db { provider = "postgresql"; url = env("DATABASE_URL") }
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
email String @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
variableCategories VariableCategory[]
|
||||||
|
fixedPlans FixedPlan[]
|
||||||
|
incomes IncomeEvent[]
|
||||||
|
allocations Allocation[]
|
||||||
|
transactions Transaction[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model VariableCategory {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
userId String
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
name String
|
||||||
|
percent Int
|
||||||
|
priority Int @default(100)
|
||||||
|
isSavings Boolean @default(false)
|
||||||
|
balanceCents BigInt @default(0)
|
||||||
|
|
||||||
|
@@unique([userId, name])
|
||||||
|
@@check(percent_gte_0, "percent >= 0")
|
||||||
|
@@check(percent_lte_100,"percent <= 100")
|
||||||
|
}
|
||||||
|
|
||||||
|
model FixedPlan {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
userId String
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
name String
|
||||||
|
cycleStart DateTime
|
||||||
|
dueOn DateTime
|
||||||
|
totalCents BigInt
|
||||||
|
fundedCents BigInt @default(0)
|
||||||
|
priority Int @default(100)
|
||||||
|
fundingMode String @default("auto-on-deposit") // or 'by-schedule'
|
||||||
|
scheduleJson Json?
|
||||||
|
|
||||||
|
@@unique([userId, name])
|
||||||
|
@@check(total_nonneg, "totalCents >= 0")
|
||||||
|
@@check(funded_nonneg, "fundedCents >= 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
model IncomeEvent {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
userId String
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
postedAt DateTime
|
||||||
|
amountCents BigInt
|
||||||
|
|
||||||
|
allocations Allocation[]
|
||||||
|
|
||||||
|
@@check(pos_amount, "amountCents > 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Allocation {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
userId String
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
kind String // 'savings' | 'variable' | 'fixed'
|
||||||
|
toId String
|
||||||
|
amountCents BigInt
|
||||||
|
incomeId String?
|
||||||
|
income IncomeEvent? @relation(fields: [incomeId], references: [id])
|
||||||
|
|
||||||
|
@@check(pos_amount, "amountCents > 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Transaction {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
userId String
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
occurredAt DateTime
|
||||||
|
kind String // 'variable-spend' | 'fixed-payment'
|
||||||
|
categoryId String?
|
||||||
|
planId String?
|
||||||
|
amountCents BigInt
|
||||||
|
|
||||||
|
@@check(pos_amount, "amountCents > 0")
|
||||||
|
}
|
||||||
20
api/src/scripts/seed.ts
Normal file
20
api/src/scripts/seed.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// prisma/seed.ts (optional: creates one demo user + categories)
|
||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
const db = new PrismaClient();
|
||||||
|
async function main() {
|
||||||
|
const user = await db.user.upsert({
|
||||||
|
where: { email: 'demo@user.test' },
|
||||||
|
update: {},
|
||||||
|
create: { email: 'demo@user.test' }
|
||||||
|
});
|
||||||
|
await db.variableCategory.createMany({
|
||||||
|
data: [
|
||||||
|
{ userId: user.id, name: 'Groceries', percent: 30, priority: 10 },
|
||||||
|
{ userId: user.id, name: 'Gas', percent: 20, priority: 20 },
|
||||||
|
{ userId: user.id, name: 'Fun', percent: 50, priority: 30 }
|
||||||
|
],
|
||||||
|
skipDuplicates: true
|
||||||
|
});
|
||||||
|
console.log('Seeded:', user.email);
|
||||||
|
}
|
||||||
|
main().finally(()=>db.$disconnect());
|
||||||
10
api/src/server.ts
Normal file
10
api/src/server.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import Fastify from "fastify";
|
||||||
|
|
||||||
|
const app = Fastify({ logger: true });
|
||||||
|
app.get("/health", async () => ({ ok: true }));
|
||||||
|
|
||||||
|
const port = Number(process.env.PORT ?? 8080);
|
||||||
|
app.listen({ port, host: "0.0.0.0" }).catch((err) => {
|
||||||
|
app.log.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
15
api/tsconfig.json
Normal file
15
api/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ES2022",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist", "prisma.config.ts"]
|
||||||
|
}
|
||||||
69
docker-compose.yml
Normal file
69
docker-compose.yml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# docker-compose.yml (root)
|
||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
x-env: &api_env
|
||||||
|
NODE_ENV: production
|
||||||
|
PORT: "8080"
|
||||||
|
DATABASE_URL: postgres://app:app@postgres:5432/skymoney
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:15
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: skymoney
|
||||||
|
POSTGRES_USER: app
|
||||||
|
POSTGRES_PASSWORD: app
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
api:
|
||||||
|
build:
|
||||||
|
context: ./api
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
environment:
|
||||||
|
<<: *api_env
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
expose:
|
||||||
|
- "8080"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
# OPTIONAL: build web and serve with Caddy
|
||||||
|
web:
|
||||||
|
# If you want Compose to build your Vite app automatically:
|
||||||
|
image: node:20-alpine
|
||||||
|
working_dir: /app
|
||||||
|
volumes:
|
||||||
|
- ./web:/app
|
||||||
|
- webdist:/build-out
|
||||||
|
command: >
|
||||||
|
sh -c "npm ci && npm run build && rm -rf /build-out/* && cp -r dist/* /build-out/"
|
||||||
|
environment:
|
||||||
|
# If your web build needs an API base url:
|
||||||
|
- VITE_API_BASE=/api
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
# This runs once at up; to rebuild, `docker compose up -d --build web`
|
||||||
|
restart: "no"
|
||||||
|
|
||||||
|
caddy:
|
||||||
|
image: caddy:2
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
volumes:
|
||||||
|
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
- caddydata:/data
|
||||||
|
- caddyconfig:/config
|
||||||
|
# Serve built web files (if using web service above)
|
||||||
|
- webdist:/srv/site
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
|
caddydata:
|
||||||
|
caddyconfig:
|
||||||
|
webdist:
|
||||||
Reference in New Issue
Block a user