WhatsApp/Signal/Formstack/admin updates
This commit is contained in:
parent
bcecf61a46
commit
d0cc5a21de
451 changed files with 16139 additions and 39623 deletions
|
|
@ -2,22 +2,28 @@ FROM node:22-bookworm-slim AS base
|
|||
|
||||
FROM base AS builder
|
||||
ARG APP_DIR=/opt/bridge-frontend
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN mkdir -p ${APP_DIR}/
|
||||
RUN npm i -g turbo
|
||||
RUN corepack enable && corepack prepare pnpm@9.15.4 --activate
|
||||
RUN pnpm add -g turbo
|
||||
WORKDIR ${APP_DIR}
|
||||
COPY . .
|
||||
RUN turbo prune --scope=@link-stack/bridge-frontend --scope=@link-stack/bridge-migrations --docker
|
||||
|
||||
FROM base AS installer
|
||||
ARG APP_DIR=/opt/bridge-frontend
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
WORKDIR ${APP_DIR}
|
||||
RUN corepack enable && corepack prepare pnpm@9.15.4 --activate
|
||||
COPY --from=builder ${APP_DIR}/.gitignore .gitignore
|
||||
COPY --from=builder ${APP_DIR}/out/json/ .
|
||||
COPY --from=builder ${APP_DIR}/out/package-lock.json ./package-lock.json
|
||||
RUN npm ci
|
||||
COPY --from=builder ${APP_DIR}/out/pnpm-lock.yaml ./pnpm-lock.yaml
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
COPY --from=builder ${APP_DIR}/out/full/ .
|
||||
RUN npm i -g turbo
|
||||
RUN pnpm add -g turbo
|
||||
RUN turbo run build --filter=@link-stack/bridge-frontend --filter=@link-stack/bridge-migrations
|
||||
|
||||
FROM base AS runner
|
||||
|
|
@ -29,15 +35,15 @@ LABEL maintainer="Darren Clarke <darren@redaranj.com>"
|
|||
LABEL org.label-schema.build-date=$BUILD_DATE
|
||||
LABEL org.label-schema.version=$VERSION
|
||||
ENV APP_DIR ${APP_DIR}
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN corepack enable && corepack prepare pnpm@9.15.4 --activate
|
||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
dumb-init
|
||||
RUN mkdir -p ${APP_DIR}
|
||||
WORKDIR ${APP_DIR}
|
||||
COPY --from=installer ${APP_DIR}/node_modules/ ./node_modules/
|
||||
COPY --from=installer ${APP_DIR}/apps/bridge-frontend/ ./apps/bridge-frontend/
|
||||
COPY --from=installer ${APP_DIR}/apps/bridge-migrations/ ./apps/bridge-migrations/
|
||||
COPY --from=installer ${APP_DIR}/package.json ./package.json
|
||||
COPY --from=installer ${APP_DIR} ./
|
||||
RUN chown -R node:node ${APP_DIR}/
|
||||
WORKDIR ${APP_DIR}/apps/bridge-frontend/
|
||||
RUN chmod +x docker-entrypoint.sh
|
||||
|
|
|
|||
|
|
@ -1,36 +1,133 @@
|
|||
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
||||
# Bridge Frontend
|
||||
|
||||
## Getting Started
|
||||
Frontend application for managing communication bridges between various messaging platforms and the CDR Link system.
|
||||
|
||||
First, run the development server:
|
||||
## Overview
|
||||
|
||||
Bridge Frontend provides a web interface for configuring and managing communication channels including Signal, WhatsApp, Facebook, and Voice integrations. It handles bot registration, webhook configuration, and channel settings.
|
||||
|
||||
## Features
|
||||
|
||||
- **Channel Management**: Configure Signal, WhatsApp, Facebook, and Voice channels
|
||||
- **Bot Registration**: Register and manage bots for each communication platform
|
||||
- **Webhook Configuration**: Set up webhooks for message routing
|
||||
- **Settings Management**: Configure channel-specific settings and behaviors
|
||||
- **User Authentication**: Secure access with NextAuth.js
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Node.js >= 20
|
||||
- npm >= 10
|
||||
- PostgreSQL database
|
||||
- Running bridge-worker service
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Run database migrations
|
||||
npm run migrate:latest
|
||||
|
||||
# Run development server
|
||||
npm run dev
|
||||
# or
|
||||
yarn dev
|
||||
# or
|
||||
pnpm dev
|
||||
# or
|
||||
bun dev
|
||||
|
||||
# Build for production
|
||||
npm run build
|
||||
|
||||
# Start production server
|
||||
npm run start
|
||||
```
|
||||
|
||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
||||
### Environment Variables
|
||||
|
||||
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
|
||||
Required environment variables:
|
||||
|
||||
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
|
||||
- `DATABASE_URL` - PostgreSQL connection string
|
||||
- `DATABASE_HOST` - Database host
|
||||
- `DATABASE_NAME` - Database name
|
||||
- `DATABASE_USER` - Database username
|
||||
- `DATABASE_PASSWORD` - Database password
|
||||
- `NEXTAUTH_URL` - Application URL
|
||||
- `NEXTAUTH_SECRET` - NextAuth.js secret
|
||||
- `GOOGLE_CLIENT_ID` - Google OAuth client ID
|
||||
- `GOOGLE_CLIENT_SECRET` - Google OAuth client secret
|
||||
|
||||
## Learn More
|
||||
### Available Scripts
|
||||
|
||||
To learn more about Next.js, take a look at the following resources:
|
||||
- `npm run dev` - Start development server
|
||||
- `npm run build` - Build for production
|
||||
- `npm run start` - Start production server
|
||||
- `npm run lint` - Run ESLint
|
||||
- `npm run migrate:latest` - Run all pending migrations
|
||||
- `npm run migrate:down` - Rollback last migration
|
||||
- `npm run migrate:up` - Run next migration
|
||||
- `npm run migrate:make` - Create new migration
|
||||
|
||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
||||
## Architecture
|
||||
|
||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
|
||||
### Database Schema
|
||||
|
||||
## Deploy on Vercel
|
||||
The application manages the following main entities:
|
||||
|
||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
||||
- **Bots**: Communication channel bot configurations
|
||||
- **Webhooks**: Webhook endpoints for external integrations
|
||||
- **Settings**: Channel-specific configuration settings
|
||||
- **Users**: User accounts with role-based permissions
|
||||
|
||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
|
||||
### API Routes
|
||||
|
||||
- `/api/auth` - Authentication endpoints
|
||||
- `/api/[service]/bots` - Bot management for each service
|
||||
- `/api/[service]/webhooks` - Webhook configuration
|
||||
|
||||
### Page Structure
|
||||
|
||||
- `/` - Dashboard/home page
|
||||
- `/login` - Authentication page
|
||||
- `/[...segment]` - Dynamic routing for CRUD operations
|
||||
- `@create` - Create new entities
|
||||
- `@detail` - View entity details
|
||||
- `@edit` - Edit existing entities
|
||||
|
||||
## Integration
|
||||
|
||||
### Database Access
|
||||
|
||||
Uses Kysely ORM for type-safe database queries:
|
||||
|
||||
```typescript
|
||||
import { db } from '@link-stack/database'
|
||||
|
||||
const bots = await db
|
||||
.selectFrom('bots')
|
||||
.selectAll()
|
||||
.execute()
|
||||
```
|
||||
|
||||
### Authentication
|
||||
|
||||
Integrated with NextAuth.js using database adapter:
|
||||
|
||||
```typescript
|
||||
import { authOptions } from '@link-stack/auth'
|
||||
```
|
||||
|
||||
## Docker Support
|
||||
|
||||
```bash
|
||||
# Build image
|
||||
docker build -t link-stack/bridge-frontend .
|
||||
|
||||
# Run with docker-compose
|
||||
docker-compose -f docker/compose/bridge.yml up
|
||||
```
|
||||
|
||||
## Related Services
|
||||
|
||||
- **bridge-worker**: Processes messages from configured channels
|
||||
- **bridge-whatsapp**: WhatsApp-specific integration service
|
||||
- **bridge-migrations**: Database schema management
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import { Create } from "@link-stack/bridge-ui";
|
||||
|
||||
type PageProps = {
|
||||
params: { segment: string[] };
|
||||
params: Promise<{ segment: string[] }>;
|
||||
};
|
||||
|
||||
export default function Page({ params: { segment } }: PageProps) {
|
||||
export default async function Page({ params }: PageProps) {
|
||||
const { segment } = await params;
|
||||
const service = segment[0];
|
||||
|
||||
return <Create service={service} />;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { db } from "@link-stack/bridge-common";
|
||||
import { serviceConfig, Detail } from "@link-stack/bridge-ui";
|
||||
|
||||
type Props = {
|
||||
params: { segment: string[] };
|
||||
type PageProps = {
|
||||
params: Promise<{ segment: string[] }>;
|
||||
};
|
||||
|
||||
export default async function Page({ params: { segment } }: Props) {
|
||||
export default async function Page({ params }: PageProps) {
|
||||
const { segment } = await params;
|
||||
const service = segment[0];
|
||||
const id = segment?.[1];
|
||||
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ import { db } from "@link-stack/bridge-common";
|
|||
import { serviceConfig, Edit } from "@link-stack/bridge-ui";
|
||||
|
||||
type PageProps = {
|
||||
params: { segment: string[] };
|
||||
params: Promise<{ segment: string[] }>;
|
||||
};
|
||||
|
||||
export default async function Page({ params: { segment } }: PageProps) {
|
||||
export default async function Page({ params }: PageProps) {
|
||||
const { segment } = await params;
|
||||
const service = segment[0];
|
||||
const id = segment?.[1];
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ import { db } from "@link-stack/bridge-common";
|
|||
import { serviceConfig, List } from "@link-stack/bridge-ui";
|
||||
|
||||
type PageProps = {
|
||||
params: {
|
||||
params: Promise<{
|
||||
segment: string[];
|
||||
};
|
||||
}>;
|
||||
};
|
||||
|
||||
export default async function Page({ params: { segment } }: PageProps) {
|
||||
export default async function Page({ params }: PageProps) {
|
||||
const { segment } = await params;
|
||||
const service = segment[0];
|
||||
|
||||
if (!service) return null;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,6 @@
|
|||
import GoogleProvider from "next-auth/providers/google";
|
||||
import { KyselyAdapter } from "@auth/kysely-adapter";
|
||||
import { db } from "@link-stack/bridge-common";
|
||||
|
||||
export const authOptions = {
|
||||
// @ts-ignore
|
||||
adapter: KyselyAdapter(db),
|
||||
providers: [
|
||||
GoogleProvider({
|
||||
clientId: process.env.GOOGLE_CLIENT_ID!,
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
export { relinkBot as POST } from "@link-stack/bridge-ui";
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
import NextAuth from "next-auth";
|
||||
import { authOptions } from "@/app/_lib/authentication";
|
||||
|
||||
// Force this route to be dynamic (not statically generated at build time)
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
const handler = NextAuth(authOptions);
|
||||
|
||||
export { handler as GET, handler as POST };
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { Metadata } from "next";
|
|||
import { LicenseInfo } from "@mui/x-license";
|
||||
|
||||
LicenseInfo.setLicenseKey(
|
||||
"c787ac6613c5f2aa0494c4285fe3e9f2Tz04OTY1NyxFPTE3NDYzNDE0ODkwMDAsUz1wcm8sTE09c3Vic2NyaXB0aW9uLEtWPTI=",
|
||||
"2a7dd73ee59e3e028b96b0d2adee1ad8Tz0xMTMwOTUsRT0xNzc5MDYyMzk5MDAwLFM9cHJvLExNPXN1YnNjcmlwdGlvbixQVj1pbml0aWFsLEtWPTI=",
|
||||
);
|
||||
|
||||
export const metadata: Metadata = {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,6 @@
|
|||
|
||||
set -e
|
||||
echo "running migrations"
|
||||
(cd ../bridge-migrations/ && npm run migrate:up:all)
|
||||
(cd ../bridge-migrations/ && pnpm run migrate:up:all)
|
||||
echo "starting bridge-frontend"
|
||||
exec dumb-init npm run start
|
||||
exec dumb-init pnpm run start
|
||||
|
|
|
|||
|
|
@ -1,23 +1,81 @@
|
|||
import { withAuth } from "next-auth/middleware";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export default withAuth({
|
||||
pages: {
|
||||
signIn: `/login`,
|
||||
export default withAuth(
|
||||
function middleware(req) {
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
const nonce = Buffer.from(crypto.randomUUID()).toString("base64");
|
||||
|
||||
// Allow digiresilience.org for embedding documentation
|
||||
const frameSrcDirective = `frame-src 'self' https://digiresilience.org;`;
|
||||
|
||||
const cspHeader = `
|
||||
default-src 'self';
|
||||
${frameSrcDirective}
|
||||
connect-src 'self';
|
||||
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' ${isDev ? "'unsafe-eval'" : ""};
|
||||
style-src 'self' 'unsafe-inline';
|
||||
img-src 'self' blob: data:;
|
||||
font-src 'self';
|
||||
object-src 'none';
|
||||
base-uri 'self';
|
||||
form-action 'self';
|
||||
frame-ancestors 'self';
|
||||
upgrade-insecure-requests;
|
||||
`;
|
||||
const contentSecurityPolicyHeaderValue = cspHeader
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.trim();
|
||||
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
requestHeaders.set("x-nonce", nonce);
|
||||
requestHeaders.set(
|
||||
"Content-Security-Policy",
|
||||
contentSecurityPolicyHeaderValue,
|
||||
);
|
||||
|
||||
const response = NextResponse.next({
|
||||
request: {
|
||||
headers: requestHeaders,
|
||||
},
|
||||
});
|
||||
|
||||
response.headers.set(
|
||||
"Content-Security-Policy",
|
||||
contentSecurityPolicyHeaderValue,
|
||||
);
|
||||
|
||||
// Additional security headers
|
||||
response.headers.set("X-Frame-Options", "SAMEORIGIN");
|
||||
response.headers.set("X-Content-Type-Options", "nosniff");
|
||||
response.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
|
||||
response.headers.set("X-XSS-Protection", "1; mode=block");
|
||||
response.headers.set(
|
||||
"Permissions-Policy",
|
||||
"camera=(), microphone=(), geolocation=()"
|
||||
);
|
||||
|
||||
return response;
|
||||
},
|
||||
callbacks: {
|
||||
authorized: ({ token }) => {
|
||||
if (process.env.SETUP_MODE === "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token?.email) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
{
|
||||
pages: {
|
||||
signIn: `/login`,
|
||||
},
|
||||
},
|
||||
});
|
||||
callbacks: {
|
||||
authorized: ({ token }) => {
|
||||
if (process.env.SETUP_MODE === "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (token?.email) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export const config = {
|
||||
matcher: ["/((?!ws|wss|api|_next/static|_next/image|favicon.ico).*)"],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@link-stack/bridge-frontend",
|
||||
"version": "2.2.0",
|
||||
"version": "3.3.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
|
|
@ -13,28 +13,28 @@
|
|||
"migrate:down:one": "tsx database/migrate.ts down:one"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/kysely-adapter": "^1.5.2",
|
||||
"@link-stack/bridge-common": "*",
|
||||
"@link-stack/bridge-ui": "*",
|
||||
"@link-stack/ui": "*",
|
||||
"@mui/icons-material": "^5",
|
||||
"@mui/material": "^5",
|
||||
"@mui/material-nextjs": "^5",
|
||||
"@mui/x-license": "^7.18.0",
|
||||
"next": "^14.2.25",
|
||||
"next-auth": "^4.24.8",
|
||||
"react": "18.3.1",
|
||||
"react-dom": "18.3.1",
|
||||
"sharp": "^0.33.5",
|
||||
"tsx": "^4.19.1"
|
||||
"@auth/kysely-adapter": "^1.10.0",
|
||||
"@mui/icons-material": "^6",
|
||||
"@mui/material": "^6",
|
||||
"@mui/material-nextjs": "^6",
|
||||
"@mui/x-license": "^7",
|
||||
"@link-stack/bridge-common": "workspace:*",
|
||||
"@link-stack/bridge-ui": "workspace:*",
|
||||
"next": "15.5.4",
|
||||
"next-auth": "^4.24.11",
|
||||
"react": "19.2.0",
|
||||
"react-dom": "19.2.0",
|
||||
"sharp": "^0.34.4",
|
||||
"tsx": "^4.20.6",
|
||||
"@link-stack/ui": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@link-stack/eslint-config": "*",
|
||||
"@link-stack/typescript-config": "*",
|
||||
"@types/node": "^22",
|
||||
"@types/pg": "^8.11.10",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@link-stack/eslint-config": "workspace:*",
|
||||
"@link-stack/typescript-config": "workspace:*",
|
||||
"@types/node": "^24",
|
||||
"@types/pg": "^8.15.5",
|
||||
"@types/react": "^19",
|
||||
"@types/react-dom": "^19",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
apps/bridge-frontend/public/robots.txt
Normal file
2
apps/bridge-frontend/public/robots.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
User-agent: *
|
||||
Disallow: /
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
|
|
@ -14,14 +18,24 @@
|
|||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
],
|
||||
"target": "ES2017"
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue