Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions localenv/cloud-nine-wallet/dbinit.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ CREATE USER happy_life_bank_auth WITH PASSWORD 'happy_life_bank_auth';
CREATE DATABASE happy_life_bank_auth;
ALTER DATABASE happy_life_bank_auth OWNER TO happy_life_bank_auth;

CREATE USER cloud_nine_wallet_pos WITH PASSWORD 'cloud_nine_wallet_pos';
CREATE DATABASE cloud_nine_wallet_pos;
ALTER DATABASE cloud_nine_wallet_pos OWNER TO cloud_nine_wallet_pos;

CREATE USER happy_life_bank_pos WITH PASSWORD 'happy_life_bank_pos';
CREATE DATABASE happy_life_bank_pos;
ALTER DATABASE happy_life_bank_pos OWNER TO happy_life_bank_pos;

CREATE USER happy_life_bank_card_service WITH PASSWORD 'happy_life_bank_card_service';
CREATE DATABASE happy_life_bank_card_service;
ALTER DATABASE happy_life_bank_card_service OWNER TO happy_life_bank_card_service;
35 changes: 35 additions & 0 deletions packages/point-of-sale/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
FROM node:20-alpine3.20

RUN adduser -D rafiki
WORKDIR /home/rafiki

# Install Corepack and pnpm as the Rafiki user
USER rafiki
RUN mkdir -p /home/rafiki/.local/bin
ENV PATH="/home/rafiki/.local/bin:$PATH"
RUN corepack enable --install-directory ~/.local/bin
RUN corepack prepare [email protected] --activate
COPY pnpm-lock.yaml package.json pnpm-workspace.yaml .npmrc tsconfig.json tsconfig.build.json ./

# Fetch the pnpm dependencies, but use a local cache.
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm fetch \
| grep -v "cross-device link not permitted\|Falling back to copying packages from store"

# Copy the source code and chown the relevant folders back to the Rafiki user
USER root
COPY . ./
RUN chown -v -R rafiki:rafiki /home/rafiki/localenv
RUN chown -v -R rafiki:rafiki /home/rafiki/packages
RUN chown -v -R rafiki:rafiki /home/rafiki/test

# As the Rafiki user, install the rest of the dependencies and build the source code
USER rafiki
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm install \
--recursive \
--offline \
--frozen-lockfile
RUN pnpm --filter point-of-sale build:deps

CMD pnpm --filter point-of-sale dev
71 changes: 71 additions & 0 deletions packages/point-of-sale/Dockerfile.prod
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
FROM node:20-alpine3.20 AS base

WORKDIR /home/rafiki

ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"

RUN corepack enable
RUN corepack prepare [email protected] --activate

COPY pnpm-lock.yaml ./

RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm fetch \
| grep -v "cross-device link not permitted\|Falling back to copying packages from store"

FROM base AS prod-deps

COPY package.json pnpm-workspace.yaml .npmrc ./
COPY packages/point-of-sale/knexfile.js ./packages/point-of-sale/knexfile.js
COPY packages/point-of-sale/package.json ./packages/point-of-sale/package.json
COPY packages/token-introspection/package.json ./packages/token-introspection/package.json

RUN pnpm clean
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm install \
--recursive \
--prefer-offline \
--frozen-lockfile \
--prod \
| grep -v "cross-device link not permitted\|Falling back to copying packages from store"

FROM base AS builder

COPY package.json pnpm-workspace.yaml .npmrc tsconfig.json tsconfig.build.json ./
COPY packages/point-of-sale ./packages/point-of-sale
COPY packages/token-introspection ./packages/token-introspection

RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
pnpm install \
--recursive \
--offline \
--frozen-lockfile
RUN pnpm --filter point-of-sale build

FROM node:20-alpine3.20 AS runner

# Since this is from a fresh image, we need to first create the Rafiki user
RUN adduser -D rafiki
WORKDIR /home/rafiki

COPY --from=prod-deps /home/rafiki/node_modules ./node_modules
COPY --from=prod-deps /home/rafiki/packages/point-of-sale/node_modules ./packages/point-of-sale/node_modules
COPY --from=prod-deps /home/rafiki/packages/point-of-sale/package.json ./packages/point-of-sale/package.json
COPY --from=prod-deps /home/rafiki/packages/token-introspection/node_modules ./packages/token-introspection/node_modules
COPY --from=prod-deps /home/rafiki/packages/token-introspection/package.json ./packages/token-introspection/package.json
COPY --from=prod-deps /home/rafiki/packages/point-of-sale/knexfile.js ./packages/point-of-sale/knexfile.js

COPY --from=builder /home/rafiki/packages/point-of-sale/migrations/ ./packages/point-of-sale/migrations
COPY --from=builder /home/rafiki/packages/point-of-sale/dist ./packages/point-of-sale/dist
COPY --from=builder /home/rafiki/packages/token-introspection/dist ./packages/token-introspection/dist
COPY --from=builder /home/rafiki/packages/point-of-sale/knexfile.js ./packages/point-of-sale/knexfile.js

USER root

# For additional paranoia, we make it so that the Rafiki user has no write access to the packages
RUN chown -R :rafiki /home/rafiki/packages
RUN chmod -R 750 /home/rafiki/packages

USER rafiki
CMD ["node", "-r", "/home/rafiki/packages/point-of-sale/dist/telemetry/index.js", "/home/rafiki/packages/point-of-sale/dist/index.js"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the POS service will publish telemetry data for now

43 changes: 43 additions & 0 deletions packages/point-of-sale/knexfile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Update with your config settings.

/**
* @type { Object.<string, import("knex").Knex.Config> }
*/
module.exports = {
development: {
client: 'postgresql',
connection: {
database: 'pos',
user: 'postgres',
password: 'password'
}
},

testing: {
client: 'postgresql',
connection: {
database: 'pos_testing',
user: 'postgres',
password: 'password'
},
pool: {
min: 2,
max: 10
},
migrations: {
tableName: 'pos_knex_migrations'
}
},

production: {
client: 'postgresql',
connection: process.env.POS_DATABASE_URL,
pool: {
min: 2,
max: 10
},
migrations: {
tableName: 'pos_knex_migrations'
}
}
}
37 changes: 37 additions & 0 deletions packages/point-of-sale/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "point-of-sale",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"knex": "knex",
"dev": "ts-node-dev --inspect=0.0.0.0:9229 --respawn --transpile-only src/index.ts",
"build": "pnpm build:deps && pnpm clean && tsc --build tsconfig.json",
"build:deps": "pnpm --filter token-introspection build",
"clean": "rm -fr dist/"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@adonisjs/fold": "^8.2.0",
"@apollo/server": "^4.11.2",
"@koa/cors": "^5.0.0",
"@koa/router": "^12.0.2",
"dotenv": "^16.4.7",
"knex": "^3.1.0",
"koa": "^3.0.0",
"koa-bodyparser": "^4.4.1",
"objection": "^3.1.5",
"objection-db-errors": "^1.1.2",
"pg": "^8.11.3",
"pino": "^8.19.0"
},
"devDependencies": {
"@types/koa": "2.15.0",
"@types/koa-bodyparser": "^4.3.12",
"@types/koa__cors": "^5.0.0",
"@types/koa__router": "^12.0.4"
}
}
113 changes: 113 additions & 0 deletions packages/point-of-sale/src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Knex } from 'knex'
import { Logger } from 'pino'
import { IAppConfig } from './config/app'
import { Server } from 'http'
import { IocContract } from '@adonisjs/fold'
import Koa, { DefaultState } from 'koa'
import Router from '@koa/router'
import bodyParser from 'koa-bodyparser'
import cors from '@koa/cors'

export interface AppServices {
logger: Promise<Logger>
knex: Promise<Knex>
config: Promise<IAppConfig>
}

export type AppContainer = IocContract<AppServices>

export interface AppContextData {
logger: Logger
container: AppContainer
// Set by @koa/router.
params: { [key: string]: string }
}

export type AppContext = Koa.ParameterizedContext<DefaultState, AppContextData>

export class App {
private posServer!: Server
public isShuttingDown = false
private logger!: Logger
private config!: IAppConfig

public constructor(private container: IocContract<AppServices>) {}

public async boot(): Promise<void> {
this.config = await this.container.use('config')
this.logger = await this.container.use('logger')
}

public async startPosServer(port: number): Promise<void> {
const koa = await this.createKoaServer()

const router = new Router<DefaultState, AppContext>()
router.use(bodyParser())
router.get('/healthz', (ctx: AppContext): void => {
ctx.status = 200
})

koa.use(cors())
koa.use(router.routes())

this.posServer = koa.listen(port)
}

public async shutdown(): Promise<void> {
this.isShuttingDown = true

if (this.posServer) {
await this.stopServer(this.posServer)
}
}

private async stopServer(server: Server): Promise<void> {
return new Promise((resolve, reject) => {
server.close((err) => {
if (err) {
reject(err)
}

resolve()
})
})
}

public getPort() {
const address = this.posServer?.address()
if (address && !(typeof address == 'string')) {
return address.port
}
return 0
}

private async createKoaServer(): Promise<Koa<Koa.DefaultState, AppContext>> {
const koa = new Koa<DefaultState, AppContext>({
proxy: this.config.trustProxy
})

koa.context.container = this.container
koa.context.logger = await this.container.use('logger')

koa.use(
async (
ctx: {
status: number
set: (arg0: string, arg1: string) => void
body: string
},
next: () => void | PromiseLike<void>
): Promise<void> => {
if (this.isShuttingDown) {
ctx.status = 503
ctx.set('Connection', 'close')
ctx.body = 'Server is in the process of restarting'
} else {
return next()
}
}
)

return koa
}
}
41 changes: 41 additions & 0 deletions packages/point-of-sale/src/config/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import dotenv from 'dotenv'

function envString(name: string, defaultValue?: string): string {
const envValue = process.env[name]

if (envValue) return envValue
if (defaultValue) return defaultValue

throw new Error(`Environment variable ${name} must be set.`)
}

function envInt(name: string, value: number): number {
const envValue = process.env[name]
return envValue == null ? value : parseInt(envValue)
}

function envBool(name: string, value: boolean): boolean {
const envValue = process.env[name]
return envValue == null ? value : envValue === 'true'
}

export type IAppConfig = typeof Config

dotenv.config({
path: process.env.ENV_FILE || '.env'
})

export const Config = {
logLevel: envString('LOG_LEVEL', 'info'),
databaseUrl:
process.env.NODE_ENV === 'test'
? `${process.env.POS_DATABASE_URL}`
: envString(
'POS_DATABASE_URL',
'postgresql://postgres:password@localhost:5432/pos_development'
),
env: envString('NODE_ENV', 'development'),
port: envInt('PORT', 3020),
trustProxy: envBool('TRUST_PROXY', false),
enableManualMigrations: envBool('ENABLE_MANUAl_MIGRATIONS', false)
}
Loading
Loading