diff --git a/.gitignore b/.gitignore index 5a83693..4011990 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules .env .turbo +*.tsbuildinfo build/** **/dist/** .next/** @@ -19,9 +20,14 @@ docker-compose.yml coverage .pgpass **/dist/** -.metamigo.local.json +.bridge.local.json out/ signald-state/* !./signald-state/.gitkeep baileys-state signald-state +project.org +**/.openapi-generator/ +apps/bridge-worker/scripts/* +ENVIRONMENT_VARIABLES_MIGRATION.md +local-scripts/* diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 98b3eaf..8fcd82b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: node:20-bookworm-slim +image: node:22-bookworm-slim stages: - build @@ -8,37 +8,44 @@ stages: build-all: stage: build variables: - TURBO_TOKEN: $TURBO_TOKEN - TURBO_TEAM: $TURBO_TEAM + TURBO_TOKEN: ${TURBO_TOKEN} + TURBO_TEAM: ${TURBO_TEAM} + ZAMMAD_URL: ${ZAMMAD_URL} + PNPM_HOME: "/pnpm" script: - - npm install npm@latest -g - - npm install -g turbo - - npm ci + - export PATH="$PNPM_HOME:$PATH" + - corepack enable && corepack prepare pnpm@9.15.4 --activate + - pnpm add -g turbo + - pnpm install --frozen-lockfile - turbo build .docker-build: - image: registry.gitlab.com/digiresilience/link/link-stack/buildx:${CI_COMMIT_REF_NAME} + image: registry.gitlab.com/digiresilience/link/link-stack/buildx:main services: - docker:dind stage: docker-build variables: + DOCKER_HOST: tcp://docker:2375 + DOCKER_TLS_CERTDIR: "" DOCKER_TAG: ${CI_COMMIT_SHORT_SHA} - DOCKER_CONTEXT: . + BUILD_CONTEXT: . only: - main - develop - tags script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - - DOCKER_BUILDKIT=1 docker build --pull --no-cache -t ${DOCKER_NS}:${DOCKER_TAG} -f ${DOCKERFILE_PATH} ${DOCKER_CONTEXT} + - DOCKER_BUILDKIT=1 docker build --pull --no-cache -t ${DOCKER_NS}:${DOCKER_TAG} -f ${DOCKERFILE_PATH} ${BUILD_CONTEXT} - docker push ${DOCKER_NS}:${DOCKER_TAG} .docker-release: - image: registry.gitlab.com/digiresilience/link/link-stack/buildx:${CI_COMMIT_REF_NAME} + image: registry.gitlab.com/digiresilience/link/link-stack/buildx:main services: - docker:dind stage: docker-release variables: + DOCKER_HOST: tcp://docker:2375 + DOCKER_TLS_CERTDIR: "" DOCKER_TAG: ${CI_COMMIT_SHORT_SHA} DOCKER_TAG_NEW: ${CI_COMMIT_REF_NAME} only: @@ -73,49 +80,49 @@ link-docker-release: variables: DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/link -leafcutter-docker-build: +bridge-frontend-docker-build: extends: .docker-build variables: - DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/leafcutter - DOCKERFILE_PATH: ./apps/leafcutter/Dockerfile + DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/bridge-frontend + DOCKERFILE_PATH: ./apps/bridge-frontend/Dockerfile -leafcutter-docker-release: +bridge-frontend-docker-release: extends: .docker-release variables: - DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/leafcutter + DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/bridge-frontend -metamigo-docker-build: +bridge-worker-docker-build: extends: .docker-build variables: - DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/metamigo - DOCKERFILE_PATH: ./apps/metamigo-cli/Dockerfile + DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/bridge-worker + DOCKERFILE_PATH: ./apps/bridge-worker/Dockerfile -metamigo-docker-release: +bridge-worker-docker-release: extends: .docker-release variables: - DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/metamigo + DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/bridge-worker -elasticsearch-docker-build: +bridge-whatsapp-docker-build: extends: .docker-build variables: - DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/elasticsearch - DOCKERFILE_PATH: ./docker/elasticsearch/Dockerfile + DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/bridge-whatsapp + DOCKERFILE_PATH: ./apps/bridge-whatsapp/Dockerfile -elasticsearch-docker-release: +bridge-whatsapp-docker-release: extends: .docker-release variables: - DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/elasticsearch + DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/bridge-whatsapp -label-studio-docker-build: +signal-cli-rest-api-docker-build: extends: .docker-build variables: - DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/label-studio - DOCKERFILE_PATH: ./docker/label-studio/Dockerfile + DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/signal-cli-rest-api + DOCKERFILE_PATH: ./docker/signal-cli-rest-api/Dockerfile -label-studio-docker-release: +signal-cli-rest-api-docker-release: extends: .docker-release variables: - DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/label-studio + DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/signal-cli-rest-api memcached-docker-build: extends: .docker-build @@ -183,35 +190,48 @@ redis-docker-release: variables: DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/redis -signald-docker-build: - extends: .docker-build - variables: - DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/signald - DOCKERFILE_PATH: ./docker/signald/Dockerfile - -signald-docker-release: - extends: .docker-release - variables: - DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/signald - zammad-docker-build: extends: .docker-build variables: DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/zammad DOCKERFILE_PATH: ./docker/zammad/Dockerfile - DOCKER_CONTEXT: ./docker/zammad + BUILD_CONTEXT: ./docker/zammad + PNPM_HOME: "/pnpm" before_script: - - apk --update add nodejs npm + - export PATH="$PNPM_HOME:$PATH" + - corepack enable && corepack prepare pnpm@9.15.4 --activate script: - - npm install npm@latest -g - - npm install -g turbo - - npm ci - - turbo build --force --filter zammad-addon-* + - pnpm add -g turbo + - pnpm install --frozen-lockfile + - turbo build --force --filter @link-stack/zammad-addon-* - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY - - DOCKER_BUILDKIT=1 docker build --pull --no-cache -t ${DOCKER_NS}:${DOCKER_TAG} -f ${DOCKERFILE_PATH} ${DOCKER_CONTEXT} + - DOCKER_BUILDKIT=1 docker build --build-arg EMBEDDED=true --pull --no-cache -t ${DOCKER_NS}:${DOCKER_TAG} -f ${DOCKERFILE_PATH} ${BUILD_CONTEXT} - docker push ${DOCKER_NS}:${DOCKER_TAG} zammad-docker-release: extends: .docker-release variables: DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/zammad + +zammad-standalone-docker-build: + extends: .docker-build + variables: + DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/zammad-standalone + DOCKERFILE_PATH: ./docker/zammad/Dockerfile + BUILD_CONTEXT: ./docker/zammad + PNPM_HOME: "/pnpm" + before_script: + - export PATH="$PNPM_HOME:$PATH" + - corepack enable && corepack prepare pnpm@9.15.4 --activate + script: + - pnpm add -g turbo + - pnpm install --frozen-lockfile + - turbo build --force --filter @link-stack/zammad-addon-* + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - DOCKER_BUILDKIT=1 docker build --pull --no-cache -t ${DOCKER_NS}:${DOCKER_TAG} -f ${DOCKERFILE_PATH} ${BUILD_CONTEXT} + - docker push ${DOCKER_NS}:${DOCKER_TAG} + +zammad-standalone-docker-release: + extends: .docker-release + variables: + DOCKER_NS: ${CI_REGISTRY}/digiresilience/link/link-stack/zammad-standalone diff --git a/.gitpod.dockerfile b/.gitpod.dockerfile deleted file mode 100644 index e2b4a7b..0000000 --- a/.gitpod.dockerfile +++ /dev/null @@ -1,78 +0,0 @@ -FROM gitpod/workspace-full - -# install tools we need -RUN set -ex; \ - pyenv global system; \ - sudo add-apt-repository ppa:ansible/ansible; \ - sudo add-apt-repository ppa:maxmind/ppa; \ - curl -s https://helm.baltorepo.com/organization/signing.asc | sudo apt-key add - ; \ - curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash; \ - echo "deb https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list ; \ - sudo apt-get update; \ - sudo apt-get -y upgrade ; \ - sudo apt-get install -y \ - ansible \ - build-essential \ - httpie \ - fd-find \ - ffmpeg \ - geoipupdate \ - gitlab-runner \ - helm \ - htop \ - iotop \ - iptraf \ - jq \ - kitty-terminfo \ - libolm-dev \ - ncdu \ - postgresql \ - pwgen \ - python3-wheel \ - ripgrep \ - rsync \ - scdaemon \ - socat \ - tmux \ - unrar \ - unzip \ - vifm \ - vim \ - yamllint \ - zsh \ - zsh-syntax-highlighting \ - ; sudo rm -rf /var/lib/apt/lists/* - -RUN set -ex; \ - brew install \ - zoxide \ - fzf; - -# needed for tailscale -RUN sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-nft - -# install npm global packages we need -RUN set -ex; \ - npm install -g \ - standard-version \ - turbo \ - ; - -# make a place for all our warez -RUN sudo mkdir -p /usr/local/bin - -# install AWS' kubectl -# from https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html -ARG KUBECTL_URL="https://amazon-eks.s3.us-west-2.amazonaws.com/1.21.2/2021-07-05/bin/linux/amd64/kubectl" -RUN set -ex; \ - curl -o kubectl "${KUBECTL_URL}"; \ - chmod +x kubectl; \ - sudo mv kubectl /usr/local/bin - -# install cloudflared -# from https://github.com/cloudflare/cloudflared/releases -ARG CLOUDFLARED_VERSION="2023.2.1" -RUN set -ex; \ - wget --progress=dot:mega https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64.deb; \ - sudo dpkg -i cloudflared-linux-amd64.deb; \ - cloudflared --version diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index b7144e3..0000000 --- a/.gitpod.yml +++ /dev/null @@ -1,63 +0,0 @@ ---- -# build the docker image for our gitpod from this dockerfile -image: - file: .gitpod.dockerfile -# all init+before are run in prebuilds, and on workspace startup -tasks: - - name: npm install - init: | - npm install -# extra extensions we share -vscode: - extensions: - - redhat.vscode-yaml - - ms-azuretools.vscode-docker - - ms-kubernetes-tools.vscode-kubernetes-tools - - ms-vscode.makefile-tools - - bungcip.better-toml - - sleistner.vscode-fileutils - - esbenp.prettier-vscode - - darkriszty.markdown-table-prettify - - VisualStudioExptTeam.vscodeintellicode - -ports: - - name: Zammad - port: 8001 - onOpen: notify - - - name: Leafcutter Local - port: 3001 - onOpen: notify - - - name: Leafcutter - port: 8004 - onOpen: notify - - - name: Link - port: 8003 - onOpen: notify - - - name: Link Local - port: 3000 - onOpen: notify - - - - name: Metamigo - port: 8002 - onOpen: notify - - - name: Metamigo Local - port: 2999 - onOpen: notify - - - name: Metamigo API - port: 8004 - onOpen: notify - - - name: Zammad Postgres - port: 5432 - onOpen: notify - - - name: Metamigo Postgres - port: 5433 - onOpen: notify \ No newline at end of file diff --git a/.nvmrc b/.nvmrc index 6ed5da9..32cfab6 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20.2.0 +v22.18.0 diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 4183c4a..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "prettier.prettierPath": "" -} diff --git a/Makefile b/Makefile deleted file mode 100644 index dfd3742..0000000 --- a/Makefile +++ /dev/null @@ -1,83 +0,0 @@ -CURRENT_UID := $(shell id -u):$(shell id -g) -PACKAGE_NAME ?= $(shell jq -r '.name' package.json) -PACKAGE_VERSION?= $(shell jq -r '.version' package.json) -BUILD_DATE ?=$(shell date -u +"%Y-%m-%dT%H:%M:%SZ") -DOCKER_ARGS ?= -DOCKER_NS ?= registry.gitlab.com/digiresilience/link/${PACKAGE_NAME} -DOCKER_TAG ?= test -DOCKER_BUILD := docker build ${DOCKER_ARGS} --build-arg BUILD_DATE=${BUILD_DATE} -DOCKER_BUILD_FRESH := ${DOCKER_BUILD} --pull --no-cache -DOCKER_BUILD_ARGS := --build-arg VCS_REF=${CI_COMMIT_SHORT_SHA} -DOCKER_PUSH := docker push -DOCKER_BUILD_TAG := ${DOCKER_NS}:${DOCKER_TAG} - -.PHONY: .npmrc -.EXPORT_ALL_VARIABLES: - -.npmrc: -ifdef CI_JOB_TOKEN - echo '@guardianproject-ops:registry=https://gitlab.com/api/v4/packages/npm/' > .npmrc - echo '@digiresilience:registry=https://gitlab.com/api/v4/packages/npm/' >> .npmrc - echo '//gitlab.com/api/v4/packages/npm/:_authToken=${CI_JOB_TOKEN}' >> .npmrc - echo '//gitlab.com/api/v4/projects/:_authToken=${CI_JOB_TOKEN}' >> .npmrc - echo '//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}' >> .npmrc -endif - -docker/build: .npmrc - DOCKER_BUILDKIT=1 ${DOCKER_BUILD} ${DOCKER_BUILD_ARGS} -t ${DOCKER_BUILD_TAG} ${PWD} - -docker/build-fresh: .npmrc - DOCKER_BUILDKIT=1 ${DOCKER_BUILD_FRESH} ${DOCKER_BUILD_ARGS} -t ${DOCKER_BUILD_TAG} ${PWD} - -docker/add-tag: - docker pull ${DOCKER_NS}:${DOCKER_TAG} - docker tag ${DOCKER_NS}:${DOCKER_TAG} ${DOCKER_NS}:${DOCKER_TAG_NEW} - docker push ${DOCKER_NS}:${DOCKER_TAG_NEW} - -docker/push: - ${DOCKER_PUSH} ${DOCKER_BUILD_TAG} - -docker/build-push: docker/build docker/push -docker/build-fresh-push: docker/build-fresh docker/push - -# don't use this to generate passwords for production -generate-secrets: - ZAMMAD_DATABASE_PASSWORD=$(shell openssl rand -hex 16) - METAMIGO_DATABASE_ROOT_PASSWORD=$(shell openssl rand -hex 16) - METAMIGO_DATABASE_PASSWORD=$(shell openssl rand -hex 16) - METAMIGO_DATABASE_AUTHENTICATOR_PASSWORD=$(shell openssl rand -hex 16) - NEXTAUTH_AUDIENCE=$(shell openssl rand -hex 16) - NEXTAUTH_SECRET=$(shell openssl rand -hex 16) - -generate-keys: - docker exec -i $(shell docker ps -aqf "name=metamigo-frontend") bash -c "/opt/metamigo/cli gen-jwks" - -setup-signal: - mkdir -p signald - -create-admin-user: - docker exec -i $(shell docker ps -aqf "name=metamigo-postgresql") bash < ./scripts/create-admin-user.sh - - -.env: - @test -f .env || echo "You must create .env please refer to the README" && exit 1 - -start: .env - CURRENT_UID=$(CURRENT_UID) docker compose -f docker-compose.link.yml up -d - -start-dev: .env - CURRENT_UID=$(CURRENT_UID) docker compose up --build -d - -restart: .env - CURRENT_UID=$(CURRENT_UID) docker restart $(shell docker ps -a -q) - -stop: - CURRENT_UID=$(CURRENT_UID) docker compose down - -destroy: - docker compose down - docker volume prune - - -dev-metamigo: - CURRENT_UID=$(CURRENT_UID) docker compose up -d metamigo-postgresql signald diff --git a/README.md b/README.md index 5b2659e..155729c 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,23 @@ -# Dev Setup +# CDR Link -> NOTE: When using Gitpod/Codespaces, use at least 16GB RAM +CDR Link is a simple & streamlined helpdesk that lets you tag, assign and respond to your tickets. It is developed by the [Center for Digital Resilience](https://digiresilience.org) and powered by [Zammad](https://zammad.org). -Local dev with docker-compose +Key differences between CDR Link and a standard Zammad installation: -* Create `link-stack/.env` from Bitwarden `.env for root of link-stack` -* Run local dev with docker-compose: - ``` - git clone ... - cd link-stack - make start-dev - ``` +- In addition to the full Zammad interface, CDR Link also provides a simplified 'shell' interface that focuses on the most-commonly-used functionality. +- Additional channels to communicate with users, including Signal, Whatsapp & Twilio voice messaging. +- More stringent privacy defaults: ticket data is never sent over email and calls to third-party services are restricted. -Or for local dev of a single app +## Developing -* Create `link-stack/apps/link/.env.local` from Bitwarden `.env.local for link-stack/apps/link` -* Create `link-stack/apps/metamigo-frontend/.metamigo.local.json` from Bitwarden `.metamigo.local.json for link-stack/apps/metamigo/frontend` -* Build locally for development: - ``` - npm install - make dev-metamigo # this starts the containers - npm run migrate # this migrates the db - npm run dev:metamigo # this runs metamigo frontend and api - ``` +This is a monorepo that contains CDR Link and several supporting applications and libraries. It also includes Dockerfiles to build all of the other containers required for an installation. By tagging our own versions of these dependencies, we can make sure that different versions of the supporting containers all work together and are updated in sync. -# TODO +We use [Turborepo](https://turbo.build) to manage development and building of the packages. To get started: + +- `npm install` in the root directory +- `turbo build` to build all packages + +To run a single package: + +- `turbo dev --filter @link-stack/link` -- [ ] Delete old JWT config stuff -- [ ] Consolidate config -- [ ] Complete react-admin upgrade.. make all the metamigo-frontend stuff work - * https://marmelab.com/react-admin/Upgrade.html#no-more-prop-injection-in-page-components -- [ ] Get metamigo-worker working -- [ ] Migrate off mui/styles - * https://mui.com/material-ui/migration/v5-style-changes/ - * the codemods might help us? \ No newline at end of file diff --git a/apps/bridge-frontend/.eslintrc.json b/apps/bridge-frontend/.eslintrc.json new file mode 100644 index 0000000..bffb357 --- /dev/null +++ b/apps/bridge-frontend/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/apps/leafcutter/.gitignore b/apps/bridge-frontend/.gitignore similarity index 72% rename from apps/leafcutter/.gitignore rename to apps/bridge-frontend/.gitignore index 16922fd..fd3dbb5 100644 --- a/apps/leafcutter/.gitignore +++ b/apps/bridge-frontend/.gitignore @@ -4,6 +4,7 @@ /node_modules /.pnp .pnp.js +.yarn/install-state.gz # testing /coverage @@ -21,18 +22,15 @@ # debug npm-debug.log* +yarn-debug.log* +yarn-error.log* # local env files -.env.local -.env.development.local -.env.test.local -.env.production.local +.env*.local # vercel .vercel -/storybook-static - -*.tgz - -.vscode +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/apps/bridge-frontend/Dockerfile b/apps/bridge-frontend/Dockerfile new file mode 100644 index 0000000..75a19ce --- /dev/null +++ b/apps/bridge-frontend/Dockerfile @@ -0,0 +1,54 @@ +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 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/pnpm-lock.yaml ./pnpm-lock.yaml +RUN pnpm install --frozen-lockfile + +COPY --from=builder ${APP_DIR}/out/full/ . +RUN pnpm add -g turbo +RUN turbo run build --filter=@link-stack/bridge-frontend --filter=@link-stack/bridge-migrations + +FROM base AS runner +ARG APP_DIR=/opt/bridge-frontend +WORKDIR ${APP_DIR}/ +ARG BUILD_DATE +ARG VERSION +LABEL maintainer="Darren Clarke " +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} ./ +RUN chown -R node:node ${APP_DIR}/ +WORKDIR ${APP_DIR}/apps/bridge-frontend/ +RUN chmod +x docker-entrypoint.sh +USER node +EXPOSE 3000 +ENV PORT 3000 +ENV NODE_ENV production +ENTRYPOINT ["/opt/bridge-frontend/apps/bridge-frontend/docker-entrypoint.sh"] diff --git a/apps/bridge-frontend/README.md b/apps/bridge-frontend/README.md new file mode 100644 index 0000000..ebec248 --- /dev/null +++ b/apps/bridge-frontend/README.md @@ -0,0 +1,133 @@ +# Bridge Frontend + +Frontend application for managing communication bridges between various messaging platforms and the CDR Link system. + +## 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 + +# Build for production +npm run build + +# Start production server +npm run start +``` + +### Environment Variables + +Required environment variables: + +- `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 + +### Available Scripts + +- `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 + +## Architecture + +### Database Schema + +The application manages the following main entities: + +- **Bots**: Communication channel bot configurations +- **Webhooks**: Webhook endpoints for external integrations +- **Settings**: Channel-specific configuration settings +- **Users**: User accounts with role-based permissions + +### 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 \ No newline at end of file diff --git a/apps/bridge-frontend/app/(login)/login/page.tsx b/apps/bridge-frontend/app/(login)/login/page.tsx new file mode 100644 index 0000000..8f79c27 --- /dev/null +++ b/apps/bridge-frontend/app/(login)/login/page.tsx @@ -0,0 +1,14 @@ +import { Metadata } from "next"; +import { getSession } from "next-auth/react"; +import { Login } from "@/app/_components/Login"; + +export const dynamic = "force-dynamic"; + +export const metadata: Metadata = { + title: "Login", +}; + +export default async function Page() { + const session = await getSession(); + return ; +} diff --git a/apps/bridge-frontend/app/(main)/[...segment]/@create/page.tsx b/apps/bridge-frontend/app/(main)/[...segment]/@create/page.tsx new file mode 100644 index 0000000..bc5b6b8 --- /dev/null +++ b/apps/bridge-frontend/app/(main)/[...segment]/@create/page.tsx @@ -0,0 +1,12 @@ +import { Create } from "@link-stack/bridge-ui"; + +type PageProps = { + params: Promise<{ segment: string[] }>; +}; + +export default async function Page({ params }: PageProps) { + const { segment } = await params; + const service = segment[0]; + + return ; +} diff --git a/apps/bridge-frontend/app/(main)/[...segment]/@detail/page.tsx b/apps/bridge-frontend/app/(main)/[...segment]/@detail/page.tsx new file mode 100644 index 0000000..e857b7d --- /dev/null +++ b/apps/bridge-frontend/app/(main)/[...segment]/@detail/page.tsx @@ -0,0 +1,28 @@ +import { db } from "@link-stack/bridge-common"; +import { serviceConfig, Detail } from "@link-stack/bridge-ui"; + +type PageProps = { + params: Promise<{ segment: string[] }>; +}; + +export default async function Page({ params }: PageProps) { + const { segment } = await params; + const service = segment[0]; + const id = segment?.[1]; + + if (!id) return null; + + const { + [service]: { table }, + } = serviceConfig; + + const row = await db + .selectFrom(table) + .selectAll() + .where("id", "=", id) + .executeTakeFirst(); + + if (!row) return null; + + return ; +} diff --git a/apps/bridge-frontend/app/(main)/[...segment]/@edit/page.tsx b/apps/bridge-frontend/app/(main)/[...segment]/@edit/page.tsx new file mode 100644 index 0000000..82c8052 --- /dev/null +++ b/apps/bridge-frontend/app/(main)/[...segment]/@edit/page.tsx @@ -0,0 +1,28 @@ +import { db } from "@link-stack/bridge-common"; +import { serviceConfig, Edit } from "@link-stack/bridge-ui"; + +type PageProps = { + params: Promise<{ segment: string[] }>; +}; + +export default async function Page({ params }: PageProps) { + const { segment } = await params; + const service = segment[0]; + const id = segment?.[1]; + + if (!id) return null; + + const { + [service]: { table }, + } = serviceConfig; + + const row = await db + .selectFrom(table) + .selectAll() + .where("id", "=", id) + .executeTakeFirst(); + + if (!row) return null; + + return ; +} diff --git a/apps/bridge-frontend/app/(main)/[...segment]/layout.tsx b/apps/bridge-frontend/app/(main)/[...segment]/layout.tsx new file mode 100644 index 0000000..c360a57 --- /dev/null +++ b/apps/bridge-frontend/app/(main)/[...segment]/layout.tsx @@ -0,0 +1,3 @@ +import { ServiceLayout } from "@link-stack/bridge-ui"; + +export default ServiceLayout; diff --git a/apps/bridge-frontend/app/(main)/[...segment]/page.tsx b/apps/bridge-frontend/app/(main)/[...segment]/page.tsx new file mode 100644 index 0000000..7b4fc03 --- /dev/null +++ b/apps/bridge-frontend/app/(main)/[...segment]/page.tsx @@ -0,0 +1,23 @@ +import { db } from "@link-stack/bridge-common"; +import { serviceConfig, List } from "@link-stack/bridge-ui"; + +type PageProps = { + params: Promise<{ + segment: string[]; + }>; +}; + +export default async function Page({ params }: PageProps) { + const { segment } = await params; + const service = segment[0]; + + if (!service) return null; + + const config = serviceConfig[service]; + + if (!config) return null; + + const rows = await db.selectFrom(config.table).selectAll().execute(); + + return ; +} diff --git a/apps/bridge-frontend/app/(main)/layout.tsx b/apps/bridge-frontend/app/(main)/layout.tsx new file mode 100644 index 0000000..32203a2 --- /dev/null +++ b/apps/bridge-frontend/app/(main)/layout.tsx @@ -0,0 +1,9 @@ +import { InternalLayout } from "@/app/_components/InternalLayout"; + +export default function Layout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return {children}; +} diff --git a/apps/bridge-frontend/app/(main)/page.tsx b/apps/bridge-frontend/app/(main)/page.tsx new file mode 100644 index 0000000..e01be47 --- /dev/null +++ b/apps/bridge-frontend/app/(main)/page.tsx @@ -0,0 +1,5 @@ +import { Home } from "@link-stack/bridge-ui"; + +export default function Page() { + return ; +} diff --git a/apps/bridge-frontend/app/_components/InternalLayout.tsx b/apps/bridge-frontend/app/_components/InternalLayout.tsx new file mode 100644 index 0000000..72985ad --- /dev/null +++ b/apps/bridge-frontend/app/_components/InternalLayout.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { FC, PropsWithChildren, useState } from "react"; +import { Grid } from "@mui/material"; +import { CssBaseline } from "@mui/material"; +import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter"; +import { SessionProvider } from "next-auth/react"; +import { Sidebar } from "./Sidebar"; + +export const InternalLayout: FC = ({ children }) => { + const [open, setOpen] = useState(true); + + return ( + + + + + + + {children as any} + + + + + ); +}; diff --git a/apps/bridge-frontend/app/_components/Login.tsx b/apps/bridge-frontend/app/_components/Login.tsx new file mode 100644 index 0000000..8e40129 --- /dev/null +++ b/apps/bridge-frontend/app/_components/Login.tsx @@ -0,0 +1,185 @@ +"use client"; + +import { FC, useState } from "react"; +import { + Box, + Grid, + Container, + IconButton, + Typography, + TextField, +} from "@mui/material"; +import { + Apple as AppleIcon, + Google as GoogleIcon, + Key as KeyIcon, +} from "@mui/icons-material"; +import { signIn } from "next-auth/react"; +import Image from "next/image"; +import LinkLogo from "@/app/_images/link-logo-small.png"; +import { colors, fonts } from "@link-stack/ui"; +import { useSearchParams } from "next/navigation"; + +type LoginProps = { + session: any; +}; + +export const Login: FC = ({ session }) => { + const origin = + typeof window !== "undefined" && window.location.origin + ? window.location.origin + : ""; + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const params = useSearchParams(); + const error = params.get("error"); + const { darkGray, cdrLinkOrange, white } = colors; + const { poppins } = fonts; + const buttonStyles = { + borderRadius: 500, + width: "100%", + fontSize: "16px", + fontWeight: "bold", + backgroundColor: white, + "&:hover": { + color: white, + backgroundColor: cdrLinkOrange, + }, + }; + const fieldStyles = { + "& label.Mui-focused": { + color: cdrLinkOrange, + }, + "& .MuiInput-underline:after": { + borderBottomColor: cdrLinkOrange, + }, + "& .MuiFilledInput-underline:after": { + borderBottomColor: cdrLinkOrange, + }, + "& .MuiOutlinedInput-root": { + "&.Mui-focused fieldset": { + borderColor: cdrLinkOrange, + }, + }, + }; + + return ( + + + + + + + Link logo + + + + + CDR Bridge + + + + + + {!session ? ( + + + {error ? ( + + + + {`${error} error`} + + + + ) : null} + + + signIn("google", { + callbackUrl: `${origin}`, + }) + } + > + + Sign in with Google + + + + + signIn("apple", { + callbackUrl: `${window.location.origin}`, + }) + } + > + + Sign in with Apple + + + + + ) : null} + {session ? ( + + {` ${session.user.name ?? session.user.email}.`} + + ) : null} + + + + + ); +}; diff --git a/apps/bridge-frontend/app/_components/Sidebar.tsx b/apps/bridge-frontend/app/_components/Sidebar.tsx new file mode 100644 index 0000000..31aaaaa --- /dev/null +++ b/apps/bridge-frontend/app/_components/Sidebar.tsx @@ -0,0 +1,399 @@ +"use client"; + +import { FC } from "react"; +import { + Box, + Grid, + Typography, + List, + ListItemButton, + ListItemIcon, + ListItemText, + ListItemSecondaryAction, + Drawer, +} from "@mui/material"; +import { + ExpandCircleDown as ExpandCircleDownIcon, + AccountCircle as AccountCircleIcon, + Chat as ChatIcon, + PermPhoneMsg as PhoneIcon, + WhatsApp as WhatsAppIcon, + Facebook as FacebookIcon, + AirlineStops as AirlineStopsIcon, + Logout as LogoutIcon, +} from "@mui/icons-material"; +import { usePathname } from "next/navigation"; +import Link from "next/link"; +import Image from "next/image"; +import { typography, fonts, Button } from "@link-stack/ui"; +import LinkLogo from "@/app/_images/link-logo-small.png"; +import { useSession, signOut } from "next-auth/react"; + +const openWidth = 270; +const closedWidth = 70; + +const MenuItem = ({ + name, + href, + Icon, + iconSize, + inset = false, + selected = false, + open = true, + badge, + target = "_self", +}: any) => ( + + + {iconSize > 0 ? ( + + + + + + ) : ( + + + + + )} + {open && ( + + {name} + + } + /> + )} + {badge && badge > 0 ? ( + + + {badge} + + + ) : null} + + +); + +interface SidebarProps { + open: boolean; + setOpen: (open: boolean) => void; +} + +export const Sidebar: FC = ({ open, setOpen }) => { + const pathname = usePathname(); + const { poppins } = fonts; + const { bodyLarge } = typography; + const { data: session } = useSession(); + const user = session?.user; + + const logout = () => { + signOut({ callbackUrl: "/login" }); + }; + + return ( + + { + setOpen!(!open); + }} + > + + + + + + + Link logo + + . + + {open && ( + + + CDR Bridge + + + )} + + + + + + + + + + + + + + + + {user?.image && ( + + + Profile image + + + )} + + + + {user?.email} + + + +