Skip to content
Open
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.19.6
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
20
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ COPY . /app
RUN yarn run build
RUN npm prune --production --omit=dev
RUN rm -rf src/
RUN mkdir -p /app/dist

FROM node:20-alpine3.18
ENV NODE_ENV=production
COPY --from=0 /app /app
WORKDIR /app
RUN apk add --update --no-cache openssl1.1-compat
RUN apk add --update --no-cache openssl1.1-compat postgresql-client
COPY ./docker-entrypoint.sh /docker-entrypoint.sh
CMD /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
CMD ["/docker-entrypoint.sh"]
53 changes: 53 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
services:
db:
image: postgres:15-alpine
container_name: labs_gql_db
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: labsdb
ports:
- "5433:5432"
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 2s
retries: 10

elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:7.17.9
container_name: labs_gql_elasticsearch
environment:
- discovery.type=single-node
- xpack.security.enabled=false
ports:
- "9200:9200"
volumes:
- elasticsearch_data:/usr/share/elasticsearch/data
healthcheck:
test: ["CMD-SHELL", "curl -s http://localhost:9200/_cluster/health | grep -q green || grep -q yellow"]
interval: 5s
retries: 20

app:
build: .
container_name: labs_gql_app
depends_on:
db:
condition: service_healthy
elasticsearch:
condition: service_healthy
ports:
- "5000:5000"
- "5001:5001"
env_file:
- .env
environment:
- ELASTIC_URL=http://elasticsearch:9200
stdin_open: true
tty: true

volumes:
db_data:
elasticsearch_data:
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
"scripts": {
"start": "node dist",
"clean": "rm -rf dist",
"build": "npm -s run clean && npm -s run swagger && npm -s run prisma && npx tsc --skipLibCheck && cpy '**/*' '!**/*.ts' ../dist/ --cwd=src/ --no-overwrite --parents",
"build": "npm -s run clean && npm -s run swagger && npm -s run prisma && npx tsc --skipLibCheck && cp -r src/ dist/ --no-clobber --parents",
"prisma": "prisma format && prisma generate",
"dev": "ts-node-dev --no-notify --respawn --transpile-only src",
"debug": "ts-node-dev --no-notify --respawn src",
"send-event-recommendations": "ts-node scripts/sendEventRecommendations.ts",
"seed-dummy": "ts-node scripts/seedDummy.ts",
"generate-token": "ts-node scripts/generateToken.ts",
"test-queries": "ts-node scripts/testQueries.ts",
"swagger": "rm src/badgr/Api.ts; swagger-typescript-api -p badgr-api-v2.yaml -n Api2.ts -o ./src/badgr; echo 'type json = JSON;' | cat - src/badgr/Api2.ts > src/badgr/Api.ts; rm src/badgr/Api2.ts"
},
"dependencies": {
Expand Down Expand Up @@ -42,6 +45,7 @@
"gpt-tokenizer": "^2.1.1",
"graphql": "^15.5.0",
"graphql-type-json": "^0.3.2",
"graphql-upload": "^13.0.0",
"graphql-ws": "^5.14.2",
"handlebars": "^4.7.8",
"json-schema": "^0.3.0",
Expand Down Expand Up @@ -87,4 +91,4 @@
"ts-node-dev": "^1.1.6",
"typescript": "^5.2.2"
}
}
}
41 changes: 41 additions & 0 deletions scripts/generateToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'reflect-metadata';
import { PrismaClient } from '@prisma/client';
import { signTokenAdmin, signTokenManager, signTokenUser } from '../src/utils';

const prisma = new PrismaClient();

async function main() {
const eventId = process.argv[2] || 'event-test-2025';
const role = process.argv[3] || 'admin'; // admin, manager, mentor, or student

const event = await prisma.event.findUniqueOrThrow({ where: { id: eventId } });

let token: string;

if (role === 'admin') {
token = signTokenAdmin(event);
console.log(`\nAdmin token for event "${eventId}":\n${token}\n`);
} else if (role === 'manager') {
token = signTokenManager(event);
console.log(`\nManager token for event "${eventId}":\n${token}\n`);
} else if (role === 'mentor') {
const mentor = await prisma.mentor.findFirst({ where: { eventId } });
if (!mentor) throw new Error(`No mentors found for event ${eventId}`);
token = signTokenUser(mentor);
console.log(`\nMentor token for ${mentor.email} in event "${eventId}":\n${token}\n`);
} else if (role === 'student') {
const student = await prisma.student.findFirst({ where: { eventId } });
if (!student) throw new Error(`No students found for event ${eventId}`);
token = signTokenUser(student);
console.log(`\nStudent token for ${student.email} in event "${eventId}":\n${token}\n`);
} else {
throw new Error(`Unknown role: ${role}. Use admin, manager, mentor, or student.`);
}
}

main()
.catch((err) => {
console.error(err);
process.exit(1);
})
.finally(() => prisma.$disconnect());
180 changes: 180 additions & 0 deletions scripts/seedDummy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
import { PrismaClient, Track, ProjectStatus } from '@prisma/client';

const prisma = new PrismaClient();

function daysFromNow(days: number): Date {
const d = new Date();
d.setDate(d.getDate() + days);
return d;
}

async function main(): Promise<void> {
const eventId = 'event-test-2025';

const event = await prisma.event.upsert({
where: { id: eventId },
update: {
name: 'Dummy Event',
title: 'Dependency Update Test Event',
emailSignature: '— CodeDay Labs',
studentApplicationsEndAt: daysFromNow(14),
mentorApplicationsEndAt: daysFromNow(14),
matchingDueAt: daysFromNow(21),
startsAt: daysFromNow(30),
projectWorkStartsAt: daysFromNow(31),
},
create: {
id: eventId,
name: 'Dummy Event',
title: 'Dependency Update Test Event',
emailSignature: '— CodeDay Labs',
studentApplicationsStartAt: new Date(),
mentorApplicationsStartAt: new Date(),
studentApplicationsEndAt: daysFromNow(14),
mentorApplicationsEndAt: daysFromNow(14),
matchingStartsAt: null,
matchingDueAt: daysFromNow(21),
matchingEndsAt: null,
startsAt: daysFromNow(30),
projectWorkStartsAt: daysFromNow(31),
studentApplicationSchema: {},
studentApplicationUi: {},
studentApplicationPostprocess: {},
mentorApplicationSchema: {},
mentorApplicationUi: {},
mentorApplicationPostprocess: {},
hasBeginner: true,
hasIntermediate: true,
hasAdvanced: true,
certificationStatements: ['Participants agree to CodeDay Labs testing.'],
defaultWeeks: 4,
isActive: true,
matchPreferenceSubmissionOpen: true,
},
});

const mentorA = await prisma.mentor.upsert({
where: {
email_eventId: { email: 'mentor.alice@example.com', eventId: event.id },
},
update: {
givenName: 'Alice',
surname: 'Mentor',
username: 'mentor.alice',
profile: { bio: 'Senior engineer testing dependencies.' },
},
create: {
givenName: 'Alice',
surname: 'Mentor',
username: 'mentor.alice',
email: 'mentor.alice@example.com',
profile: { bio: 'Senior engineer testing dependencies.' },
timezone: 'UTC',
eventId: event.id,
},
});

const mentorB = await prisma.mentor.upsert({
where: {
email_eventId: { email: 'mentor.bob@example.com', eventId: event.id },
},
update: {
givenName: 'Bob',
surname: 'Mentor',
username: 'mentor.bob',
profile: { bio: 'Backend mentor for testing.' },
},
create: {
givenName: 'Bob',
surname: 'Mentor',
username: 'mentor.bob',
email: 'mentor.bob@example.com',
profile: { bio: 'Backend mentor for testing.' },
timezone: 'UTC',
eventId: event.id,
},
});

const studentA = await prisma.student.upsert({
where: {
email_eventId: { email: 'student.ava@example.com', eventId: event.id },
},
update: {
givenName: 'Ava',
surname: 'Student',
username: 'student.ava',
profile: { interests: ['graphql', 'ts'] },
},
create: {
givenName: 'Ava',
surname: 'Student',
username: 'student.ava',
email: 'student.ava@example.com',
profile: { interests: ['graphql', 'ts'] },
track: Track.BEGINNER,
minHours: 10,
eventId: event.id,
},
});

const studentB = await prisma.student.upsert({
where: {
email_eventId: { email: 'student.ben@example.com', eventId: event.id },
},
update: {
givenName: 'Ben',
surname: 'Student',
username: 'student.ben',
profile: { interests: ['elasticsearch', 'node'] },
},
create: {
givenName: 'Ben',
surname: 'Student',
username: 'student.ben',
email: 'student.ben@example.com',
profile: { interests: ['elasticsearch', 'node'] },
track: Track.INTERMEDIATE,
minHours: 12,
eventId: event.id,
},
});

const project = await prisma.project.upsert({
where: { id: 'project-test-2025' },
update: {
description: 'Test project for dependency validation.',
deliverables: 'Working prototype and docs.',
status: ProjectStatus.ACCEPTED,
},
create: {
id: 'project-test-2025',
description: 'Test project for dependency validation.',
deliverables: 'Working prototype and docs.',
track: Track.INTERMEDIATE,
maxStudents: 3,
status: ProjectStatus.ACCEPTED,
eventId: event.id,
mentors: { connect: [{ id: mentorA.id }, { id: mentorB.id }] },
students: { connect: [{ id: studentA.id }, { id: studentB.id }] },
},
});

console.log('Seed complete');
console.table([
{ entity: 'Event', id: event.id },
{ entity: 'Mentor', id: mentorA.id },
{ entity: 'Mentor', id: mentorB.id },
{ entity: 'Student', id: studentA.id },
{ entity: 'Student', id: studentB.id },
{ entity: 'Project', id: project.id },
]);
}

main()
.catch((err) => {
console.error(err);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
Loading