Production deploy: stream API field mapping, Caddy domain config, systemd service, Docker bridge binding

This commit is contained in:
BizzleBot 2026-02-19 17:36:51 +00:00
parent c6b8fb752a
commit 8f4e5dea40
11 changed files with 46 additions and 22 deletions

View File

@ -43,6 +43,15 @@ export default async function DonatePage() {
</p> </p>
</header> </header>
{/* Coming soon notice */}
<div className="bg-amber-500/10 border border-amber-500/30 rounded-2xl p-6 flex items-center gap-4">
<span className="text-2xl">🚧</span>
<div>
<h3 className="font-bold text-amber-400 text-lg">Online Donations Coming Soon</h3>
<p className="text-stone-400 text-sm">We&apos;re setting up secure payment processing. In the meantime, please visit <a href="https://ccfriendsofwildlife.org" className="text-teal underline" target="_blank" rel="noopener">ccfriendsofwildlife.org</a> to donate directly.</p>
</div>
</div>
{/* Impact banner */} {/* Impact banner */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-px bg-white/5 rounded-2xl overflow-hidden"> <div className="grid grid-cols-1 md:grid-cols-3 gap-px bg-white/5 rounded-2xl overflow-hidden">
{[ {[

View File

@ -0,0 +1,5 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default prisma;

View File

@ -1,12 +1,11 @@
import { Router, Request, Response, NextFunction } from 'express'; import { Router, Request, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator'; import { body, validationResult } from 'express-validator';
import { PrismaClient } from '@prisma/client'; import prisma from '../lib/prisma';
import { hashPassword, verifyPassword } from '../utils/password'; import { hashPassword, verifyPassword } from '../utils/password';
import { signToken } from '../utils/jwt'; import { signToken } from '../utils/jwt';
import { authenticate, AuthRequest } from '../middleware/auth'; import { authenticate, AuthRequest } from '../middleware/auth';
const router = Router(); const router = Router();
const prisma = new PrismaClient();
const validateRegister = [ const validateRegister = [
body('email').isEmail().normalizeEmail(), body('email').isEmail().normalizeEmail(),

View File

@ -1,10 +1,9 @@
import { Router, Response, NextFunction } from 'express'; import { Router, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator'; import { body, validationResult } from 'express-validator';
import { PrismaClient } from '@prisma/client'; import prisma from '../lib/prisma';
import { authenticate, requireRole, optionalAuth, AuthRequest } from '../middleware/auth'; import { authenticate, requireRole, optionalAuth, AuthRequest } from '../middleware/auth';
const router = Router(); const router = Router();
const prisma = new PrismaClient();
// GET /api/burrows — public // GET /api/burrows — public
router.get('/', optionalAuth, async (_req: AuthRequest, res: Response, next: NextFunction) => { router.get('/', optionalAuth, async (_req: AuthRequest, res: Response, next: NextFunction) => {

View File

@ -1,10 +1,9 @@
import { Router, Response, NextFunction } from 'express'; import { Router, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator'; import { body, validationResult } from 'express-validator';
import { PrismaClient } from '@prisma/client'; import prisma from '../lib/prisma';
import { optionalAuth, authenticate, requireRole, AuthRequest } from '../middleware/auth'; import { optionalAuth, authenticate, requireRole, AuthRequest } from '../middleware/auth';
const router = Router(); const router = Router();
const prisma = new PrismaClient();
// POST /api/donations — public (guest) or authenticated // POST /api/donations — public (guest) or authenticated
// Stripe integration stub: stores intent, returns client_secret placeholder // Stripe integration stub: stores intent, returns client_secret placeholder

View File

@ -1,10 +1,9 @@
import { Router, Response, NextFunction } from 'express'; import { Router, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator'; import { body, validationResult } from 'express-validator';
import { PrismaClient } from '@prisma/client'; import prisma from '../lib/prisma';
import { authenticate, requireRole, optionalAuth, AuthRequest } from '../middleware/auth'; import { authenticate, requireRole, optionalAuth, AuthRequest } from '../middleware/auth';
const router = Router(); const router = Router();
const prisma = new PrismaClient();
// GET /api/events — public // GET /api/events — public
router.get('/', optionalAuth, async (_req: AuthRequest, res: Response, next: NextFunction) => { router.get('/', optionalAuth, async (_req: AuthRequest, res: Response, next: NextFunction) => {

View File

@ -1,10 +1,9 @@
import { Router, Response, NextFunction } from 'express'; import { Router, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator'; import { body, validationResult } from 'express-validator';
import { PrismaClient } from '@prisma/client'; import prisma from '../lib/prisma';
import { authenticate, requireRole, optionalAuth, AuthRequest } from '../middleware/auth'; import { authenticate, requireRole, optionalAuth, AuthRequest } from '../middleware/auth';
const router = Router(); const router = Router();
const prisma = new PrismaClient();
// GET /api/sightings — public // GET /api/sightings — public
router.get('/', optionalAuth, async (_req: AuthRequest, res: Response, next: NextFunction) => { router.get('/', optionalAuth, async (_req: AuthRequest, res: Response, next: NextFunction) => {

View File

@ -1,9 +1,8 @@
import { Router, Response, NextFunction } from 'express'; import { Router, Response, NextFunction } from 'express';
import { PrismaClient } from '@prisma/client'; import prisma from '../lib/prisma';
import { AuthRequest } from '../middleware/auth'; import { AuthRequest } from '../middleware/auth';
const router = Router(); const router = Router();
const prisma = new PrismaClient();
// GET /api/stats — public dashboard stats // GET /api/stats — public dashboard stats
router.get('/', async (_req: AuthRequest, res: Response, next: NextFunction) => { router.get('/', async (_req: AuthRequest, res: Response, next: NextFunction) => {

View File

@ -1,16 +1,28 @@
import { Router, Response, NextFunction } from 'express'; import { Router, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator'; import { body, validationResult } from 'express-validator';
import { PrismaClient } from '@prisma/client'; import prisma from '../lib/prisma';
import { authenticate, requireRole, AuthRequest } from '../middleware/auth'; import { authenticate, requireRole, AuthRequest } from '../middleware/auth';
const router = Router(); const router = Router();
const prisma = new PrismaClient();
// GET /api/streams — public // GET /api/streams — public
function mapStream(s: any) {
return {
id: s.id,
name: s.name,
location: s.camera_location,
status: s.status,
viewerCount: 0,
hlsUrl: s.stream_url,
thumbnailUrl: s.thumbnail_url,
createdAt: s.created_at,
};
}
router.get('/', async (_req: AuthRequest, res: Response, next: NextFunction) => { router.get('/', async (_req: AuthRequest, res: Response, next: NextFunction) => {
try { try {
const streams = await prisma.livestreamSource.findMany({ orderBy: { status: 'asc' } }); const streams = await prisma.livestreamSource.findMany({ orderBy: { status: 'asc' } });
res.json(streams); res.json(streams.map(mapStream));
} catch (err) { next(err); } } catch (err) { next(err); }
}); });
@ -19,7 +31,7 @@ router.get('/:id', async (req: AuthRequest, res: Response, next: NextFunction) =
try { try {
const stream = await prisma.livestreamSource.findUnique({ where: { id: req.params.id } }); const stream = await prisma.livestreamSource.findUnique({ where: { id: req.params.id } });
if (!stream) { res.status(404).json({ error: 'Stream not found' }); return; } if (!stream) { res.status(404).json({ error: 'Stream not found' }); return; }
res.json(stream); res.json(mapStream(stream));
} catch (err) { next(err); } } catch (err) { next(err); }
}); });

View File

@ -1,10 +1,9 @@
import { Router, Response, NextFunction } from 'express'; import { Router, Response, NextFunction } from 'express';
import { body, validationResult } from 'express-validator'; import { body, validationResult } from 'express-validator';
import { PrismaClient } from '@prisma/client'; import prisma from '../lib/prisma';
import { authenticate, requireRole, AuthRequest } from '../middleware/auth'; import { authenticate, requireRole, AuthRequest } from '../middleware/auth';
const router = Router(); const router = Router();
const prisma = new PrismaClient();
// ─── VOLUNTEER HOURS ───────────────────────────────────────────────────────── // ─── VOLUNTEER HOURS ─────────────────────────────────────────────────────────

View File

@ -8,7 +8,7 @@ services:
environment: environment:
POSTGRES_DB: owl_stream POSTGRES_DB: owl_stream
POSTGRES_USER: owl_user POSTGRES_USER: owl_user
POSTGRES_PASSWORD: ${DB_PASSWORD:-owl_secure_password} POSTGRES_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD must be set in .env}
volumes: volumes:
- owl_pg_data:/var/lib/postgresql/data - owl_pg_data:/var/lib/postgresql/data
networks: networks:
@ -23,6 +23,10 @@ services:
options: options:
max-size: "10m" max-size: "10m"
max-file: "3" max-file: "3"
deploy:
resources:
limits:
memory: 256M
owl-backend: owl-backend:
build: build:
@ -34,11 +38,11 @@ services:
owl-db: owl-db:
condition: service_healthy condition: service_healthy
environment: environment:
DATABASE_URL: postgresql://owl_user:${DB_PASSWORD:-owl_secure_password}@owl-db:5432/owl_stream DATABASE_URL: postgresql://owl_user:${DB_PASSWORD:?DB_PASSWORD required}@owl-db:5432/owl_stream
JWT_SECRET: ${JWT_SECRET:-owl_jwt_secret_change_me} JWT_SECRET: ${JWT_SECRET:?JWT_SECRET required}
PORT: 3020 PORT: 3020
HOST: 0.0.0.0 HOST: 0.0.0.0
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-http://localhost:8089} ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-https://owls.bizzle.cloud,http://localhost:8089}
ports: ports:
- "127.0.0.1:3020:3020" - "127.0.0.1:3020:3020"
networks: networks:
@ -71,6 +75,7 @@ services:
- NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://127.0.0.1:3020} - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://127.0.0.1:3020}
ports: ports:
- "127.0.0.1:8110:8089" - "127.0.0.1:8110:8089"
- "172.18.0.1:8110:8089"
networks: networks:
- owl-network - owl-network
healthcheck: healthcheck: