Skip to content

Commit e571018

Browse files
authored
Merge pull request #90 from Ecohackerfarm/release/v0.10.0
release: v0.10.0
2 parents c5dfbcb + 9bc149e commit e571018

30 files changed

Lines changed: 1635 additions & 892 deletions

File tree

db/seed.sql

Lines changed: 973 additions & 844 deletions
Large diffs are not rendered by default.

docker-compose.yml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ services:
2424
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
2525
BETTER_AUTH_URL: ${BETTER_AUTH_URL}
2626
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
27-
ports:
28-
- "3000:3000"
2927
environment:
3028
DATABASE_URL: ${DATABASE_URL:-postgresql://power2plant:power2plant@db:5432/power2plant}
3129
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
@@ -43,4 +41,16 @@ services:
4341
networks:
4442
- default
4543

44+
nginx:
45+
image: nginx:1.27-alpine
46+
ports:
47+
- "80:80"
48+
volumes:
49+
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
50+
depends_on:
51+
app:
52+
condition: service_healthy
53+
networks:
54+
- default
55+
4656
volumes: {}

docs/server-setup.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,8 @@ chmod 600 $PROJECT_PATH/prod/.env
184184

185185
`scripts/server/setup.sh` generates and installs:
186186
- `/etc/systemd/system/${PROJECT}-{prod,dev,deploy.path,deploy.service}`
187-
- `/etc/nginx/sites-available/${PROJECT}` (symlinked to enabled)
187+
- `/etc/nginx/conf.d/${PROJECT}-rate-limits.conf``limit_req_zone` for `/share/*` (60 req/min per IP)
188+
- `/etc/nginx/sites-available/${PROJECT}` (symlinked to enabled) — includes rate-limited `/share/` location
188189
- `$PROJECT_PATH/prod/webhook/hooks.json` (from template, secret substituted)
189190

190191
```sh
@@ -224,8 +225,9 @@ systemctl status ${PROJECT}-prod
224225
docker compose --project-directory $PROJECT_PATH/prod logs -f app
225226
```
226227

227-
> Prod app binds as `:::3000:3000` — nginx proxies to `[::1]:3000`. If the app
228-
> isn't reachable via nginx, verify the port binding in `docker ps`.
228+
> Prod app binds `:::3000` — host nginx proxies to `[::1]:3000`. `/share/*` traffic
229+
> is rate-limited to 60 req/min per IP (burst 10) at the nginx layer.
230+
> If the app isn't reachable via nginx, verify the port binding in `docker ps`.
229231
230232
---
231233

nginx/nginx.conf

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Rate-limit zone for public share pages: 60 req/min per IP (= 1 r/s)
2+
# 10m shared memory holds ~160k IPs
3+
limit_req_zone $binary_remote_addr zone=share_rl:10m rate=1r/s;
4+
5+
server {
6+
listen 80;
7+
server_name _;
8+
9+
# Increase header buffer for Next.js responses
10+
proxy_buffer_size 16k;
11+
proxy_buffers 4 16k;
12+
proxy_busy_buffers_size 16k;
13+
14+
# Forward real client IP to the app
15+
proxy_set_header Host $host;
16+
proxy_set_header X-Real-IP $remote_addr;
17+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
18+
proxy_set_header X-Forwarded-Proto $scheme;
19+
20+
# Public share pages — rate limited
21+
location /share/ {
22+
limit_req zone=share_rl burst=10 nodelay;
23+
limit_req_status 429;
24+
25+
proxy_pass http://app:3000;
26+
}
27+
28+
# Everything else — no rate limit
29+
location / {
30+
proxy_pass http://app:3000;
31+
}
32+
}

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"react-dom": "^19.0.0",
4848
"react-leaflet": "^5.0.0",
4949
"shadcn": "^4.6.0",
50+
"sonner": "^2.0.7",
5051
"tailwind-merge": "^3.5.0",
5152
"tw-animate-css": "^1.4.0"
5253
},

pnpm-lock.yaml

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "user" ADD COLUMN "trustScore" DOUBLE PRECISION NOT NULL DEFAULT 1.0;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
-- CreateTable
2+
CREATE TABLE "GardenShare" (
3+
"token" TEXT NOT NULL,
4+
"userId" TEXT NOT NULL,
5+
"beds" JSONB NOT NULL,
6+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
7+
"expiresAt" TIMESTAMP(3) NOT NULL,
8+
9+
CONSTRAINT "GardenShare_pkey" PRIMARY KEY ("token")
10+
);
11+
12+
-- CreateIndex
13+
CREATE INDEX "GardenShare_userId_idx" ON "GardenShare"("userId");
14+
15+
-- AddForeignKey
16+
ALTER TABLE "GardenShare" ADD CONSTRAINT "GardenShare_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE;

prisma/schema.prisma

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,16 +167,29 @@ enum SourceClassification {
167167
}
168168

169169
model user {
170-
id String @id @default(cuid())
170+
id String @id @default(cuid())
171171
name String
172-
email String @unique
173-
emailVerified Boolean @default(false)
172+
email String @unique
173+
emailVerified Boolean @default(false)
174174
image String?
175-
createdAt DateTime @default(now())
176-
updatedAt DateTime @updatedAt
175+
trustScore Float @default(1.0)
176+
createdAt DateTime @default(now())
177+
updatedAt DateTime @updatedAt
177178
sessions session[]
178179
accounts account[]
179180
garden UserGarden?
181+
gardenShares GardenShare[]
182+
}
183+
184+
model GardenShare {
185+
token String @id
186+
userId String
187+
user user @relation(fields: [userId], references: [id], onDelete: Cascade)
188+
beds Json
189+
createdAt DateTime @default(now())
190+
expiresAt DateTime
191+
192+
@@index([userId])
180193
}
181194

182195
model session {

scripts/server/setup.sh

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,16 @@ echo " wrote ${PROJECT}-deploy.path and ${PROJECT}-deploy.service"
110110
touch /run/p2p-deploy.trigger
111111
chmod 777 /run/p2p-deploy.trigger # world-writable sentinel (no sensitive content)
112112

113-
# ── Nginx config ──────────────────────────────────────────────────────────────
113+
# ── Nginx rate-limit zone ─────────────────────────────────────────────────────
114+
# limit_req_zone must live in the http{} context → /etc/nginx/conf.d/
115+
cat > "/etc/nginx/conf.d/${PROJECT}-rate-limits.conf" <<'RLCONF'
116+
# 60 req/min per IP on /share/* (token-guessing mitigation)
117+
# 10m shared memory ≈ 160k tracked IPs
118+
limit_req_zone $binary_remote_addr zone=p2p_share_rl:10m rate=1r/s;
119+
RLCONF
120+
echo " wrote /etc/nginx/conf.d/${PROJECT}-rate-limits.conf"
121+
122+
# ── Nginx site config ─────────────────────────────────────────────────────────
114123
# Single-quoted NGINX heredoc prevents shell expansion — nginx vars ($host etc.) preserved.
115124
# sed substitutes only $DOMAIN.
116125
nginx_template=$(cat <<'NGINX'
@@ -126,6 +135,19 @@ server {
126135
proxy_read_timeout 10s;
127136
}
128137
138+
# Rate-limited: 60 req/min per IP, burst of 10
139+
location /share/ {
140+
limit_req zone=p2p_share_rl burst=10 nodelay;
141+
limit_req_status 429;
142+
143+
proxy_pass http://[::1]:3000;
144+
proxy_set_header Host $host;
145+
proxy_set_header X-Real-IP $remote_addr;
146+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
147+
proxy_set_header X-Forwarded-Proto $scheme;
148+
proxy_read_timeout 60s;
149+
}
150+
129151
location / {
130152
proxy_pass http://[::1]:3000;
131153
proxy_set_header Host $host;

0 commit comments

Comments
 (0)