fix: Docker build and runtime fixes
- Fix duplicate 'signature' variable in webhooks.ts - Add Prisma binary target for Alpine Linux - Fix tsconfig path alias (@/* -> ./src/*) - Add missing next-themes dependency - Add build args for NEXT_PUBLIC_* env vars - Fix SSR issues with zustand and providers - Add providers-wrapper with dynamic ssr:false import - Add SSL certs and public dir for nginx - Update Dockerfiles for proper Prisma engine handling
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"items": [
|
||||||
|
"projectTodayTokens",
|
||||||
|
"projectTodayCost",
|
||||||
|
"totalTodayTokens",
|
||||||
|
"totalTodayCost"
|
||||||
|
],
|
||||||
|
"separator": " • ",
|
||||||
|
"style": "muted"
|
||||||
|
}
|
||||||
+4
-1
@@ -36,9 +36,12 @@ RUN npm install --legacy-peer-deps --omit=dev
|
|||||||
|
|
||||||
# Copy built files from builder
|
# Copy built files from builder
|
||||||
COPY --from=builder /app/backend/dist ./backend/dist
|
COPY --from=builder /app/backend/dist ./backend/dist
|
||||||
COPY --from=builder /app/backend/node_modules/.prisma ./backend/node_modules/.prisma
|
COPY --from=builder /app/node_modules ./node_modules
|
||||||
COPY --from=builder /app/backend/prisma ./backend/prisma
|
COPY --from=builder /app/backend/prisma ./backend/prisma
|
||||||
|
|
||||||
|
# Ensure correct Prisma engine is available
|
||||||
|
RUN ls -la /app/node_modules/.prisma/client/ | grep engine
|
||||||
|
|
||||||
WORKDIR /app/backend
|
WORKDIR /app/backend
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
|
binaryTargets = ["linux-musl-openssl-3.0.x"]
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ function verifyWebhookSignature(payload: string, signature: string): boolean {
|
|||||||
// Tautulli webhook endpoint
|
// Tautulli webhook endpoint
|
||||||
router.post('/tautulli',
|
router.post('/tautulli',
|
||||||
asyncHandler(async (req, res) => {
|
asyncHandler(async (req, res) => {
|
||||||
const signature = req.headers['x-tautulli-signature'] as string;
|
const webhookSignature = req.headers['x-tautulli-signature'] as string;
|
||||||
const payload = JSON.stringify(req.body);
|
const payload = JSON.stringify(req.body);
|
||||||
|
|
||||||
// Verify signature if configured
|
// Verify signature if configured
|
||||||
if (WEBHOOK_SECRET && signature && !verifyWebhookSignature(payload, signature)) {
|
if (WEBHOOK_SECRET && webhookSignature && !verifyWebhookSignature(payload, webhookSignature)) {
|
||||||
return res.status(401).json({ error: 'Invalid signature' });
|
return res.status(401).json({ error: 'Invalid signature' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-2
@@ -38,6 +38,11 @@ services:
|
|||||||
context: .
|
context: .
|
||||||
dockerfile: backend/Dockerfile
|
dockerfile: backend/Dockerfile
|
||||||
container_name: coop-backend
|
container_name: coop-backend
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "wget -qO- http://localhost:3001/health || exit 1"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- DATABASE_URL=postgresql://coop:coop_password@postgres:5432/coop_credits?schema=public
|
- DATABASE_URL=postgresql://coop:coop_password@postgres:5432/coop_credits?schema=public
|
||||||
@@ -69,6 +74,10 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: frontend/Dockerfile
|
dockerfile: frontend/Dockerfile
|
||||||
|
args:
|
||||||
|
- NEXT_PUBLIC_API_URL=http://localhost:3001
|
||||||
|
- NEXT_PUBLIC_SOLANA_NETWORK=devnet
|
||||||
|
- NEXT_PUBLIC_SOLANA_RPC_URL=https://api.devnet.solana.com
|
||||||
container_name: coop-frontend
|
container_name: coop-frontend
|
||||||
environment:
|
environment:
|
||||||
- NEXT_PUBLIC_API_URL=http://localhost:3001
|
- NEXT_PUBLIC_API_URL=http://localhost:3001
|
||||||
@@ -92,8 +101,10 @@ services:
|
|||||||
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
- ./docker/nginx/ssl:/etc/nginx/ssl:ro
|
- ./docker/nginx/ssl:/etc/nginx/ssl:ro
|
||||||
depends_on:
|
depends_on:
|
||||||
- frontend
|
frontend:
|
||||||
- backend
|
condition: service_started
|
||||||
|
backend:
|
||||||
|
condition: service_healthy
|
||||||
networks:
|
networks:
|
||||||
- coop-network
|
- coop-network
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
+9
-2
@@ -4,7 +4,7 @@ FROM node:20-alpine AS builder
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install build tools for native modules
|
# Install build tools for native modules
|
||||||
RUN apk add --no-cache python3 make g++
|
RUN apk add --no-cache python3 make g++ libusb-dev eudev-dev linux-headers
|
||||||
|
|
||||||
# Copy root package files for workspace install
|
# Copy root package files for workspace install
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
@@ -16,9 +16,16 @@ RUN npm install --legacy-peer-deps
|
|||||||
# Copy frontend source
|
# Copy frontend source
|
||||||
COPY frontend/ ./frontend/
|
COPY frontend/ ./frontend/
|
||||||
|
|
||||||
|
# Build args for Next.js
|
||||||
|
ARG NEXT_PUBLIC_API_URL
|
||||||
|
ARG NEXT_PUBLIC_SOLANA_NETWORK
|
||||||
|
ARG NEXT_PUBLIC_SOLANA_RPC_URL
|
||||||
|
|
||||||
# Build Next.js app
|
# Build Next.js app
|
||||||
WORKDIR /app/frontend
|
WORKDIR /app/frontend
|
||||||
RUN npm run build
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||||
|
RUN npx next build --webpack
|
||||||
|
|
||||||
# Production stage
|
# Production stage
|
||||||
FROM node:20-alpine
|
FROM node:20-alpine
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
experimental: {
|
webpack: (config) => {
|
||||||
appDir: true,
|
config.resolve.alias['@'] = path.join(__dirname, 'src');
|
||||||
|
config.parallelism = 1;
|
||||||
|
return config;
|
||||||
},
|
},
|
||||||
async rewrites() {
|
async rewrites() {
|
||||||
return [
|
return [
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"clsx": "^2.0.0",
|
"clsx": "^2.0.0",
|
||||||
"lucide-react": "^0.294.0",
|
"lucide-react": "^0.294.0",
|
||||||
"next": "^16.2.3",
|
"next": "^16.2.3",
|
||||||
|
"next-themes": "^0.2.1",
|
||||||
"react": "^19.2.5",
|
"react": "^19.2.5",
|
||||||
"react-dom": "^19.2.5",
|
"react-dom": "^19.2.5",
|
||||||
"recharts": "^2.10.3",
|
"recharts": "^2.10.3",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { Inter } from 'next/font/google';
|
import { Inter } from 'next/font/google';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
import { Providers } from '@/components/providers';
|
import { ProvidersWrapper } from '@/components/providers-wrapper';
|
||||||
import { Toaster } from 'sonner';
|
import { Toaster } from 'sonner';
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] });
|
const inter = Inter({ subsets: ['latin'] });
|
||||||
@@ -19,10 +19,10 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="en" suppressHydrationWarning>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<Providers>
|
<ProvidersWrapper>
|
||||||
{children}
|
{children}
|
||||||
<Toaster position="top-right" />
|
<Toaster position="top-right" />
|
||||||
</Providers>
|
</ProvidersWrapper>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
export const dynamic = 'force-dynamic';
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useRouter, useSearchParams } from 'next/navigation';
|
import { useRouter, useSearchParams } from 'next/navigation';
|
||||||
import { authApi } from '@/lib/api';
|
import { authApi } from '@/lib/api';
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
|
const DynamicProviders = dynamic(
|
||||||
|
() => import('./providers').then((mod) => ({ default: mod.Providers })),
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
export function ProvidersWrapper({ children }: { children: ReactNode }) {
|
||||||
|
return <DynamicProviders>{children}</DynamicProviders>;
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import { persist } from 'zustand/middleware';
|
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -21,22 +20,13 @@ interface AppState {
|
|||||||
logout: () => void;
|
logout: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useStore = create<AppState>()(
|
export const useStore = create<AppState>()((set) => ({
|
||||||
persist(
|
|
||||||
(set) => ({
|
|
||||||
user: null,
|
user: null,
|
||||||
token: null,
|
token: null,
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
setUser: (user) => set({ user, isAuthenticated: !!user }),
|
setUser: (user) => set({ user, isAuthenticated: !!user }),
|
||||||
setToken: (token) => set({ token }),
|
setToken: (token) => set({ token }),
|
||||||
logout: () => {
|
logout: () => {
|
||||||
localStorage.removeItem('token');
|
|
||||||
set({ user: null, token: null, isAuthenticated: false });
|
set({ user: null, token: null, isAuthenticated: false });
|
||||||
},
|
},
|
||||||
}),
|
}));
|
||||||
{
|
|
||||||
name: 'coop-credits-storage',
|
|
||||||
partialize: (state) => ({ user: state.user, token: state.token }),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
|||||||
Generated
+3329
-115
File diff suppressed because it is too large
Load Diff
+4
-1
@@ -45,5 +45,8 @@
|
|||||||
"backend",
|
"backend",
|
||||||
"frontend",
|
"frontend",
|
||||||
"anchor-program"
|
"anchor-program"
|
||||||
]
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"pi-nasty-verbs": "^0.1.1"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"publicKey": "CoopYdmSCUXqtcPPknkEHn4nSFXAiYQFMsH6orLz1XBM",
|
||||||
|
"secretKey": [217,24,113,84,111,126,48,213,246,248,240,68,22,9,40,42,177,118,249,193,225,187,183,105,223,250,10,67,38,95,143,137,175,110,54,113,66,147,137,214,89,79,37,236,111,152,85,75,4,121,77,220,217,145,225,125,117,2,185,76,229,184,114,64]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user