22
.github/workflows/main.yml
vendored
22
.github/workflows/main.yml
vendored
@@ -2,6 +2,13 @@ name: Pipeline
|
||||
|
||||
on: [push]
|
||||
|
||||
env:
|
||||
POSTGRES_DATABASE: ${{ secrets.POSTGRES_DATABASE }}
|
||||
POSTGRES_USERNAME: ${{ secrets.POSTGRES_USERNAME }}
|
||||
POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }}
|
||||
POSTGRES_HOST: ${{ secrets.POSTGRES_HOST }}
|
||||
POSTGRES_PORT: ${{ secrets.POSTGRES_PORT }}
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -32,12 +39,27 @@ jobs:
|
||||
- run: yarn audit
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15.3
|
||||
env:
|
||||
POSTGRES_USER: ${{ env.POSTGRES_USERNAME }}
|
||||
POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }}
|
||||
POSTGRES_DB: ${{ env.POSTGRES_DATABASE }}
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
- env:
|
||||
DATABASE_URL: postgres://${{ secrets.POSTGRES_USERNAME }}:${{ secrets.POSTGRES_PASSWORD }}@${{ secrets.POSTGRES_HOST }}:${{ secrets.POSTGRES_PORT }}/${{ secrets.POSTGRES_DATABASE }}
|
||||
run: |
|
||||
echo "DATABASE_URL=${DATABASE_URL}" > .env
|
||||
- run: yarn
|
||||
- run: yarn db:migrate
|
||||
- run: yarn test
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -131,3 +131,4 @@ dist
|
||||
|
||||
# Custom
|
||||
build
|
||||
.env
|
||||
|
||||
@@ -10,6 +10,11 @@ It contains basic configurations for the following:
|
||||
- Winston (logging)
|
||||
- GitHub Actions (CI/CD)
|
||||
|
||||
# Environment Variables
|
||||
|
||||
- PORT - The port to run the server on
|
||||
- `DATABASE_URL` - The URL to the database, formatted as `postgres://<username>:<password>@<host>:<port>/<database>`
|
||||
|
||||
## Commands
|
||||
|
||||
Install dependencies:
|
||||
|
||||
@@ -10,7 +10,8 @@ const config: Config = {
|
||||
testMatch: ['**/*.test.ts'],
|
||||
transform: {
|
||||
'^.+\\.ts$': '@swc/jest'
|
||||
}
|
||||
},
|
||||
setupFilesAfterEnv: ['./src/utils/clearDatabase.ts']
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
||||
@@ -13,7 +13,10 @@
|
||||
"typecheck": "tsc",
|
||||
"format": "prettier --config .prettierrc 'src/**/*.ts' --write",
|
||||
"test": "jest --runInBand --detectOpenHandles",
|
||||
"prepare": "husky install"
|
||||
"prepare": "husky install",
|
||||
"db:generate": "prisma generate",
|
||||
"db:migrate": "prisma migrate deploy",
|
||||
"db:reset": "prisma migrate reset --force"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
@@ -24,11 +27,14 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "5.1.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"cors": "^2.8.5",
|
||||
"crypto": "^1.0.1",
|
||||
"express": "^4.18.2",
|
||||
"helmet": "^7.0.0",
|
||||
"jsonschema": "^1.4.1",
|
||||
"prisma": "^5.1.1",
|
||||
"winston": "^3.10.0",
|
||||
"winston-daily-rotate-file": "^4.7.1"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "Addition" (
|
||||
"id" TEXT NOT NULL,
|
||||
"datetime" TIMESTAMP(3) NOT NULL,
|
||||
"value" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "Addition_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
17
prisma/schema.prisma
Normal file
17
prisma/schema.prisma
Normal file
@@ -0,0 +1,17 @@
|
||||
// This is your Prisma schema file,
|
||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Addition {
|
||||
id String @id
|
||||
datetime DateTime
|
||||
value Int
|
||||
}
|
||||
13
src/database/database.test.ts
Normal file
13
src/database/database.test.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { createAddition, readAddition } from './database';
|
||||
|
||||
describe('Database', () => {
|
||||
it('creates a new record in the Addition table and reads it', async () => {
|
||||
const value = 13;
|
||||
|
||||
const createResult = await createAddition(value);
|
||||
|
||||
const readResult = await readAddition(createResult.id);
|
||||
expect(readResult).toBeDefined();
|
||||
expect(readResult.value).toBe(value);
|
||||
});
|
||||
});
|
||||
32
src/database/database.ts
Normal file
32
src/database/database.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/**
|
||||
* Create a new record in the Addition table.
|
||||
* @param value - The value number to add as a new record.
|
||||
* @returns Whether the database operation succeeded.
|
||||
*/
|
||||
export async function createAddition(value: number) {
|
||||
return await prisma.addition.create({
|
||||
data: {
|
||||
id: randomUUID(),
|
||||
datetime: new Date(),
|
||||
value
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a record from the Addition table.
|
||||
* @param id - The id of the record.
|
||||
* @returns The retrieved record, or an error.
|
||||
*/
|
||||
export async function readAddition(id: string) {
|
||||
return await prisma.addition.findUniqueOrThrow({
|
||||
where: {
|
||||
id
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import server from './server/server';
|
||||
|
||||
server.listen(3000, () => {
|
||||
console.log('Server running on port 3000');
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
server.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`);
|
||||
});
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import requests from 'supertest';
|
||||
import server from './server';
|
||||
|
||||
beforeAll(() => {
|
||||
jest.mock('../utils/addition', () => ({
|
||||
addition: jest.fn((value: number) => value + 1)
|
||||
}));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
jest.mock('../utils/addition', () => ({
|
||||
addition: jest.fn((value: number) => value + 1)
|
||||
}));
|
||||
|
||||
describe('server', () => {
|
||||
it('returns input value increased by one', async () => {
|
||||
|
||||
@@ -22,14 +22,18 @@ const schema = {
|
||||
required: ['value']
|
||||
};
|
||||
|
||||
server.post('/', (req: Request, res: Response) => {
|
||||
server.post('/', async (req: Request, res: Response) => {
|
||||
logger.info(`POST / with ${JSON.stringify(req.body)}`);
|
||||
if (!validator.validate(req.body, schema).valid) {
|
||||
return res.status(400).json({ message: 'Malformed query parameters' });
|
||||
}
|
||||
|
||||
const { value } = req.body;
|
||||
|
||||
const result = await addition(value);
|
||||
|
||||
return res.json({
|
||||
response: addition(parseInt(req.body.value))
|
||||
response: result
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import { addition } from './addition';
|
||||
|
||||
jest.mock('../database/database', () => ({
|
||||
createAddition: jest.fn((value: number) => ({
|
||||
id: randomUUID(),
|
||||
datetime: new Date(),
|
||||
value
|
||||
}))
|
||||
}));
|
||||
|
||||
describe('call', () => {
|
||||
it('returns the input value increased by one', async () => {
|
||||
const response = addition(3);
|
||||
const response = await addition(3);
|
||||
|
||||
expect(response).toBe(4);
|
||||
});
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import { createAddition } from '../database/database';
|
||||
import { logger } from './logger';
|
||||
|
||||
/**
|
||||
* Format a response message containing a user input.
|
||||
* Increase the user imput value by one, and creates an Addition record with that value.
|
||||
* @param number - The user input value.
|
||||
* @returns The value increased by one.
|
||||
*/
|
||||
export function addition(value: number) {
|
||||
export async function addition(value: number) {
|
||||
logger.info(`addition(${value})`);
|
||||
await createAddition(value);
|
||||
|
||||
return value + 1;
|
||||
}
|
||||
|
||||
14
src/utils/clearDatabase.ts
Normal file
14
src/utils/clearDatabase.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
/**
|
||||
* Clear the database of all records.
|
||||
*/
|
||||
export const clearDatabase = async () => {
|
||||
await prisma.addition.deleteMany({});
|
||||
};
|
||||
|
||||
global.beforeAll(async () => {
|
||||
await clearDatabase();
|
||||
});
|
||||
29
yarn.lock
29
yarn.lock
@@ -846,6 +846,23 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@prisma/client@5.1.1":
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-5.1.1.tgz#ea2b0c8599bdb3f86d92e8df46fba795a744db01"
|
||||
integrity sha512-fxcCeK5pMQGcgCqCrWsi+I2rpIbk0rAhdrN+ke7f34tIrgPwA68ensrpin+9+fZvuV2OtzHmuipwduSY6HswdA==
|
||||
dependencies:
|
||||
"@prisma/engines-version" "5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e"
|
||||
|
||||
"@prisma/engines-version@5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e":
|
||||
version "5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-5.1.1-1.6a3747c37ff169c90047725a05a6ef02e32ac97e.tgz#2e8a1f098ec09452dbe00923b24f582f95d1747c"
|
||||
integrity sha512-owZqbY/wucbr65bXJ/ljrHPgQU5xXTSkmcE/JcbqE1kusuAXV/TLN3/exmz21SZ5rJ7WDkyk70J2G/n68iogbQ==
|
||||
|
||||
"@prisma/engines@5.1.1":
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-5.1.1.tgz#9c26d209f93a563e048eab63b1976f222f1707d0"
|
||||
integrity sha512-NV/4nVNWFZSJCCIA3HIFJbbDKO/NARc9ej0tX5S9k2EVbkrFJC4Xt9b0u4rNZWL4V+F5LAjvta8vzEUw0rw+HA==
|
||||
|
||||
"@sinclair/typebox@^0.27.8":
|
||||
version "0.27.8"
|
||||
resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz"
|
||||
@@ -1922,6 +1939,11 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
||||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
crypto@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037"
|
||||
integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==
|
||||
|
||||
dargs@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz"
|
||||
@@ -3936,6 +3958,13 @@ pretty-format@^29.0.0, pretty-format@^29.6.1:
|
||||
ansi-styles "^5.0.0"
|
||||
react-is "^18.0.0"
|
||||
|
||||
prisma@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/prisma/-/prisma-5.1.1.tgz#8f5c0f9467a828746cb94f846d694dc7b7481a9e"
|
||||
integrity sha512-WJFG/U7sMmcc6TjJTTifTfpI6Wjoh55xl4AzopVwAdyK68L9/ogNo8QQ2cxuUjJf/Wa82z/uhyh3wMzvRIBphg==
|
||||
dependencies:
|
||||
"@prisma/engines" "5.1.1"
|
||||
|
||||
prompts@^2.0.1:
|
||||
version "2.4.2"
|
||||
resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz"
|
||||
|
||||
Reference in New Issue
Block a user