chore: initial project scaffold
Setup monorepo structure with pnpm workspaces, Turborepo, TypeScript, Biome, Docker Compose (PostgreSQL, Redis, MinIO), Nginx configs, PM2 ecosystem, and SSL certificates.
This commit is contained in:
45
.gitignore
vendored
Normal file
45
.gitignore
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
.pnpm-store/
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
.next/
|
||||
.turbo/
|
||||
out/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Backups
|
||||
backups/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Docker secrets
|
||||
docker/.env
|
||||
|
||||
# PM2
|
||||
.pm2/
|
||||
|
||||
# Coverage
|
||||
coverage/
|
||||
|
||||
# Temp
|
||||
tmp/
|
||||
temp/
|
||||
28
biome.json
Normal file
28
biome.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
|
||||
"organizeImports": {
|
||||
"enabled": true
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "space",
|
||||
"indentWidth": 2,
|
||||
"lineWidth": 100
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double",
|
||||
"semicolons": "always",
|
||||
"trailingCommas": "all"
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"ignore": ["node_modules", "dist", ".next", "*.min.js"]
|
||||
}
|
||||
}
|
||||
54
docker/docker-compose.yml
Normal file
54
docker/docker-compose.yml
Normal file
@@ -0,0 +1,54 @@
|
||||
services:
|
||||
sase-postgres:
|
||||
image: postgres:17-alpine
|
||||
container_name: sase-postgres
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
ports:
|
||||
- "127.0.0.1:5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U sase"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
sase-redis:
|
||||
image: redis:7.4-alpine
|
||||
container_name: sase-redis
|
||||
restart: unless-stopped
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD}
|
||||
ports:
|
||||
- "127.0.0.1:6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
sase-minio:
|
||||
image: minio/minio:latest
|
||||
container_name: sase-minio
|
||||
restart: unless-stopped
|
||||
command: server /data --console-address ":9001"
|
||||
environment:
|
||||
- MINIO_ROOT_USER=${MINIO_ROOT_USER}
|
||||
- MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
|
||||
ports:
|
||||
- "127.0.0.1:9000:9000"
|
||||
- "127.0.0.1:9001:9001"
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "mc", "ready", "local"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
minio_data:
|
||||
36
docker/minio/init-bucket.sh
Executable file
36
docker/minio/init-bucket.sh
Executable file
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Load env vars
|
||||
source "$(dirname "$0")/../.env"
|
||||
|
||||
MINIO_ENDPOINT="http://127.0.0.1:9000"
|
||||
BUCKET_NAME="sase-schemas"
|
||||
ALIAS_NAME="sase-minio"
|
||||
|
||||
# Install mc (MinIO Client) if not available
|
||||
if ! command -v mc &> /dev/null; then
|
||||
echo "Installing MinIO Client..."
|
||||
curl -sSL https://dl.min.io/client/mc/release/linux-amd64/mc -o /tmp/mc
|
||||
chmod +x /tmp/mc
|
||||
MC="/tmp/mc"
|
||||
else
|
||||
MC="mc"
|
||||
fi
|
||||
|
||||
# Configure alias
|
||||
$MC alias set "$ALIAS_NAME" "$MINIO_ENDPOINT" "$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD"
|
||||
|
||||
# Create bucket if it doesn't exist
|
||||
if $MC ls "$ALIAS_NAME/$BUCKET_NAME" &> /dev/null; then
|
||||
echo "Bucket '$BUCKET_NAME' already exists."
|
||||
else
|
||||
$MC mb "$ALIAS_NAME/$BUCKET_NAME"
|
||||
echo "Bucket '$BUCKET_NAME' created."
|
||||
fi
|
||||
|
||||
# Set public read (download) policy
|
||||
$MC anonymous set download "$ALIAS_NAME/$BUCKET_NAME"
|
||||
echo "Public read access set on '$BUCKET_NAME'."
|
||||
|
||||
echo "MinIO bucket initialization complete."
|
||||
59
docker/nginx/sites/api.sase.tr.conf
Normal file
59
docker/nginx/sites/api.sase.tr.conf
Normal file
@@ -0,0 +1,59 @@
|
||||
server {
|
||||
server_name api.sase.tr;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# Block sensitive paths
|
||||
location ~ /\.(git|env) {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
location ~ ^/(node_modules)/ {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:4000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# 60s timeout for VIN decode operations
|
||||
proxy_connect_timeout 60s;
|
||||
proxy_send_timeout 60s;
|
||||
proxy_read_timeout 60s;
|
||||
}
|
||||
|
||||
listen 443 ssl; # managed by Certbot
|
||||
ssl_certificate /etc/letsencrypt/live/v2.sase.tr/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/v2.sase.tr/privkey.pem; # managed by Certbot
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
}
|
||||
server {
|
||||
if ($host = api.sase.tr) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
|
||||
listen 80;
|
||||
server_name api.sase.tr;
|
||||
return 404; # managed by Certbot
|
||||
|
||||
|
||||
}
|
||||
59
docker/nginx/sites/storage.sase.tr.conf
Normal file
59
docker/nginx/sites/storage.sase.tr.conf
Normal file
@@ -0,0 +1,59 @@
|
||||
server {
|
||||
server_name storage.sase.tr;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
|
||||
# Only allow /sase-schemas/ path, 404 everything else
|
||||
location /sase-schemas/ {
|
||||
proxy_pass http://127.0.0.1:9000/sase-schemas/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Cache-Control: 1 year, immutable
|
||||
add_header Cache-Control "public, max-age=31536000, immutable" always;
|
||||
|
||||
# CORS
|
||||
add_header Access-Control-Allow-Origin "https://v2.sase.tr" always;
|
||||
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
|
||||
add_header Access-Control-Allow-Headers "Range, Content-Type" always;
|
||||
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header Access-Control-Allow-Origin "https://v2.sase.tr";
|
||||
add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Range, Content-Type";
|
||||
add_header Access-Control-Max-Age 86400;
|
||||
add_header Content-Length 0;
|
||||
add_header Content-Type text/plain;
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
|
||||
location / {
|
||||
return 404;
|
||||
}
|
||||
|
||||
listen 443 ssl; # managed by Certbot
|
||||
ssl_certificate /etc/letsencrypt/live/v2.sase.tr/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/v2.sase.tr/privkey.pem; # managed by Certbot
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
}
|
||||
server {
|
||||
if ($host = storage.sase.tr) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
|
||||
listen 80;
|
||||
server_name storage.sase.tr;
|
||||
return 404; # managed by Certbot
|
||||
|
||||
|
||||
}
|
||||
57
docker/nginx/sites/v2.sase.tr.conf
Normal file
57
docker/nginx/sites/v2.sase.tr.conf
Normal file
@@ -0,0 +1,57 @@
|
||||
server {
|
||||
server_name v2.sase.tr;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
|
||||
# Block sensitive paths
|
||||
location ~ /\.(git|env) {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
location ~ ^/(node_modules)/ {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
|
||||
# Gzip compression
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
}
|
||||
|
||||
listen 443 ssl; # managed by Certbot
|
||||
ssl_certificate /etc/letsencrypt/live/v2.sase.tr/fullchain.pem; # managed by Certbot
|
||||
ssl_certificate_key /etc/letsencrypt/live/v2.sase.tr/privkey.pem; # managed by Certbot
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||
|
||||
}
|
||||
server {
|
||||
if ($host = v2.sase.tr) {
|
||||
return 301 https://$host$request_uri;
|
||||
} # managed by Certbot
|
||||
|
||||
|
||||
listen 80;
|
||||
server_name v2.sase.tr;
|
||||
return 404; # managed by Certbot
|
||||
|
||||
|
||||
}
|
||||
52
ecosystem.config.js
Normal file
52
ecosystem.config.js
Normal file
@@ -0,0 +1,52 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: "sase-api",
|
||||
cwd: "./apps/api",
|
||||
script: "dist/main.js",
|
||||
instances: 2,
|
||||
exec_mode: "cluster",
|
||||
env: {
|
||||
NODE_ENV: "production",
|
||||
PORT: 4000,
|
||||
},
|
||||
max_memory_restart: "512M",
|
||||
error_file: "../../logs/api-error.log",
|
||||
out_file: "../../logs/api-out.log",
|
||||
merge_logs: true,
|
||||
log_date_format: "YYYY-MM-DD HH:mm:ss Z",
|
||||
},
|
||||
{
|
||||
name: "sase-web",
|
||||
cwd: "./apps/web",
|
||||
script: "node_modules/.bin/next",
|
||||
args: "start",
|
||||
instances: 2,
|
||||
exec_mode: "cluster",
|
||||
env: {
|
||||
NODE_ENV: "production",
|
||||
PORT: 3000,
|
||||
},
|
||||
max_memory_restart: "512M",
|
||||
error_file: "../../logs/web-error.log",
|
||||
out_file: "../../logs/web-out.log",
|
||||
merge_logs: true,
|
||||
log_date_format: "YYYY-MM-DD HH:mm:ss Z",
|
||||
},
|
||||
{
|
||||
name: "sase-worker",
|
||||
cwd: "./apps/api",
|
||||
script: "dist/worker.js",
|
||||
instances: 1,
|
||||
exec_mode: "fork",
|
||||
env: {
|
||||
NODE_ENV: "production",
|
||||
},
|
||||
max_memory_restart: "256M",
|
||||
error_file: "../../logs/worker-error.log",
|
||||
out_file: "../../logs/worker-out.log",
|
||||
merge_logs: true,
|
||||
log_date_format: "YYYY-MM-DD HH:mm:ss Z",
|
||||
},
|
||||
],
|
||||
};
|
||||
16
package.json
Normal file
16
package.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "sase-v2",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.29.3",
|
||||
"scripts": {
|
||||
"dev": "turbo run dev",
|
||||
"build": "turbo run build",
|
||||
"lint": "turbo run lint",
|
||||
"test": "turbo run test",
|
||||
"clean": "turbo run clean"
|
||||
},
|
||||
"devDependencies": {
|
||||
"turbo": "^2"
|
||||
}
|
||||
}
|
||||
3
pnpm-workspace.yaml
Normal file
3
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
packages:
|
||||
- "apps/*"
|
||||
- "packages/*"
|
||||
22
tsconfig.json
Normal file
22
tsconfig.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16",
|
||||
"lib": ["ES2022"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@sase/*": ["packages/*/src"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist", ".next"]
|
||||
}
|
||||
22
turbo.json
Normal file
22
turbo.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"tasks": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**", ".next/**"]
|
||||
},
|
||||
"dev": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
},
|
||||
"lint": {
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": ["^build"]
|
||||
},
|
||||
"clean": {
|
||||
"cache": false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user