diff --git a/.dockerignore b/.dockerignore index 8ae4c896..2b3c04ad 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,4 +2,5 @@ .DS_Store .env node_modules +**/node_modules build \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..2a2eef9e --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,35 @@ +name: Create & Publish Docker Image + +on: + push: + tags: + - "v*.*.*" + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log In to the Container Registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build the Docker Image + run: | + npm run docker:build:push diff --git a/apps/platform/.gitignore b/apps/platform/.gitignore new file mode 100644 index 00000000..c47c3096 --- /dev/null +++ b/apps/platform/.gitignore @@ -0,0 +1,109 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test +*.local + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist +build + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# VS Code +.DS_Store diff --git a/apps/platform/Dockerfile b/apps/platform/Dockerfile index 4a00af60..a9d14320 100644 --- a/apps/platform/Dockerfile +++ b/apps/platform/Dockerfile @@ -22,7 +22,5 @@ ENV NODE_ENV="production" USER node WORKDIR /usr/src/app COPY --chown=node:node --from=build /usr/src/app ./ -RUN chmod 755 ./scripts/entrypoint.sh EXPOSE 3001 -ENTRYPOINT ["./scripts/entrypoint.sh", "--"] CMD ["dumb-init", "node", "index.js"] diff --git a/apps/platform/package.json b/apps/platform/package.json index a866dfd2..ab1a1bbf 100644 --- a/apps/platform/package.json +++ b/apps/platform/package.json @@ -43,6 +43,8 @@ "build": "tsc --build", "lint": "eslint --ext .ts --max-warnings 0 src/", "test": "jest --forceExit --testTimeout 10000", + "docker:build": "docker buildx build -f ./Dockerfile -t ghcr.io/parcelvoy/api:latest ../../", + "docker:build:push": "npm run docker:build -- --push", "migration:create": "node ./scripts/create-migration.mjs" }, "devDependencies": { diff --git a/apps/platform/scripts/entrypoint.sh b/apps/platform/scripts/entrypoint.sh deleted file mode 100644 index bcb34094..00000000 --- a/apps/platform/scripts/entrypoint.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -## Migrate the database -node ./migrate.js latest - -# Hand off to the CMD -exec "$@" \ No newline at end of file diff --git a/apps/platform/src/app.ts b/apps/platform/src/app.ts index 1c8d20de..a75ef85b 100644 --- a/apps/platform/src/app.ts +++ b/apps/platform/src/app.ts @@ -1,5 +1,5 @@ import Api from './api' -import loadDatabase, { Database, migrate } from './config/database' +import loadDatabase, { Database } from './config/database' import loadQueue from './config/queue' import loadStorage from './config/storage' import loadAuth from './config/auth' @@ -20,11 +20,8 @@ export default class App { } static async init(env: Env): Promise { - // Load database - const database = loadDatabase(env.db) - - // Migrate to latest version - await migrate(database) + // Load & migrate database + const database = await loadDatabase(env.db) // Load queue const queue = loadQueue(env.queue) diff --git a/apps/platform/src/config/controllers.ts b/apps/platform/src/config/controllers.ts index 8b67d1c9..27d71746 100644 --- a/apps/platform/src/config/controllers.ts +++ b/apps/platform/src/config/controllers.ts @@ -106,6 +106,13 @@ export const clientRouter = () => { */ export const publicRouter = () => { const router = new Router() + router.get('/health', async (ctx) => { + ctx.body = { + status: 'ok', + environment: process.env.NODE_ENV, + time: new Date(), + } + }) return register(router, AuthController, PublicSubscriptionController, diff --git a/apps/platform/src/config/database.ts b/apps/platform/src/config/database.ts index ec6396cc..142cea9e 100644 --- a/apps/platform/src/config/database.ts +++ b/apps/platform/src/config/database.ts @@ -7,7 +7,7 @@ export interface DatabaseConnection { port: number user: string password: string - database: string + database?: string } export interface DatabaseConfig { @@ -15,11 +15,15 @@ export interface DatabaseConfig { connection: DatabaseConnection } -export default (config: DatabaseConfig) => { +const connect = (config: DatabaseConfig, withDB = true) => { + const connection = config.connection + if (!withDB) { + delete connection.database + } return knex({ client: config.client, connection: { - ...config.connection, + ...connection, typeCast(field: any, next: any) { if (field.type === 'TINY' && field.length === 1) { return field.string() === '1' @@ -31,9 +35,25 @@ export default (config: DatabaseConfig) => { }) } -export const migrate = async (db: Database) => { +const migrate = async (db: Database) => { return db.migrate.latest({ directory: './db/migrations', tableName: 'migrations', }) } + +export default async (config: DatabaseConfig) => { + + // Attempt to connect & migrate + try { + const db = connect(config) + await migrate(db) + return db + } catch (error) { + + // On error, try to create the database and try again + const db = connect(config, false) + db.raw('CREATE DATABASE parcelvoy') + return connect(config) + } +} diff --git a/apps/platform/src/render/Helpers/String.ts b/apps/platform/src/render/Helpers/String.ts index 289b0974..a83ecb8c 100644 --- a/apps/platform/src/render/Helpers/String.ts +++ b/apps/platform/src/render/Helpers/String.ts @@ -53,7 +53,7 @@ export const capitalizeAll = function(str: string): string { /** * Truncates a string to the specified `length`, and appends - * it with an elipsis, `…`. + * it with an ellipsis, `…`. */ export const ellipsis = function(str: string, limit: number): string { if (!isString(str)) return '' diff --git a/apps/platform/tsconfig.json b/apps/platform/tsconfig.json index b57e4bea..25823412 100644 --- a/apps/platform/tsconfig.json +++ b/apps/platform/tsconfig.json @@ -5,5 +5,14 @@ "moduleResolution": "node", "baseUrl": "./src", "outDir": "./build" - } + }, + "include": [ + "./src/**/*", + "tsconfig.json" + ], + "exclude": [ + "node_modules", + "./**/*.spec.ts", + "./**/__mocks__/*" + ] } diff --git a/apps/ui/Dockerfile b/apps/ui/Dockerfile index 32791113..d2d14635 100644 --- a/apps/ui/Dockerfile +++ b/apps/ui/Dockerfile @@ -8,6 +8,11 @@ RUN npm run build # --------------> The production image FROM nginx:1.23.1-alpine -EXPOSE 80 +EXPOSE 3000 COPY --from=compile /usr/src/app/apps/ui/docker/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf -COPY --from=compile /usr/src/app/apps/ui/build /usr/share/nginx/html \ No newline at end of file +COPY --from=compile /usr/src/app/apps/ui/build /usr/share/nginx/html +COPY --from=compile /usr/src/app/apps/ui/scripts /usr/share/nginx/html +WORKDIR /usr/share/nginx/html +RUN apk add --no-cache bash +RUN chmod +x env.sh +CMD ["/bin/bash", "-c", "/usr/share/nginx/html/env.sh && nginx -g \"daemon off;\""] \ No newline at end of file diff --git a/apps/ui/docker/nginx/conf.d/default.conf b/apps/ui/docker/nginx/conf.d/default.conf index 7b7cb6ce..99a88880 100644 --- a/apps/ui/docker/nginx/conf.d/default.conf +++ b/apps/ui/docker/nginx/conf.d/default.conf @@ -1,9 +1,13 @@ server { - listen 80; + listen 3000; root /usr/share/nginx/html; index index.html; location / { try_files $uri $uri/ /index.html; } + + location /api { + proxy_pass http://api:3001; + } } \ No newline at end of file diff --git a/apps/ui/package.json b/apps/ui/package.json index 67a2a780..4629f263 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -39,7 +39,9 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\"" + "lint": "eslint \"src/**/*.{js,jsx,ts,tsx}\"", + "docker:build": "docker buildx build -f ./Dockerfile -t ghcr.io/parcelvoy/ui:latest ../../", + "docker:build:push": "npm run docker:build -- --push" }, "browserslist": { "production": [ diff --git a/apps/ui/public/config.js b/apps/ui/public/config.js new file mode 100644 index 00000000..760acb2c --- /dev/null +++ b/apps/ui/public/config.js @@ -0,0 +1 @@ +window.API_BASE_URL = undefined diff --git a/apps/ui/public/index.html b/apps/ui/public/index.html index 725af226..9f4d285a 100644 --- a/apps/ui/public/index.html +++ b/apps/ui/public/index.html @@ -12,6 +12,8 @@ + +