Setup initial project

This commit is contained in:
Aerilyn Weber 2026-03-27 14:50:34 +09:00
commit db79af06f7
119 changed files with 20761 additions and 0 deletions

10
docker/.dockerignore Normal file
View file

@ -0,0 +1,10 @@
node_modules
.next
dist
.turbo
*.tsbuildinfo
.env*
.git
docs/
coverage
*.md

40
docker/Dockerfile.api Normal file
View file

@ -0,0 +1,40 @@
# ---- Base ----
FROM node:22-alpine AS base
WORKDIR /app
# ---- Dependencies ----
FROM base AS dependencies
COPY package.json package-lock.json* .npmrc* ./
COPY packages/api/package.json ./packages/api/
COPY packages/shared/package.json ./packages/shared/
RUN npm install --workspace=packages/shared --workspace=packages/api 2>/dev/null || npm install
# ---- Build ----
FROM dependencies AS build
COPY packages/shared/ ./packages/shared/
COPY packages/api/ ./packages/api/
COPY tsconfig.base.json ./
RUN npm run build --workspace=packages/shared
RUN npm run build --workspace=packages/api
# ---- Production ----
FROM base AS production
ENV NODE_ENV=production
COPY --from=build /app/packages/api/dist ./packages/api/dist
COPY --from=build /app/packages/shared/dist ./packages/shared/dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/packages/api/node_modules ./packages/api/node_modules 2>/dev/null || true
COPY --from=build /app/packages/api/package.json ./packages/api/
COPY --from=build /app/packages/shared/package.json ./packages/shared/
COPY --from=build /app/package.json ./
EXPOSE 3001
CMD ["node", "packages/api/dist/main.js"]
# ---- Development ----
FROM dependencies AS development
COPY packages/shared/ ./packages/shared/
COPY packages/api/ ./packages/api/
COPY tsconfig.base.json ./
RUN npm run build --workspace=packages/shared
EXPOSE 3001
CMD ["node_modules/.bin/tsx", "watch", "packages/api/src/main.ts"]

42
docker/Dockerfile.web Normal file
View file

@ -0,0 +1,42 @@
# ---- Base ----
FROM node:22-alpine AS base
WORKDIR /app
# ---- Dependencies ----
FROM base AS dependencies
COPY package.json package-lock.json* .npmrc* ./
COPY packages/web/package.json ./packages/web/
COPY packages/shared/package.json ./packages/shared/
RUN npm install --workspace=packages/shared --workspace=packages/web 2>/dev/null || npm install
# ---- Build ----
FROM dependencies AS build
COPY packages/shared/ ./packages/shared/
COPY packages/web/ ./packages/web/
COPY tsconfig.base.json ./
RUN npm run build --workspace=packages/shared
RUN npm run build --workspace=packages/web
# ---- Production ----
FROM base AS production
ENV NODE_ENV=production
COPY --from=build /app/packages/web/.next ./packages/web/.next
COPY --from=build /app/packages/web/public ./packages/web/public
COPY --from=build /app/packages/web/package.json ./packages/web/
COPY --from=build /app/packages/shared/dist ./packages/shared/dist
COPY --from=build /app/packages/shared/package.json ./packages/shared/
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./
EXPOSE 3000
WORKDIR /app/packages/web
CMD ["node", "/app/node_modules/.bin/next", "start", "-p", "3000"]
# ---- Development ----
FROM dependencies AS development
COPY packages/shared/ ./packages/shared/
COPY packages/web/ ./packages/web/
COPY tsconfig.base.json ./
RUN npm run build --workspace=packages/shared
EXPOSE 3000
WORKDIR /app/packages/web
CMD ["node", "/app/node_modules/.bin/next", "dev", "--port", "3000", "--turbopack"]

139
docker/docker-compose.yml Normal file
View file

@ -0,0 +1,139 @@
services:
mongodb:
image: mongo:7
container_name: meshitrack-mongodb
restart: unless-stopped
ports:
- '27017:27017'
environment:
MONGO_INITDB_ROOT_USERNAME: meshitrack
MONGO_INITDB_ROOT_PASSWORD: ${MONGO_PASSWORD:-devpassword}
MONGO_INITDB_DATABASE: meshitrack
volumes:
- mongo-data:/data/db
- ./mongo/init-replica.js:/docker-entrypoint-initdb.d/init-replica.js:ro
- ./mongo/keyfile:/tmp/mongo-keyfile:ro
- ./mongo/docker-entrypoint-init.sh:/usr/local/bin/docker-entrypoint-init.sh:ro
entrypoint: ['/bin/bash', '/usr/local/bin/docker-entrypoint-init.sh']
command: ['--replSet', 'rs0', '--bind_ip_all', '--keyFile', '/etc/mongodb/keyfile']
healthcheck:
test: ['CMD', 'mongosh', '--eval', "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
keycloak:
image: quay.io/keycloak/keycloak:24.0
container_name: meshitrack-keycloak
restart: unless-stopped
ports:
- '8080:8080'
environment:
KC_DB: dev-file
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD:-admin}
KC_HEALTH_ENABLED: 'true'
KC_HOSTNAME_URL: http://localhost:8080
command: start-dev --import-realm
volumes:
- ./keycloak/realm-export.json:/opt/keycloak/data/import/realm-export.json:ro
healthcheck:
test:
[
'CMD',
'bash',
'-c',
"exec 3<>/dev/tcp/127.0.0.1/8080 && echo -e 'GET /health/ready HTTP/1.0\\r\\n\\r\\n' >&3 && cat <&3 | grep -q 'UP'",
]
interval: 15s
timeout: 10s
retries: 15
start_period: 60s
mongo-init:
image: mongo:7
container_name: meshitrack-mongo-init
restart: on-failure
depends_on:
mongodb:
condition: service_healthy
entrypoint: >
mongosh --host mongodb:27017
--username meshitrack
--password ${MONGO_PASSWORD:-devpassword}
--authenticationDatabase admin
--eval "
try { rs.initiate({_id:'rs0',members:[{_id:0,host:'mongodb:27017'}]}) }
catch(e) { if(e.codeName !== 'AlreadyInitialized') throw e }
"
api:
build:
context: ..
dockerfile: docker/Dockerfile.api
target: development
container_name: meshitrack-api
restart: unless-stopped
ports:
- '3001:3001'
depends_on:
mongo-init:
condition: service_completed_successfully
keycloak:
condition: service_healthy
environment:
NODE_ENV: development
PORT: 3001
MONGODB_URI: mongodb://meshitrack:${MONGO_PASSWORD:-devpassword}@mongodb:27017/meshitrack?authSource=admin&replicaSet=rs0
KEYCLOAK_URL: http://keycloak:8080
KEYCLOAK_ISSUER_URL: http://localhost:8080
KEYCLOAK_REALM: meshitrack
KEYCLOAK_CLIENT_ID: meshitrack-api
CORS_ORIGIN: http://localhost:3000
volumes:
- ../packages/api/src:/app/packages/api/src
- ../packages/shared/src:/app/packages/shared/src
web:
build:
context: ..
dockerfile: docker/Dockerfile.web
target: development
container_name: meshitrack-web
restart: unless-stopped
ports:
- '3000:3000'
depends_on:
- api
environment:
NODE_ENV: development
NEXT_PUBLIC_API_URL: http://localhost:3001/api/v1
NEXT_PUBLIC_KEYCLOAK_URL: http://localhost:8080
NEXT_PUBLIC_KEYCLOAK_REALM: meshitrack
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID: meshitrack-web
KEYCLOAK_URL: http://keycloak:8080
KEYCLOAK_REALM: meshitrack
KEYCLOAK_CLIENT_ID: meshitrack-web
KEYCLOAK_CLIENT_SECRET: dev-web-client-secret
NEXTAUTH_URL: http://localhost:3000
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET:-dev-secret-change-in-production}
volumes:
- ../packages/web/src:/app/packages/web/src
mongo-express:
image: mongo-express
container_name: meshitrack-mongo-express
ports:
- '8081:8081'
depends_on:
mongodb:
condition: service_healthy
environment:
ME_CONFIG_MONGODB_ADMINUSERNAME: meshitrack
ME_CONFIG_MONGODB_ADMINPASSWORD: ${MONGO_PASSWORD:-devpassword}
ME_CONFIG_MONGODB_URL: mongodb://meshitrack:${MONGO_PASSWORD:-devpassword}@mongodb:27017/
profiles:
- dev
volumes:
mongo-data:

View file

@ -0,0 +1,123 @@
{
"realm": "meshitrack",
"enabled": true,
"displayName": "MeshiTrack",
"registrationAllowed": true,
"loginWithEmailAllowed": true,
"duplicateEmailsAllowed": false,
"resetPasswordAllowed": true,
"editUsernameAllowed": false,
"sslRequired": "none",
"accessTokenLifespan": 1800,
"ssoSessionIdleTimeout": 86400,
"ssoSessionMaxLifespan": 604800,
"roles": {
"realm": [
{
"name": "admin",
"description": "Administrator role - can manage household settings",
"composite": false
},
{
"name": "member",
"description": "Standard member role",
"composite": false
}
]
},
"defaultRoles": ["member"],
"clients": [
{
"clientId": "meshitrack-web",
"name": "MeshiTrack Web App",
"enabled": true,
"publicClient": false,
"secret": "dev-web-client-secret",
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"redirectUris": [
"http://localhost:3000/*",
"http://localhost:3000",
"http://localhost:3000/api/auth/callback/keycloak"
],
"webOrigins": ["http://localhost:3000"],
"attributes": {},
"protocolMappers": [
{
"name": "household-ids-mapper",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"config": {
"claim.name": "householdIds",
"user.attribute": "householdIds",
"jsonType.label": "JSON",
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true",
"multivalued": "false"
}
},
{
"name": "audience-mapper",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-mapper",
"config": {
"included.client.audience": "meshitrack-api",
"id.token.claim": "false",
"access.token.claim": "true"
}
}
]
},
{
"clientId": "meshitrack-api",
"name": "MeshiTrack API",
"enabled": true,
"publicClient": false,
"bearerOnly": true,
"standardFlowEnabled": false,
"directAccessGrantsEnabled": false
}
],
"users": [
{
"username": "testuser1",
"enabled": true,
"email": "testuser1@meshitrack.local",
"firstName": "Test",
"lastName": "User1",
"emailVerified": true,
"credentials": [
{
"type": "password",
"value": "test1234",
"temporary": false
}
],
"realmRoles": ["member", "admin"],
"attributes": {
"householdIds": ["[\"000000000000000000000001\"]"]
}
},
{
"username": "testuser2",
"enabled": true,
"email": "testuser2@meshitrack.local",
"firstName": "Test",
"lastName": "User2",
"emailVerified": true,
"credentials": [
{
"type": "password",
"value": "test1234",
"temporary": false
}
],
"realmRoles": ["member"],
"attributes": {
"householdIds": ["[\"000000000000000000000001\"]"]
}
}
]
}

View file

@ -0,0 +1,7 @@
#!/bin/bash
# Copy the mounted keyfile and set required permissions
mkdir -p /etc/mongodb
cp /tmp/mongo-keyfile /etc/mongodb/keyfile
chmod 400 /etc/mongodb/keyfile
chown mongodb:mongodb /etc/mongodb/keyfile
exec docker-entrypoint.sh "$@"

View file

@ -0,0 +1,15 @@
// MongoDB replica set initialization script
// Runs on first startup via /docker-entrypoint-initdb.d/
try {
rs.initiate({
_id: 'rs0',
members: [{ _id: 0, host: 'mongodb:27017' }],
});
print('Replica set initiated successfully');
} catch (e) {
if (e.codeName === 'AlreadyInitialized') {
print('Replica set already initialized');
} else {
print('Error initiating replica set: ' + e.message);
}
}