create-forgeon 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/bin/create-forgeon.mjs +265 -0
- package/package.json +16 -0
- package/templates/base/.editorconfig +11 -0
- package/templates/base/README.md +56 -0
- package/templates/base/apps/api/Dockerfile +23 -0
- package/templates/base/apps/api/package.json +40 -0
- package/templates/base/apps/api/prisma/migrations/0001_init/migration.sql +12 -0
- package/templates/base/apps/api/prisma/migrations/migration_lock.toml +1 -0
- package/templates/base/apps/api/prisma/schema.prisma +15 -0
- package/templates/base/apps/api/prisma/seed.ts +20 -0
- package/templates/base/apps/api/src/app.module.ts +34 -0
- package/templates/base/apps/api/src/common/dto/echo-query.dto.ts +6 -0
- package/templates/base/apps/api/src/common/filters/app-exception.filter.ts +130 -0
- package/templates/base/apps/api/src/config/app.config.ts +13 -0
- package/templates/base/apps/api/src/health/health.controller.ts +31 -0
- package/templates/base/apps/api/src/main.ts +26 -0
- package/templates/base/apps/api/src/prisma/prisma.module.ts +9 -0
- package/templates/base/apps/api/src/prisma/prisma.service.ts +27 -0
- package/templates/base/apps/api/tsconfig.build.json +9 -0
- package/templates/base/apps/api/tsconfig.json +9 -0
- package/templates/base/apps/web/Dockerfile +13 -0
- package/templates/base/apps/web/index.html +13 -0
- package/templates/base/apps/web/package.json +23 -0
- package/templates/base/apps/web/src/App.tsx +37 -0
- package/templates/base/apps/web/src/main.tsx +9 -0
- package/templates/base/apps/web/src/styles.css +33 -0
- package/templates/base/apps/web/tsconfig.json +18 -0
- package/templates/base/apps/web/vite.config.ts +15 -0
- package/templates/base/docs/AI/ARCHITECTURE.md +38 -0
- package/templates/base/docs/AI/PROJECT.md +32 -0
- package/templates/base/docs/AI/TASKS.md +48 -0
- package/templates/base/docs/README.md +5 -0
- package/templates/base/infra/docker/.env.example +10 -0
- package/templates/base/infra/docker/compose.yml +45 -0
- package/templates/base/infra/docker/nginx.Dockerfile +16 -0
- package/templates/base/infra/nginx/nginx.conf +32 -0
- package/templates/base/package.json +24 -0
- package/templates/base/packages/core/README.md +4 -0
- package/templates/base/packages/core/package.json +14 -0
- package/templates/base/packages/core/src/index.ts +1 -0
- package/templates/base/packages/core/tsconfig.json +8 -0
- package/templates/base/packages/i18n/package.json +19 -0
- package/templates/base/packages/i18n/src/forgeon-i18n.module.ts +47 -0
- package/templates/base/packages/i18n/src/index.ts +2 -0
- package/templates/base/packages/i18n/tsconfig.json +9 -0
- package/templates/base/pnpm-workspace.yaml +3 -0
- package/templates/base/resources/i18n/en/common.json +5 -0
- package/templates/base/resources/i18n/en/validation.json +3 -0
- package/templates/base/resources/i18n/uk/common.json +5 -0
- package/templates/base/resources/i18n/uk/validation.json +3 -0
- package/templates/base/tsconfig.base.json +17 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Controller, Get, Optional, Query } from '@nestjs/common';
|
|
2
|
+
import { I18nService } from 'nestjs-i18n';
|
|
3
|
+
import { EchoQueryDto } from '../common/dto/echo-query.dto';
|
|
4
|
+
|
|
5
|
+
@Controller('health')
|
|
6
|
+
export class HealthController {
|
|
7
|
+
constructor(@Optional() private readonly i18n?: I18nService) {}
|
|
8
|
+
|
|
9
|
+
@Get()
|
|
10
|
+
getHealth(@Query('lang') lang?: string) {
|
|
11
|
+
return {
|
|
12
|
+
status: 'ok',
|
|
13
|
+
message: this.translate('common.ok', lang),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@Get('echo')
|
|
18
|
+
getEcho(@Query() query: EchoQueryDto) {
|
|
19
|
+
return { value: query.value };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private translate(key: string, lang?: string): string {
|
|
23
|
+
if (!this.i18n) {
|
|
24
|
+
if (key === 'common.ok') return 'OK';
|
|
25
|
+
return key;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const value = this.i18n.t(key, { lang, defaultValue: key });
|
|
29
|
+
return typeof value === 'string' ? value : key;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { ValidationPipe } from '@nestjs/common';
|
|
3
|
+
import { ConfigService } from '@nestjs/config';
|
|
4
|
+
import { NestFactory } from '@nestjs/core';
|
|
5
|
+
import { AppModule } from './app.module';
|
|
6
|
+
import { AppExceptionFilter } from './common/filters/app-exception.filter';
|
|
7
|
+
|
|
8
|
+
async function bootstrap() {
|
|
9
|
+
const app = await NestFactory.create(AppModule);
|
|
10
|
+
|
|
11
|
+
app.setGlobalPrefix('api');
|
|
12
|
+
app.useGlobalPipes(
|
|
13
|
+
new ValidationPipe({
|
|
14
|
+
whitelist: true,
|
|
15
|
+
transform: true,
|
|
16
|
+
}),
|
|
17
|
+
);
|
|
18
|
+
app.useGlobalFilters(app.get(AppExceptionFilter));
|
|
19
|
+
|
|
20
|
+
const configService = app.get(ConfigService);
|
|
21
|
+
const port = configService.get<number>('app.port') ?? 3000;
|
|
22
|
+
|
|
23
|
+
await app.listen(port);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
bootstrap();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { PrismaClient } from '@prisma/client';
|
|
3
|
+
|
|
4
|
+
@Injectable()
|
|
5
|
+
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
|
|
6
|
+
constructor() {
|
|
7
|
+
const databaseUrl =
|
|
8
|
+
process.env.DATABASE_URL ??
|
|
9
|
+
'postgresql://postgres:postgres@localhost:5432/app?schema=public';
|
|
10
|
+
|
|
11
|
+
super({
|
|
12
|
+
datasources: {
|
|
13
|
+
db: {
|
|
14
|
+
url: databaseUrl,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async onModuleInit(): Promise<void> {
|
|
21
|
+
await this.$connect();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async onModuleDestroy(): Promise<void> {
|
|
25
|
+
await this.$disconnect();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
FROM node:20-alpine
|
|
2
|
+
WORKDIR /app
|
|
3
|
+
RUN corepack enable
|
|
4
|
+
|
|
5
|
+
COPY package.json pnpm-workspace.yaml ./
|
|
6
|
+
COPY apps/web/package.json apps/web/package.json
|
|
7
|
+
RUN pnpm install --frozen-lockfile=false
|
|
8
|
+
|
|
9
|
+
COPY apps/web apps/web
|
|
10
|
+
WORKDIR /app/apps/web
|
|
11
|
+
RUN pnpm build
|
|
12
|
+
|
|
13
|
+
CMD ["pnpm", "preview", "--host", "0.0.0.0", "--port", "5173"]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Forgeon Web</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
13
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forgeon/web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "tsc -b && vite build",
|
|
9
|
+
"preview": "vite preview"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"react": "^18.3.1",
|
|
13
|
+
"react-dom": "^18.3.1"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/react": "^18.3.3",
|
|
17
|
+
"@types/react-dom": "^18.3.0",
|
|
18
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
19
|
+
"typescript": "^5.7.3",
|
|
20
|
+
"vite": "^5.4.11"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import './styles.css';
|
|
3
|
+
|
|
4
|
+
type HealthResponse = {
|
|
5
|
+
status: string;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export default function App() {
|
|
10
|
+
const [data, setData] = useState<HealthResponse | null>(null);
|
|
11
|
+
const [error, setError] = useState<string | null>(null);
|
|
12
|
+
|
|
13
|
+
const checkApi = async () => {
|
|
14
|
+
setError(null);
|
|
15
|
+
try {
|
|
16
|
+
const response = await fetch('/api/health');
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
throw new Error(`HTTP ${response.status}`);
|
|
19
|
+
}
|
|
20
|
+
const payload = (await response.json()) as HealthResponse;
|
|
21
|
+
setData(payload);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
setError(err instanceof Error ? err.message : 'Unknown error');
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<main className="page">
|
|
29
|
+
<h1>Forgeon Fullstack Scaffold</h1>
|
|
30
|
+
<p>Default frontend preset: React + Vite + TypeScript.</p>
|
|
31
|
+
<button onClick={checkApi}>Check API health</button>
|
|
32
|
+
{data ? <pre>{JSON.stringify(data, null, 2)}</pre> : null}
|
|
33
|
+
{error ? <p className="error">{error}</p> : null}
|
|
34
|
+
</main>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
body {
|
|
6
|
+
margin: 0;
|
|
7
|
+
background: #f8fafc;
|
|
8
|
+
color: #0f172a;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.page {
|
|
12
|
+
max-width: 720px;
|
|
13
|
+
margin: 3rem auto;
|
|
14
|
+
padding: 0 1rem;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
button {
|
|
18
|
+
padding: 0.6rem 1rem;
|
|
19
|
+
border: 0;
|
|
20
|
+
border-radius: 0.5rem;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pre {
|
|
25
|
+
background: #e2e8f0;
|
|
26
|
+
padding: 1rem;
|
|
27
|
+
border-radius: 0.5rem;
|
|
28
|
+
overflow: auto;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.error {
|
|
32
|
+
color: #b91c1c;
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": false,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"types": ["vite/client"]
|
|
16
|
+
},
|
|
17
|
+
"include": ["src"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [react()],
|
|
6
|
+
server: {
|
|
7
|
+
port: 5173,
|
|
8
|
+
proxy: {
|
|
9
|
+
'/api': {
|
|
10
|
+
target: 'http://localhost:3000',
|
|
11
|
+
changeOrigin: true,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# ARCHITECTURE
|
|
2
|
+
|
|
3
|
+
## Monorepo Layout
|
|
4
|
+
|
|
5
|
+
- `apps/*` - deployable apps
|
|
6
|
+
- `packages/*` - reusable modules/presets
|
|
7
|
+
- `infra/*` - runtime infrastructure
|
|
8
|
+
- `resources/*` - static assets (translations)
|
|
9
|
+
|
|
10
|
+
## Environment Flags
|
|
11
|
+
|
|
12
|
+
- `PORT` - API port (default 3000)
|
|
13
|
+
- `DATABASE_URL` - Prisma Postgres connection
|
|
14
|
+
- `I18N_ENABLED` - toggles i18n package wiring
|
|
15
|
+
- `I18N_DEFAULT_LANG` - default language
|
|
16
|
+
- `I18N_FALLBACK_LANG` - fallback language
|
|
17
|
+
|
|
18
|
+
## Default DB Stack
|
|
19
|
+
|
|
20
|
+
Current default is Prisma + Postgres.
|
|
21
|
+
|
|
22
|
+
- Prisma schema and migrations live in `apps/api/prisma`
|
|
23
|
+
- DB access is encapsulated via `PrismaModule` (`apps/api/src/prisma`)
|
|
24
|
+
|
|
25
|
+
## Future DB Presets (Not Implemented Yet)
|
|
26
|
+
|
|
27
|
+
A future preset can switch DB by:
|
|
28
|
+
1. Replacing `PrismaModule` with another DB module package (for example Mongo package).
|
|
29
|
+
2. Updating `infra/docker/compose.yml` DB service.
|
|
30
|
+
3. Updating `DATABASE_URL` and related env keys.
|
|
31
|
+
4. Keeping app-level services dependent only on repository/data-access abstractions.
|
|
32
|
+
|
|
33
|
+
## Future Feature Modules
|
|
34
|
+
|
|
35
|
+
Reusable features should be added as workspace packages and imported by apps as needed:
|
|
36
|
+
|
|
37
|
+
- `packages/core` for shared backend primitives
|
|
38
|
+
- Additional packages for auth presets, guards, queues, mailers, etc.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# PROJECT
|
|
2
|
+
|
|
3
|
+
## What This Repository Is
|
|
4
|
+
|
|
5
|
+
A canonical fullstack monorepo scaffold intended to be reused as a project starter.
|
|
6
|
+
|
|
7
|
+
## Structure
|
|
8
|
+
|
|
9
|
+
- `apps/api` - NestJS backend
|
|
10
|
+
- `apps/web` - frontend scaffold (default React + Vite + TS)
|
|
11
|
+
- `packages/core` - shared backend core placeholder
|
|
12
|
+
- `packages/i18n` - reusable nestjs-i18n integration package
|
|
13
|
+
- `infra` - Docker Compose + Nginx
|
|
14
|
+
- `resources/i18n` - translation dictionaries
|
|
15
|
+
- `docs` - documentation and AI workflow prompts
|
|
16
|
+
|
|
17
|
+
## Run Modes
|
|
18
|
+
|
|
19
|
+
### Dev mode
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm install
|
|
23
|
+
pnpm dev
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Docker mode
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
docker compose --env-file infra/docker/.env.example -f infra/docker/compose.yml up --build
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The API uses Prisma and expects `DATABASE_URL` from env.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# TASKS
|
|
2
|
+
|
|
3
|
+
## Feature Discovery Matrix
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
Scan this monorepo and build a backend feature matrix by app/package.
|
|
7
|
+
Use only evidence from code and dependencies.
|
|
8
|
+
Output:
|
|
9
|
+
1) taxonomy by category
|
|
10
|
+
2) feature comparison table
|
|
11
|
+
3) common core
|
|
12
|
+
4) unique features
|
|
13
|
+
5) architectural inconsistencies
|
|
14
|
+
Include file references for every feature.
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Add Module Package
|
|
18
|
+
|
|
19
|
+
```text
|
|
20
|
+
Create a new reusable package under packages/ for <feature-name>.
|
|
21
|
+
Requirements:
|
|
22
|
+
- minimal API
|
|
23
|
+
- NestJS-compatible module
|
|
24
|
+
- docs in package README
|
|
25
|
+
- wire into apps/api conditionally via env flag
|
|
26
|
+
- keep backward compatibility
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Refactor Core
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
Move shared backend logic from apps/api into packages/core.
|
|
33
|
+
Do not change behavior.
|
|
34
|
+
Update imports, package dependencies, and docs.
|
|
35
|
+
Run build checks and show changed files.
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Generate Preset
|
|
39
|
+
|
|
40
|
+
```text
|
|
41
|
+
Create a new preset flow for create-forgeon:
|
|
42
|
+
- add new flag(s)
|
|
43
|
+
- update interactive questions
|
|
44
|
+
- update generated files
|
|
45
|
+
- keep defaults: Prisma + Postgres, React + Vite + TS
|
|
46
|
+
- update docs/AI/ARCHITECTURE.md
|
|
47
|
+
```
|
|
48
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
services:
|
|
2
|
+
db:
|
|
3
|
+
image: postgres:16-alpine
|
|
4
|
+
restart: unless-stopped
|
|
5
|
+
environment:
|
|
6
|
+
POSTGRES_USER: ${POSTGRES_USER}
|
|
7
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
8
|
+
POSTGRES_DB: ${POSTGRES_DB}
|
|
9
|
+
ports:
|
|
10
|
+
- "5432:5432"
|
|
11
|
+
volumes:
|
|
12
|
+
- db_data:/var/lib/postgresql/data
|
|
13
|
+
healthcheck:
|
|
14
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
|
|
15
|
+
interval: 10s
|
|
16
|
+
timeout: 5s
|
|
17
|
+
retries: 10
|
|
18
|
+
|
|
19
|
+
api:
|
|
20
|
+
build:
|
|
21
|
+
context: ../..
|
|
22
|
+
dockerfile: apps/api/Dockerfile
|
|
23
|
+
restart: unless-stopped
|
|
24
|
+
environment:
|
|
25
|
+
PORT: ${PORT}
|
|
26
|
+
DATABASE_URL: ${DATABASE_URL}
|
|
27
|
+
I18N_ENABLED: ${I18N_ENABLED}
|
|
28
|
+
I18N_DEFAULT_LANG: ${I18N_DEFAULT_LANG}
|
|
29
|
+
I18N_FALLBACK_LANG: ${I18N_FALLBACK_LANG}
|
|
30
|
+
depends_on:
|
|
31
|
+
db:
|
|
32
|
+
condition: service_healthy
|
|
33
|
+
|
|
34
|
+
nginx:
|
|
35
|
+
build:
|
|
36
|
+
context: ../..
|
|
37
|
+
dockerfile: infra/docker/nginx.Dockerfile
|
|
38
|
+
restart: unless-stopped
|
|
39
|
+
depends_on:
|
|
40
|
+
- api
|
|
41
|
+
ports:
|
|
42
|
+
- "8080:80"
|
|
43
|
+
|
|
44
|
+
volumes:
|
|
45
|
+
db_data:
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
FROM node:20-alpine AS web-builder
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
RUN corepack enable
|
|
5
|
+
|
|
6
|
+
COPY package.json pnpm-workspace.yaml ./
|
|
7
|
+
COPY apps/web/package.json apps/web/package.json
|
|
8
|
+
RUN pnpm install --frozen-lockfile=false
|
|
9
|
+
|
|
10
|
+
COPY apps/web apps/web
|
|
11
|
+
WORKDIR /app/apps/web
|
|
12
|
+
RUN pnpm build
|
|
13
|
+
|
|
14
|
+
FROM nginx:1.27-alpine
|
|
15
|
+
COPY infra/nginx/nginx.conf /etc/nginx/nginx.conf
|
|
16
|
+
COPY --from=web-builder /app/apps/web/dist /usr/share/nginx/html
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
worker_processes auto;
|
|
2
|
+
|
|
3
|
+
events {
|
|
4
|
+
worker_connections 1024;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
http {
|
|
8
|
+
include /etc/nginx/mime.types;
|
|
9
|
+
default_type application/octet-stream;
|
|
10
|
+
sendfile on;
|
|
11
|
+
|
|
12
|
+
server {
|
|
13
|
+
listen 80;
|
|
14
|
+
server_name _;
|
|
15
|
+
|
|
16
|
+
root /usr/share/nginx/html;
|
|
17
|
+
index index.html;
|
|
18
|
+
|
|
19
|
+
location /api/ {
|
|
20
|
+
proxy_pass http://api:3000/api/;
|
|
21
|
+
proxy_http_version 1.1;
|
|
22
|
+
proxy_set_header Host $host;
|
|
23
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
24
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
25
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
location / {
|
|
29
|
+
try_files $uri /index.html;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "forgeon",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"packageManager": "pnpm@10.0.0",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "pnpm --parallel --filter @forgeon/api --filter @forgeon/web dev",
|
|
8
|
+
"build": "pnpm -r build",
|
|
9
|
+
"postinstall": "pnpm --filter @forgeon/api prisma:generate",
|
|
10
|
+
"create:forgeon": "node scripts/create-forgeon.mjs",
|
|
11
|
+
"docker:up": "docker compose --env-file infra/docker/.env.example -f infra/docker/compose.yml up --build",
|
|
12
|
+
"docker:down": "docker compose -f infra/docker/compose.yml down -v"
|
|
13
|
+
},
|
|
14
|
+
"pnpm": {
|
|
15
|
+
"onlyBuiltDependencies": [
|
|
16
|
+
"@nestjs/core",
|
|
17
|
+
"@prisma/client",
|
|
18
|
+
"@prisma/engines",
|
|
19
|
+
"esbuild",
|
|
20
|
+
"prisma"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forgeon/i18n",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc -p tsconfig.json"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"@nestjs/common": "^11.0.1",
|
|
12
|
+
"nestjs-i18n": "^10.5.1"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@types/node": "^22.10.7",
|
|
16
|
+
"typescript": "^5.7.3"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { DynamicModule, Module } from '@nestjs/common';
|
|
2
|
+
import {
|
|
3
|
+
AcceptLanguageResolver,
|
|
4
|
+
I18nJsonLoader,
|
|
5
|
+
I18nModule,
|
|
6
|
+
QueryResolver,
|
|
7
|
+
} from 'nestjs-i18n';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
|
|
10
|
+
export interface ForgeonI18nOptions {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
defaultLang: string;
|
|
13
|
+
fallbackLang: string;
|
|
14
|
+
path?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@Module({})
|
|
18
|
+
export class ForgeonI18nModule {
|
|
19
|
+
static register(options: ForgeonI18nOptions): DynamicModule {
|
|
20
|
+
if (!options.enabled) {
|
|
21
|
+
return { module: ForgeonI18nModule };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const translationsPath =
|
|
25
|
+
options.path ?? join(process.cwd(), 'resources', 'i18n');
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
module: ForgeonI18nModule,
|
|
29
|
+
imports: [
|
|
30
|
+
I18nModule.forRoot({
|
|
31
|
+
fallbackLanguage: options.fallbackLang,
|
|
32
|
+
loader: I18nJsonLoader,
|
|
33
|
+
loaderOptions: {
|
|
34
|
+
path: translationsPath,
|
|
35
|
+
watch: false,
|
|
36
|
+
},
|
|
37
|
+
resolvers: [
|
|
38
|
+
{ use: AcceptLanguageResolver, options: { matchType: 'strict-loose' } },
|
|
39
|
+
{ use: QueryResolver, options: ['lang'] },
|
|
40
|
+
],
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
43
|
+
exports: [I18nModule],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|