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