Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20.19.6
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
FROM node:14.17.1-alpine
FROM node:20-alpine3.18

ENV NODE_ENV=production
RUN apk add --no-cache postgresql-client openssl1.1-compat
RUN mkdir /app
COPY yarn.lock /app
COPY package.json /app
Expand All @@ -9,5 +10,7 @@ WORKDIR /app
RUN NODE_ENV=development yarn install
COPY . /app

RUN npx prisma generate
RUN yarn run build

CMD npm run start
64 changes: 52 additions & 12 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,23 +1,63 @@
version: '3.9'

services:
db:
image: postgres:13
restart: always
container_name: integration-tests-prisma
postgres:
image: postgres:15-alpine
container_name: clear-gql-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: test
ports:
- '5433:5432'
- "5433:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5

elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.9
container_name: clear-gql-elasticsearch
environment:
POSTGRES_USER: prisma
POSTGRES_PASSWORD: prisma
POSTGRES_DB: tests
discovery.type: single-node
xpack.security.enabled: "false"
ports:
- "9200:9200"
volumes:
- elasticsearch_data:/usr/share/elasticsearch/data
healthcheck:
test: ["CMD-SHELL", "pg_isready"]
test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
interval: 10s
timeout: 5s
retries: 5
healthy:
image: hello-world
app:
build: .
container_name: clear-gql-app
depends_on:
db:
postgres:
condition: service_healthy
elasticsearch:
condition: service_healthy
env_file:
- .env
environment:
NODE_ENV: ${NODE_ENV:-development}
DATABASE_URL: ${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/test}
ports:
- "4000:5000"
volumes:
- .:/app
- /app/node_modules
command: npm run dev
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4000/.well-known/apollo/server-health"]
interval: 10s
timeout: 5s
retries: 5

volumes:
postgres_data:
elasticsearch_data:
18 changes: 10 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,24 @@
"scripts": {
"start": "prisma migrate deploy && node dist",
"clean": "rm -rf dist",
"build": "npm -s run clean && npm -s run prisma && npx tsc && cpy '**/*' '!**/*.ts' ../dist/ --cwd=src/ --no-overwrite --parents",
"build": "npm -s run clean && npm -s run prisma && npx tsc && cp -r src/**/* dist/ --parents 2>/dev/null || true",
"prisma": "prisma format && prisma generate",
"dev": "ts-node-dev --no-notify --respawn --transpile-only src",
"test-docker:up": "docker compose up -d && dotenv -e .env.test -- prisma migrate deploy",
"test-docker:down": "docker compose down",
"test": "yarn test-docker:up && dotenv -e .env.test -- jest -i --coverage && yarn test-docker:down"
"test": "yarn test-docker:up && dotenv -e .env.test -- jest -i --coverage && yarn test-docker:down",
"seed-dummy": "ts-node scripts/seedDummy.ts",
"generate-token": "ts-node scripts/generateToken.ts",
"test-queries": "ts-node scripts/testQueries.ts"
},
"dependencies": {
"@codeday/uploader-node": "^1.0.1",
"@prisma/client": "~3.12.0",
"@prisma/client": "4.4.0",
"@types/dot-object": "^2.1.2",
"@types/node-schedule": "^1.3.2",
"@types/ws": "^7.4.7",
"addresser": "^1.1.19",
"apollo-server-express": "^3.1.2",
"apollo-server-express": "^3.13.0",
"class-validator": "^0.13.1",
"cuid": "^2.1.8",
"dot-object": "^2.1.4",
Expand All @@ -26,7 +29,7 @@
"graphql": "^15.5.1",
"graphql-fields": "^2.0.3",
"graphql-scalars": "^1.10.0",
"graphql-upload": "^12.0.0",
"graphql-upload": "^13.0.0",
"graphql-ws": "^5.3.0",
"gravatar": "^1.8.2",
"handlebars": "^4.7.7",
Expand All @@ -39,14 +42,14 @@
"node-schedule": "^2.0.0",
"phone": "^3.1.8",
"postmark": "^2.7.8",
"prisma": "~3.12.0",
"prisma": "4.4.0",
"razorpay": "^2.8.0",
"reflect-metadata": "^0.1.13",
"stripe": "^8.183.0",
"twilio": "^3.71.1",
"type-graphql": "^1.1.1",
"typedi": "0.8.0",
"typegraphql-prisma": "0.20.1",
"typegraphql-prisma": "0.22.0",
"webhook-discord": "^3.7.8",
"ws": "^8.1.0"
},
Expand All @@ -67,7 +70,6 @@
"@types/node-fetch": "2",
"@types/validator": "^13.7.2",
"babel-jest": "^27.4.6",
"cpy-cli": "^3.1.1",
"csv-stringify": "^6.2.0",
"dotenv-cli": "^4.1.1",
"jest": "^27.4.7",
Expand Down
88 changes: 88 additions & 0 deletions scripts/generateToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import jwt from 'jsonwebtoken';
import config from '../src/config';

enum AuthRole {
ATTENDEE = 'a',
VOLUNTEER = 'v',
MANAGER = 'm',
ADMIN = 'A',
}

interface TokenPayload {
t: AuthRole; // type
u?: string; // username (required for manager/volunteer)
iat?: number;
exp?: number;
}

async function generateToken(): Promise<void> {
const args = process.argv.slice(2);

if (args.length < 1) {
console.error('Usage: yarn generate-token <role> [username]');
console.error('Roles: admin, manager, volunteer, attendee');
console.error('Note: manager and volunteer require a username');
console.error('\nExamples:');
console.error(' yarn generate-token admin');
console.error(' yarn generate-token manager alice');
process.exit(1);
}

const roleArg = args[0].toLowerCase();
const username = args[1];

const roleMap: { [key: string]: AuthRole } = {
'admin': AuthRole.ADMIN,
'manager': AuthRole.MANAGER,
'volunteer': AuthRole.VOLUNTEER,
'attendee': AuthRole.ATTENDEE,
};

if (!roleMap[roleArg]) {
console.error(`Invalid role: ${roleArg}`);
console.error(`Valid roles: ${Object.keys(roleMap).join(', ')}`);
process.exit(1);
}

const role = roleMap[roleArg];

if ((role === AuthRole.MANAGER || role === AuthRole.VOLUNTEER) && !username) {
console.error(`Error: ${roleArg} role requires a username`);
console.error(`Usage: yarn generate-token ${roleArg} <username>`);
process.exit(1);
}

if (role === AuthRole.ADMIN && username) {
console.error('Warning: admin tokens should not specify a username (ignoring)');
}

const payload: TokenPayload = {
t: role,
};

if (username && (role === AuthRole.MANAGER || role === AuthRole.VOLUNTEER)) {
payload.u = username;
}

const token = jwt.sign(payload, config.auth.secret, {
audience: config.auth.audience,
expiresIn: '24h',
});

console.log('\n=== Generated JWT Token ===');
console.log(`\nToken:\n${token}\n`);
console.log('=== Token Details ===');
console.log(`Role: ${roleArg} (${role})`);
if (username) {
console.log(`Username: ${username}`);
}
console.log(`Audience: ${config.auth.audience}`);
console.log('\nUsage:');
console.log(` export API_KEY="${token}"`);
console.log(` yarn test-queries\n`);
}

generateToken().catch((error) => {
console.error('Error generating token:', error);
process.exit(1);
});
Loading
Loading