├── src ├── activities │ ├── entities │ │ └── activity.entity.ts │ ├── dto │ │ ├── update-activity.dto.ts │ │ └── create-activity.dto.ts │ ├── activities.module.ts │ ├── activities.service.spec.ts │ ├── activities.controller.spec.ts │ ├── activities.controller.ts │ └── activities.service.ts ├── types.ts ├── app.service.ts ├── auth │ ├── jwt │ │ ├── jwt-auth.guard.ts │ │ ├── jwt-auth.module.ts │ │ ├── jwt-auth.strategy.ts │ │ └── jwt-auth.service.ts │ ├── google │ │ ├── google-auth.guard.ts │ │ ├── google-auth.module.ts │ │ ├── google-auth.controller.ts │ │ └── google-auth.strategy.ts │ ├── dto │ │ └── register-user.dto.ts │ ├── auth.controller.ts │ └── auth.module.ts ├── prisma │ ├── prisma.module.ts │ ├── prisma.service.ts │ └── prisma.service.spec.ts ├── app.module.ts ├── main.ts ├── app.controller.spec.ts └── app.controller.ts ├── .prettierrc ├── tsconfig.build.json ├── nest-cli.json ├── prisma ├── migrations │ ├── migration_lock.toml │ ├── 20221002185927_init │ │ └── migration.sql │ ├── 20221002192538_init │ │ └── migration.sql │ ├── 20220925203103_init │ │ └── migration.sql │ ├── 20221002192910_init │ │ └── migration.sql │ └── 20221002182729_init │ │ └── migration.sql ├── schema.prisma └── seed.ts ├── test ├── jest-e2e.json └── app.e2e-spec.ts ├── docker-compose.yml ├── .gitignore ├── tsconfig.json ├── .eslintrc.js ├── package.json └── README.md /src/activities/entities/activity.entity.ts: -------------------------------------------------------------------------------- 1 | export class Activity {} 2 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type JwtPayload = { sub: number; email: string }; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src" 5 | } 6 | -------------------------------------------------------------------------------- /prisma/migrations/migration_lock.toml: -------------------------------------------------------------------------------- 1 | # Please do not edit this file manually 2 | # It should be added in your version-control system (i.e. Git) 3 | provider = "postgresql" -------------------------------------------------------------------------------- /src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/auth/jwt/jwt-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class JwtAuthGuard extends AuthGuard('jwt') {} 6 | -------------------------------------------------------------------------------- /src/auth/google/google-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class GoogleOauthGuard extends AuthGuard('google') {} 6 | -------------------------------------------------------------------------------- /src/activities/dto/update-activity.dto.ts: -------------------------------------------------------------------------------- 1 | import { PartialType } from '@nestjs/swagger'; 2 | import { CreateActivityDto } from './create-activity.dto'; 3 | 4 | export class UpdateActivityDto extends PartialType(CreateActivityDto) {} 5 | -------------------------------------------------------------------------------- /test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/prisma/prisma.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PrismaService } from './prisma.service'; 3 | 4 | @Module({ 5 | providers: [PrismaService], 6 | exports: [PrismaService], 7 | }) 8 | export class PrismaModule {} 9 | -------------------------------------------------------------------------------- /src/auth/dto/register-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | 3 | export class RegisterUserDto { 4 | @ApiProperty() 5 | email: string; 6 | 7 | @ApiProperty() 8 | firstName: string; 9 | 10 | @ApiProperty() 11 | lastName: string; 12 | } 13 | -------------------------------------------------------------------------------- /prisma/migrations/20221002185927_init/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - A unique constraint covering the columns `[userId]` on the table `ProfessorInfo` will be added. If there are existing duplicate values, this will fail. 5 | 6 | */ 7 | -- CreateIndex 8 | CREATE UNIQUE INDEX "ProfessorInfo_userId_key" ON "ProfessorInfo"("userId"); 9 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | postgres: 4 | image: postgres 5 | restart: always 6 | environment: 7 | - POSTGRES_USER=sandbox 8 | - POSTGRES_PASSWORD=chongus 9 | - POSTGRES_DB=fat 10 | volumes: 11 | - postgres:/var/lib/postgresql/data 12 | ports: 13 | - '5432:5432' 14 | volumes: 15 | postgres: 16 | -------------------------------------------------------------------------------- /src/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Res } from '@nestjs/common'; 2 | import { ApiTags } from '@nestjs/swagger'; 3 | import { Response } from 'express'; 4 | 5 | @Controller('auth') 6 | @ApiTags('auth') 7 | export class AuthController { 8 | @Get() 9 | async auth(@Res() res: Response) { 10 | return res.redirect('/auth/google'); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/prisma/prisma.service.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication, Injectable } from '@nestjs/common'; 2 | import { PrismaClient } from '@prisma/client'; 3 | 4 | @Injectable() 5 | export class PrismaService extends PrismaClient { 6 | async enableShutdownHooks(app: INestApplication) { 7 | this.$on('beforeExit', async () => { 8 | await app.close(); 9 | }); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /prisma/migrations/20221002192538_init/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `title` on the `User` table. All the data in the column will be lost. 5 | 6 | */ 7 | -- AlterTable 8 | ALTER TABLE "ProfessorInfo" ALTER COLUMN "teachingReleaseExplanation" DROP NOT NULL; 9 | 10 | -- AlterTable 11 | ALTER TABLE "User" DROP COLUMN "title", 12 | ALTER COLUMN "preferred_name" DROP NOT NULL; 13 | -------------------------------------------------------------------------------- /src/activities/activities.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ActivitiesService } from './activities.service'; 3 | import { ActivitiesController } from './activities.controller'; 4 | import { PrismaModule } from 'src/prisma/prisma.module'; 5 | 6 | @Module({ 7 | controllers: [ActivitiesController], 8 | providers: [ActivitiesService], 9 | imports: [PrismaModule], 10 | }) 11 | export class ActivitiesModule {} 12 | -------------------------------------------------------------------------------- /src/auth/google/google-auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { JwtAuthModule } from '../jwt/jwt-auth.module'; 3 | import { GoogleOauthController } from './google-auth.controller'; 4 | import { GoogleOauthStrategy } from './google-auth.strategy'; 5 | 6 | @Module({ 7 | imports: [JwtAuthModule], 8 | controllers: [GoogleOauthController], 9 | providers: [GoogleOauthStrategy], 10 | }) 11 | export class GoogleOauthModule {} 12 | -------------------------------------------------------------------------------- /src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { PassportModule } from '@nestjs/passport'; 3 | import { JwtAuthModule } from './jwt/jwt-auth.module'; 4 | import { AuthController } from './auth.controller'; 5 | import { PrismaModule } from 'src/prisma/prisma.module'; 6 | import { GoogleOauthModule } from './google/google-auth.module'; 7 | 8 | @Module({ 9 | controllers: [AuthController], 10 | imports: [PassportModule, JwtAuthModule, PrismaModule, GoogleOauthModule], 11 | }) 12 | export class AuthModule {} 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | pnpm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # OS 15 | .DS_Store 16 | 17 | # Tests 18 | /coverage 19 | /.nyc_output 20 | 21 | # IDEs and editors 22 | /.idea 23 | .project 24 | .classpath 25 | .c9/ 26 | *.launch 27 | .settings/ 28 | *.sublime-workspace 29 | 30 | # IDE - VSCode 31 | .vscode/* 32 | !.vscode/settings.json 33 | !.vscode/tasks.json 34 | !.vscode/launch.json 35 | !.vscode/extensions.json 36 | 37 | .env -------------------------------------------------------------------------------- /src/prisma/prisma.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { PrismaService } from './prisma.service'; 3 | 4 | describe('PrismaService', () => { 5 | let service: PrismaService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [PrismaService], 10 | }).compile(); 11 | 12 | service = module.get(PrismaService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/activities/activities.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ActivitiesService } from './activities.service'; 3 | 4 | describe('ActivitiesService', () => { 5 | let service: ActivitiesService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ActivitiesService], 10 | }).compile(); 11 | 12 | service = module.get(ActivitiesService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /prisma/migrations/20220925203103_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "Role" AS ENUM ('FACULTY', 'MERIT_COMMITTEE_MEMBER', 'MERIT_COMMITTEE_HEAD', 'DEAN'); 3 | 4 | -- CreateTable 5 | CREATE TABLE "User" ( 6 | "id" SERIAL NOT NULL, 7 | "email" TEXT NOT NULL, 8 | "first_name" TEXT NOT NULL, 9 | "last_name" TEXT NOT NULL, 10 | "preferred_name" TEXT NOT NULL, 11 | "title" TEXT NOT NULL, 12 | "role" "Role" NOT NULL DEFAULT 'FACULTY', 13 | 14 | CONSTRAINT "User_pkey" PRIMARY KEY ("id") 15 | ); 16 | 17 | -- CreateIndex 18 | CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "es2017", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": true, 16 | "noImplicitAny": true, 17 | "strictBindCallApply": true, 18 | "forceConsistentCasingInFileNames": true, 19 | "noFallthroughCasesInSwitch": true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/activities/dto/create-activity.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger'; 2 | import { ActivityCategory, SignificanceLevel, Semester} from '@prisma/client'; 3 | 4 | export class CreateActivityDto { 5 | @ApiProperty() 6 | year: number; 7 | 8 | @ApiProperty() 9 | semester: Semester; 10 | 11 | @ApiProperty() 12 | dateModified: Date; 13 | 14 | @ApiProperty() 15 | name: string; 16 | 17 | @ApiProperty() 18 | description: string; 19 | 20 | @ApiProperty() 21 | category: ActivityCategory; 22 | 23 | @ApiProperty() 24 | significance: SignificanceLevel; 25 | 26 | @ApiProperty() 27 | isFavorite: boolean; 28 | } 29 | -------------------------------------------------------------------------------- /src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | import { PrismaModule } from './prisma/prisma.module'; 5 | import { ActivitiesModule } from './activities/activities.module'; 6 | import { ConfigModule } from '@nestjs/config'; 7 | import { AuthModule } from './auth/auth.module'; 8 | 9 | @Module({ 10 | imports: [ 11 | PrismaModule, 12 | ActivitiesModule, 13 | AuthModule, 14 | ConfigModule.forRoot({ isGlobal: true }), 15 | ], 16 | controllers: [AppController], 17 | providers: [AppService], 18 | }) 19 | export class AppModule {} 20 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; 4 | import * as cookieParser from 'cookie-parser'; 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule); 8 | app.use(cookieParser()); 9 | 10 | const config = new DocumentBuilder() 11 | .setTitle('Faculty Event Tracker') 12 | .setDescription('The FAT API description') 13 | .setVersion('0.1') 14 | .build(); 15 | 16 | const document = SwaggerModule.createDocument(app, config); 17 | SwaggerModule.setup('api', app, document); 18 | 19 | await app.listen(3001); 20 | } 21 | bootstrap(); 22 | -------------------------------------------------------------------------------- /src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /src/activities/activities.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ActivitiesController } from './activities.controller'; 3 | import { ActivitiesService } from './activities.service'; 4 | 5 | describe('ActivitiesController', () => { 6 | let controller: ActivitiesController; 7 | 8 | beforeEach(async () => { 9 | const module: TestingModule = await Test.createTestingModule({ 10 | controllers: [ActivitiesController], 11 | providers: [ActivitiesService], 12 | }).compile(); 13 | 14 | controller = module.get(ActivitiesController); 15 | }); 16 | 17 | it('should be defined', () => { 18 | expect(controller).toBeDefined(); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Req, UseGuards } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | import { PrismaClient } from '@prisma/client'; 5 | import { JwtAuthGuard } from './auth/jwt/jwt-auth.guard'; 6 | 7 | const prisma = new PrismaClient(); 8 | 9 | @Controller() 10 | export class AppController { 11 | constructor(private readonly appService: AppService) {} 12 | 13 | @Get() 14 | getHello(): string { 15 | return this.appService.getHello(); 16 | } 17 | 18 | @Get('protected') 19 | @UseGuards(JwtAuthGuard) 20 | async protected(@Req() req: any) { 21 | console.log('User', req.user); 22 | return 'Protected route! Your email is: ' + req.user.email; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { AppModule } from './../src/app.module'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeEach(async () => { 10 | const moduleFixture: TestingModule = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/ (GET)', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir : __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | ignorePatterns: ['.eslintrc.js'], 19 | rules: { 20 | '@typescript-eslint/interface-name-prefix': 'off', 21 | '@typescript-eslint/explicit-function-return-type': 'off', 22 | '@typescript-eslint/explicit-module-boundary-types': 'off', 23 | '@typescript-eslint/no-explicit-any': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/auth/jwt/jwt-auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigService } from '@nestjs/config'; 3 | import { JwtModule } from '@nestjs/jwt'; 4 | import { PrismaModule } from 'src/prisma/prisma.module'; 5 | import { JwtAuthService } from './jwt-auth.service'; 6 | import { JwtAuthStrategy } from './jwt-auth.strategy'; 7 | 8 | @Module({ 9 | imports: [ 10 | JwtModule.registerAsync({ 11 | useFactory: async (configService: ConfigService) => { 12 | return { 13 | secret: configService.get('JWT_SECRET'), 14 | signOptions: { 15 | expiresIn: configService.get('JWT_EXPIRES_IN'), 16 | }, 17 | }; 18 | }, 19 | inject: [ConfigService], 20 | }), 21 | PrismaModule, 22 | ], 23 | providers: [JwtAuthStrategy, JwtAuthService], 24 | exports: [JwtModule, JwtAuthService], 25 | }) 26 | export class JwtAuthModule {} 27 | -------------------------------------------------------------------------------- /prisma/migrations/20221002192910_init/migration.sql: -------------------------------------------------------------------------------- 1 | /* 2 | Warnings: 3 | 4 | - You are about to drop the column `first_name` on the `User` table. All the data in the column will be lost. 5 | - You are about to drop the column `last_name` on the `User` table. All the data in the column will be lost. 6 | - You are about to drop the column `preferred_name` on the `User` table. All the data in the column will be lost. 7 | - Added the required column `firstName` to the `User` table without a default value. This is not possible if the table is not empty. 8 | - Added the required column `lastName` to the `User` table without a default value. This is not possible if the table is not empty. 9 | 10 | */ 11 | -- AlterTable 12 | ALTER TABLE "User" DROP COLUMN "first_name", 13 | DROP COLUMN "last_name", 14 | DROP COLUMN "preferred_name", 15 | ADD COLUMN "firstName" TEXT NOT NULL, 16 | ADD COLUMN "lastName" TEXT NOT NULL, 17 | ADD COLUMN "preferredName" TEXT; 18 | -------------------------------------------------------------------------------- /src/auth/google/google-auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common'; 2 | import { ApiTags } from '@nestjs/swagger'; 3 | import { Request, Response } from 'express'; 4 | import { JwtAuthService } from '../jwt/jwt-auth.service'; 5 | import { GoogleOauthGuard } from './google-auth.guard'; 6 | 7 | @Controller('auth/google') 8 | @ApiTags('google-oauth') 9 | export class GoogleOauthController { 10 | constructor(private jwtAuthService: JwtAuthService) {} 11 | 12 | @Get() 13 | @UseGuards(GoogleOauthGuard) 14 | async googleAuth(@Req() _req: Request) { 15 | // Guard redirects 16 | } 17 | 18 | @Get('callback') 19 | @UseGuards(GoogleOauthGuard) 20 | async googleAuthRedirect(@Req() req: any, @Res() res: Response) { 21 | const { accessToken } = await this.jwtAuthService.signIn(req.user); 22 | res.cookie('access_token', accessToken, { 23 | httpOnly: true, 24 | sameSite: 'lax', 25 | }); 26 | return res.redirect('/protected'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/auth/google/google-auth.strategy.ts: -------------------------------------------------------------------------------- 1 | import { PassportStrategy } from '@nestjs/passport'; 2 | import { Strategy, VerifyCallback } from 'passport-google-oauth20'; 3 | import { Injectable } from '@nestjs/common'; 4 | import { ConfigService } from '@nestjs/config'; 5 | 6 | @Injectable() 7 | export class GoogleOauthStrategy extends PassportStrategy(Strategy, 'google') { 8 | constructor(configService: ConfigService) { 9 | super({ 10 | clientID: configService.get('OAUTH_GOOGLE_ID'), 11 | clientSecret: configService.get('OAUTH_GOOGLE_SECRET'), 12 | callbackURL: configService.get('OAUTH_GOOGLE_REDIRECT_URL'), 13 | scope: ['email', 'profile'], 14 | }); 15 | } 16 | 17 | async validate( 18 | _accessToken: string, 19 | _refreshToken: string, 20 | profile: any, 21 | done: VerifyCallback, 22 | ): Promise { 23 | const { id, name, emails } = profile; 24 | 25 | const user = { 26 | provider: 'google', 27 | providerId: id, 28 | email: emails[0].value, 29 | firstName: name.givenName, 30 | lastName: name.familyName, 31 | }; 32 | 33 | done(null, user); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/auth/jwt/jwt-auth.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Strategy } from 'passport-jwt'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 4 | import { ConfigService } from '@nestjs/config'; 5 | import { Request } from 'express'; 6 | import { PrismaService } from 'src/prisma/prisma.service'; 7 | import { JwtPayload } from 'src/types'; 8 | 9 | @Injectable() 10 | export class JwtAuthStrategy extends PassportStrategy(Strategy) { 11 | constructor(configService: ConfigService, private prisma: PrismaService) { 12 | const extractJwtFromCookie = (req: Request) => { 13 | let token = null; 14 | 15 | if (req && req.cookies) { 16 | token = req.cookies['access_token']; 17 | } 18 | return token; 19 | }; 20 | 21 | super({ 22 | jwtFromRequest: extractJwtFromCookie, 23 | ignoreExpiration: false, 24 | secretOrKey: configService.get('JWT_SECRET'), 25 | }); 26 | } 27 | 28 | async validate(payload: JwtPayload) { 29 | const user = await this.prisma.user.findFirst({ 30 | where: { id: payload.sub }, 31 | }); 32 | 33 | if (!user) throw new UnauthorizedException('Please log in to continue'); 34 | 35 | return payload; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/auth/jwt/jwt-auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, BadRequestException } from '@nestjs/common'; 2 | import { JwtService } from '@nestjs/jwt'; 3 | import { PrismaService } from 'src/prisma/prisma.service'; 4 | import { RegisterUserDto } from '../dto/register-user.dto'; 5 | 6 | @Injectable() 7 | export class JwtAuthService { 8 | constructor(private jwtService: JwtService, private prisma: PrismaService) {} 9 | 10 | async signIn(userDto: RegisterUserDto) { 11 | if (!userDto) { 12 | throw new BadRequestException('Unauthenticated'); 13 | } 14 | 15 | if(!(userDto.email.split("@")[1] === "husky.neu.edu")) { 16 | throw new BadRequestException('Must use a NEU Google email'); 17 | } 18 | 19 | var user = await this.prisma.user.findFirst({ 20 | where: { email: userDto.email }, 21 | }); 22 | 23 | if (!user) { 24 | user = await this.registerUser(userDto); 25 | } 26 | 27 | const payload = { sub: user.id, email: user.email }; 28 | 29 | return { 30 | accessToken: this.jwtService.sign(payload), 31 | }; 32 | } 33 | 34 | async registerUser(userDto: RegisterUserDto) { 35 | const newUser = await this.prisma.user.create({ 36 | data: { 37 | email: userDto.email, 38 | firstName: userDto.firstName, 39 | lastName: userDto.lastName, 40 | }, 41 | }); 42 | return newUser; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | generator client { 5 | provider = "prisma-client-js" 6 | } 7 | 8 | datasource db { 9 | provider = "postgresql" 10 | url = "postgresql://sandbox:chongus@localhost:5432/fat?schema=public" 11 | } 12 | 13 | model User { 14 | id Int @id @default(autoincrement()) 15 | email String @unique 16 | firstName String 17 | lastName String 18 | preferredName String? 19 | role Role @default(FACULTY) 20 | ProfessorInfo ProfessorInfo? 21 | Activity Activity[] 22 | Narrative Narrative[] 23 | } 24 | 25 | model ProfessorInfo { 26 | id Int @id @default(autoincrement()) 27 | user User @relation(fields: [userId], references: [id]) 28 | userId Int @unique 29 | position String 30 | teachingPercent Float 31 | researchPercent Float 32 | servicePercent Float 33 | sabbatical SabbaticalOption 34 | teachingReleaseExplanation String? 35 | } 36 | 37 | model Activity { 38 | id Int @id @default(autoincrement()) 39 | user User @relation(fields: [userId], references: [id]) 40 | userId Int 41 | year Int 42 | semester Semester 43 | dateModified DateTime 44 | name String 45 | description String 46 | category ActivityCategory 47 | significance SignificanceLevel 48 | isFavorite Boolean 49 | } 50 | 51 | model Narrative { 52 | id Int @id @default(autoincrement()) 53 | user User @relation(fields: [userId], references: [id]) 54 | userId Int 55 | year Int 56 | dateModified DateTime 57 | category NarrativeCategory 58 | text String 59 | } 60 | 61 | enum Role { 62 | FACULTY 63 | MERIT_COMMITTEE_MEMBER 64 | MERIT_COMMITTEE_HEAD 65 | DEAN 66 | } 67 | 68 | enum SabbaticalOption { 69 | NO 70 | SEMESTER 71 | YEAR 72 | } 73 | 74 | enum NarrativeCategory { 75 | SUMMARY 76 | SERVICE 77 | RESEARCH 78 | TEACHING 79 | } 80 | 81 | enum ActivityCategory { 82 | SERVICE 83 | RESEARCH 84 | TEACHING 85 | } 86 | 87 | enum SignificanceLevel { 88 | MAJOR 89 | SIGNIFICANT 90 | MINOR 91 | } 92 | 93 | enum Semester { 94 | FALL 95 | SPRING 96 | SUMMER1 97 | SUMMER2 98 | } -------------------------------------------------------------------------------- /prisma/migrations/20221002182729_init/migration.sql: -------------------------------------------------------------------------------- 1 | -- CreateEnum 2 | CREATE TYPE "SabbaticalOption" AS ENUM ('NO', 'SEMESTER', 'YEAR'); 3 | 4 | -- CreateEnum 5 | CREATE TYPE "NarrativeCategory" AS ENUM ('SUMMARY', 'SERVICE', 'RESEARCH', 'TEACHING'); 6 | 7 | -- CreateEnum 8 | CREATE TYPE "ActivityCategory" AS ENUM ('SERVICE', 'RESEARCH', 'TEACHING'); 9 | 10 | -- CreateEnum 11 | CREATE TYPE "SignificanceLevel" AS ENUM ('MAJOR', 'SIGNIFICANT', 'MINOR'); 12 | 13 | -- CreateTable 14 | CREATE TABLE "ProfessorInfo" ( 15 | "id" SERIAL NOT NULL, 16 | "userId" INTEGER NOT NULL, 17 | "position" TEXT NOT NULL, 18 | "teachingPercent" DOUBLE PRECISION NOT NULL, 19 | "researchPercent" DOUBLE PRECISION NOT NULL, 20 | "servicePercent" DOUBLE PRECISION NOT NULL, 21 | "sabbatical" "SabbaticalOption" NOT NULL, 22 | "teachingReleaseExplanation" TEXT NOT NULL, 23 | 24 | CONSTRAINT "ProfessorInfo_pkey" PRIMARY KEY ("id") 25 | ); 26 | 27 | -- CreateTable 28 | CREATE TABLE "AcademicYear" ( 29 | "id" SERIAL NOT NULL, 30 | "startDate" TIMESTAMP(3) NOT NULL, 31 | "endDate" TIMESTAMP(3) NOT NULL, 32 | 33 | CONSTRAINT "AcademicYear_pkey" PRIMARY KEY ("id") 34 | ); 35 | 36 | -- CreateTable 37 | CREATE TABLE "Activity" ( 38 | "id" SERIAL NOT NULL, 39 | "userId" INTEGER NOT NULL, 40 | "academicYearId" INTEGER NOT NULL, 41 | "date" TIMESTAMP(3) NOT NULL, 42 | "name" TEXT NOT NULL, 43 | "description" TEXT NOT NULL, 44 | "category" "ActivityCategory" NOT NULL, 45 | "significance" "SignificanceLevel" NOT NULL, 46 | "isFavorite" BOOLEAN NOT NULL, 47 | 48 | CONSTRAINT "Activity_pkey" PRIMARY KEY ("id") 49 | ); 50 | 51 | -- CreateTable 52 | CREATE TABLE "Narrative" ( 53 | "id" SERIAL NOT NULL, 54 | "userId" INTEGER NOT NULL, 55 | "academicYearId" INTEGER NOT NULL, 56 | "category" "NarrativeCategory" NOT NULL, 57 | "text" TEXT NOT NULL, 58 | 59 | CONSTRAINT "Narrative_pkey" PRIMARY KEY ("id") 60 | ); 61 | 62 | -- AddForeignKey 63 | ALTER TABLE "ProfessorInfo" ADD CONSTRAINT "ProfessorInfo_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 64 | 65 | -- AddForeignKey 66 | ALTER TABLE "Activity" ADD CONSTRAINT "Activity_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 67 | 68 | -- AddForeignKey 69 | ALTER TABLE "Activity" ADD CONSTRAINT "Activity_academicYearId_fkey" FOREIGN KEY ("academicYearId") REFERENCES "AcademicYear"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 70 | 71 | -- AddForeignKey 72 | ALTER TABLE "Narrative" ADD CONSTRAINT "Narrative_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 73 | 74 | -- AddForeignKey 75 | ALTER TABLE "Narrative" ADD CONSTRAINT "Narrative_academicYearId_fkey" FOREIGN KEY ("academicYearId") REFERENCES "AcademicYear"("id") ON DELETE RESTRICT ON UPDATE CASCADE; 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prof-event-tracker-backend", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "prebuild": "rimraf dist", 10 | "build": "nest build", 11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 12 | "start": "nest start", 13 | "start:dev": "nest start --watch", 14 | "start:debug": "nest start --debug --watch", 15 | "start:prod": "node dist/main", 16 | "dev:db:up": "docker-compose up -d", 17 | "dev:db:down": "docker-compose down", 18 | "migrate": "yarn prisma migrate dev --name init", 19 | "generate": "yarn prisma generate", 20 | "seed": "yarn prisma db seed", 21 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 22 | "test": "jest", 23 | "test:watch": "jest --watch", 24 | "test:cov": "jest --coverage", 25 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 26 | "test:e2e": "jest --config ./test/jest-e2e.json" 27 | }, 28 | "dependencies": { 29 | "@nestjs/common": "^9.0.0", 30 | "@nestjs/config": "^2.2.0", 31 | "@nestjs/core": "^9.0.0", 32 | "@nestjs/jwt": "^9.0.0", 33 | "@nestjs/passport": "^9.0.0", 34 | "@nestjs/platform-express": "^9.0.0", 35 | "@nestjs/swagger": "^6.1.2", 36 | "@prisma/client": "^4.3.1", 37 | "@types/cookie-parser": "^1.4.3", 38 | "@types/passport-google-oauth20": "^2.0.11", 39 | "@types/passport-jwt": "^3.0.7", 40 | "cookie-parser": "^1.4.6", 41 | "passport": "^0.6.0", 42 | "passport-google-oauth20": "^2.0.0", 43 | "passport-jwt": "^4.0.0", 44 | "reflect-metadata": "^0.1.13", 45 | "rimraf": "^3.0.2", 46 | "rxjs": "^7.2.0", 47 | "swagger-ui-express": "^4.5.0" 48 | }, 49 | "devDependencies": { 50 | "@nestjs/cli": "^9.0.0", 51 | "@nestjs/schematics": "^9.0.0", 52 | "@nestjs/testing": "^9.0.0", 53 | "@types/express": "^4.17.13", 54 | "@types/jest": "28.1.8", 55 | "@types/node": "^16.0.0", 56 | "@types/supertest": "^2.0.11", 57 | "@typescript-eslint/eslint-plugin": "^5.0.0", 58 | "@typescript-eslint/parser": "^5.0.0", 59 | "eslint": "^8.0.1", 60 | "eslint-config-prettier": "^8.3.0", 61 | "eslint-plugin-prettier": "^4.0.0", 62 | "jest": "28.1.3", 63 | "prettier": "^2.3.2", 64 | "prisma": "^4.3.1", 65 | "source-map-support": "^0.5.20", 66 | "supertest": "^6.1.3", 67 | "ts-jest": "28.0.8", 68 | "ts-loader": "^9.2.3", 69 | "ts-node": "^10.9.1", 70 | "tsconfig-paths": "4.1.0", 71 | "typescript": "^4.7.4" 72 | }, 73 | "prisma": { 74 | "seed": "ts-node prisma/seed.ts" 75 | }, 76 | "jest": { 77 | "moduleFileExtensions": [ 78 | "js", 79 | "json", 80 | "ts" 81 | ], 82 | "rootDir": "src", 83 | "testRegex": ".*\\.spec\\.ts$", 84 | "transform": { 85 | "^.+\\.(t|j)s$": "ts-jest" 86 | }, 87 | "collectCoverageFrom": [ 88 | "**/*.(t|j)s" 89 | ], 90 | "coverageDirectory": "../coverage", 91 | "testEnvironment": "node" 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/activities/activities.controller.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Controller, 3 | Get, 4 | Post, 5 | Body, 6 | Patch, 7 | Param, 8 | Delete, 9 | Query, 10 | UseGuards, 11 | Req, 12 | } from '@nestjs/common'; 13 | import { ActivitiesService } from './activities.service'; 14 | import { CreateActivityDto } from './dto/create-activity.dto'; 15 | import { UpdateActivityDto } from './dto/update-activity.dto'; 16 | import { ApiTags } from '@nestjs/swagger'; 17 | import { ActivityCategory, SignificanceLevel } from '@prisma/client'; 18 | import { JwtAuthGuard } from 'src/auth/jwt/jwt-auth.guard'; 19 | 20 | @Controller('activities') 21 | @ApiTags('activities') 22 | export class ActivitiesController { 23 | constructor(private readonly activitiesService: ActivitiesService) {} 24 | 25 | /** 26 | * Create an Activity for the user making the request with the given body 27 | */ 28 | @Post() 29 | @UseGuards(JwtAuthGuard) 30 | create(@Req() req: any, @Body() createActivityDto: CreateActivityDto) { 31 | return this.activitiesService.create(req.user.sub, createActivityDto); 32 | } 33 | 34 | /** 35 | * Get a list of all of the activities for a user. The default user is 36 | * the user making the request. If an alternate user is provided, then 37 | * the user making the request must have permissions above FACULTY 38 | * @param userId? alternate user to list all activities for 39 | * @param category? filter for only activities in this category 40 | * @param significance? filter for only activities with this 41 | */ 42 | @Get('all') 43 | @UseGuards(JwtAuthGuard) 44 | findAll( 45 | @Req() req: any, 46 | @Query('userId') userId?: string | undefined, 47 | @Query('category') category?: ActivityCategory | undefined, 48 | @Query('significance') significance?: SignificanceLevel | undefined, 49 | ) { 50 | return this.activitiesService.findFilter( 51 | req.user.sub, 52 | userId, 53 | category, 54 | significance, 55 | ); 56 | } 57 | 58 | /** 59 | * Get the Activity with the given ID. User making request must either 60 | * be the user associated with the Activity, or have permissions higher than 61 | * FACULTY 62 | */ 63 | @Get(':id') 64 | @UseGuards(JwtAuthGuard) 65 | findOne(@Req() req: any, @Param('id') id: string) { 66 | return this.activitiesService.findOne(req.user.sub, +id); 67 | } 68 | 69 | /** 70 | * Update the Activity with the given ID using the given body. User making request 71 | * must be the user associated with the Activity. 72 | */ 73 | @Patch(':id') 74 | @UseGuards(JwtAuthGuard) 75 | update( 76 | @Req() req: any, 77 | @Param('id') id: string, 78 | @Body() updateActivityDto: UpdateActivityDto, 79 | ) { 80 | return this.activitiesService.update(req.user.sub, +id, updateActivityDto); 81 | } 82 | 83 | /** 84 | * Delete the Activity with the given ID. User making request 85 | * must be the user associated with the Activity. 86 | */ 87 | @Delete(':id') 88 | @UseGuards(JwtAuthGuard) 89 | remove(@Req() req: any, @Param('id') id: string) { 90 | return this.activitiesService.remove(req.user.sub, +id); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Nest Logo 3 |

4 | 5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 6 | [circleci-url]: https://circleci.com/gh/nestjs/nest 7 | 8 |

A progressive Node.js framework for building efficient and scalable server-side applications.

9 |

10 | NPM Version 11 | Package License 12 | NPM Downloads 13 | CircleCI 14 | Coverage 15 | Discord 16 | Backers on Open Collective 17 | Sponsors on Open Collective 18 | 19 | Support us 20 | 21 |

22 | 24 | 25 | ## Description 26 | 27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. 28 | 29 | ## Installation 30 | 31 | ```bash 32 | $ yarn install 33 | ``` 34 | 35 | ## Running the app 36 | 37 | ```bash 38 | # development 39 | $ yarn run start 40 | 41 | # watch mode 42 | $ yarn run start:dev 43 | 44 | # production mode 45 | $ yarn run start:prod 46 | ``` 47 | 48 | ## Database 49 | ```bash 50 | # start database with docker 51 | $ yarn dev:db:up 52 | 53 | # stop database with docker 54 | $ yarn dev:db:down 55 | ``` 56 | 57 | ## Test 58 | 59 | ```bash 60 | # unit tests 61 | $ yarn run test 62 | 63 | # e2e tests 64 | $ yarn run test:e2e 65 | 66 | # test coverage 67 | $ yarn run test:cov 68 | ``` 69 | 70 | ## Support 71 | 72 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). 73 | 74 | ## Stay in touch 75 | 76 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com) 77 | - Website - [https://nestjs.com](https://nestjs.com/) 78 | - Twitter - [@nestframework](https://twitter.com/nestframework) 79 | 80 | ## License 81 | 82 | Nest is [MIT licensed](LICENSE). 83 | -------------------------------------------------------------------------------- /src/activities/activities.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 2 | import { CreateActivityDto } from './dto/create-activity.dto'; 3 | import { UpdateActivityDto } from './dto/update-activity.dto'; 4 | import { PrismaService } from 'src/prisma/prisma.service'; 5 | import { 6 | Activity, 7 | ActivityCategory, 8 | PrismaPromise, 9 | Role, 10 | SignificanceLevel, 11 | } from '@prisma/client'; 12 | 13 | @Injectable() 14 | export class ActivitiesService { 15 | constructor(private prisma: PrismaService) {} 16 | 17 | create(userId: number, createActivityDto: CreateActivityDto) { 18 | return this.prisma.activity.create({ 19 | data: { ...createActivityDto, userId: userId }, 20 | }); 21 | } 22 | 23 | async findFilter( 24 | userMakingRequestId: number, 25 | userId: string | undefined, 26 | category: ActivityCategory | undefined, 27 | significance: SignificanceLevel | undefined, 28 | ) { 29 | let result: PrismaPromise; 30 | var userToSearchId = userMakingRequestId; 31 | // if a userId query parameter is provided, then it 32 | // must be the same as the user making the request, or the user 33 | // making the request must have admin permissions 34 | if (userId) { 35 | const userMakingRequest = await this.prisma.user.findFirst({ 36 | where: { id: userMakingRequestId }, 37 | }); 38 | 39 | if ( 40 | +userId !== userMakingRequestId && 41 | userMakingRequest && 42 | userMakingRequest.role === Role.FACULTY 43 | ) { 44 | throw new UnauthorizedException( 45 | 'You do not have permission to access these activites', 46 | ); 47 | } 48 | userToSearchId = +userId; 49 | } 50 | 51 | if (!significance) { 52 | result = this.prisma.activity.findMany({ 53 | where: { userId: userToSearchId, category: category }, 54 | }); 55 | } else if (!category) { 56 | result = this.prisma.activity.findMany({ 57 | where: { userId: userToSearchId, significance: significance }, 58 | }); 59 | } else if (!significance && !category) { 60 | result = this.prisma.activity.findMany({ 61 | where: { userId: userToSearchId }, 62 | }); 63 | } else { 64 | result = this.prisma.activity.findMany({ 65 | where: { 66 | userId: userToSearchId, 67 | category: category, 68 | significance: significance, 69 | }, 70 | }); 71 | } 72 | 73 | return result; 74 | } 75 | 76 | async findOne(userId: number, activityId: number) { 77 | const user = await this.prisma.user.findFirst({ where: { id: userId } }); 78 | const activity = await this.prisma.activity.findFirst({ 79 | where: { id: activityId }, 80 | }); 81 | 82 | if ( 83 | user && 84 | activity && 85 | activity.userId !== userId && 86 | user.role === Role.FACULTY 87 | ) { 88 | throw new UnauthorizedException( 89 | 'You do not have access to this activity', 90 | ); 91 | } 92 | 93 | return this.prisma.activity.findFirst({ where: { id: activityId } }); 94 | } 95 | 96 | async update( 97 | userId: number, 98 | activityId: number, 99 | updateActivityDto: UpdateActivityDto, 100 | ) { 101 | const activity = await this.prisma.activity.findFirst({ 102 | where: { id: activityId }, 103 | }); 104 | if (activity && activity.userId !== userId) { 105 | throw new UnauthorizedException( 106 | 'You do not have access to this activity', 107 | ); 108 | } 109 | 110 | return this.prisma.activity.update({ 111 | where: { id: activityId }, 112 | data: updateActivityDto, 113 | }); 114 | } 115 | 116 | async remove(userId: number, activityId: number) { 117 | const activity = await this.prisma.activity.findFirst({ 118 | where: { id: activityId }, 119 | }); 120 | if (activity && activity.userId !== userId) { 121 | throw new UnauthorizedException( 122 | 'You do not have access to this activity', 123 | ); 124 | } 125 | return this.prisma.activity.delete({ where: { id: activityId } }); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /prisma/seed.ts: -------------------------------------------------------------------------------- 1 | // prisma/seed.ts 2 | 3 | import { PrismaClient, SabbaticalOption } from '@prisma/client'; 4 | 5 | // initialize Prisma Client 6 | const prisma = new PrismaClient(); 7 | 8 | async function createUserData() { 9 | const user1 = await prisma.user.upsert({ 10 | where: { email: 'a@b.com' }, 11 | update: {}, 12 | create: { 13 | email: 'a@b.com', 14 | firstName: 'Dave', 15 | lastName: 'Dog', 16 | role: 'FACULTY', 17 | }, 18 | }); 19 | 20 | const user2 = await prisma.user.upsert({ 21 | where: { email: 'a2@b.com' }, 22 | update: {}, 23 | create: { 24 | email: 'a2@b.com', 25 | firstName: 'Bob', 26 | lastName: 'Patel', 27 | role: 'DEAN', 28 | }, 29 | }); 30 | 31 | const user3 = await prisma.user.upsert({ 32 | where: { email: 'a3@b.com' }, 33 | update: {}, 34 | create: { 35 | email: 'a3@b.com', 36 | firstName: 'Roger', 37 | lastName: 'Wow', 38 | preferredName: 'Rob', 39 | role: 'FACULTY', 40 | }, 41 | }); 42 | 43 | const user4 = await prisma.user.upsert({ 44 | where: { email: 'a4@b.com' }, 45 | update: {}, 46 | create: { 47 | email: 'a4@b.com', 48 | firstName: 'Mark', 49 | lastName: 'Sivak', 50 | preferredName: 'Mark', 51 | role: 'MERIT_COMMITTEE_HEAD', 52 | }, 53 | }); 54 | 55 | const user5 = await prisma.user.upsert({ 56 | where: { email: 'a5@b.com' }, 57 | update: {}, 58 | create: { 59 | email: 'a5@b.com', 60 | firstName: 'Diego', 61 | lastName: 'Hernandez', 62 | role: 'MERIT_COMMITTEE_MEMBER', 63 | }, 64 | }); 65 | 66 | const user6 = await prisma.user.upsert({ 67 | where: { email: 'a6@b.com' }, 68 | update: {}, 69 | create: { 70 | email: 'a6@b.com', 71 | firstName: 'Ben', 72 | lastName: 'Lerner', 73 | preferredName: 'Blerner', 74 | role: 'FACULTY', 75 | }, 76 | }); 77 | 78 | const user7 = await prisma.user.upsert({ 79 | where: { email: 'a7@b.com' }, 80 | update: {}, 81 | create: { 82 | email: 'a7@b.com', 83 | firstName: 'Christina', 84 | lastName: 'Long', 85 | preferredName: 'Hatsune Miku', 86 | role: 'MERIT_COMMITTEE_MEMBER', 87 | }, 88 | }); 89 | 90 | const user8 = await prisma.user.upsert({ 91 | where: { email: 'a8@b.com' }, 92 | update: {}, 93 | create: { 94 | email: 'a8@b.com', 95 | firstName: 'Jeffery', 96 | lastName: 'Hopkins', 97 | preferredName: 'Jeff', 98 | role: 'FACULTY', 99 | }, 100 | }); 101 | 102 | const user9 = await prisma.user.upsert({ 103 | where: { email: 'a9@b.com' }, 104 | update: {}, 105 | create: { 106 | email: 'a9@b.com', 107 | firstName: 'John', 108 | lastName: 'Appleseed', 109 | role: 'FACULTY', 110 | }, 111 | }); 112 | } 113 | 114 | async function createActivityData() { 115 | const activity1 = await prisma.activity.upsert({ 116 | where: { id: 1 }, 117 | update: {}, 118 | create: { 119 | user: { 120 | connect: { 121 | email: 'a@b.com', 122 | }, 123 | }, 124 | year: 2023, 125 | semester: 'FALL', 126 | dateModified: new Date('2023-10-20T21:23:57.736Z'), 127 | name: 'Taught Course', 128 | description: 129 | 'Led the the course intro to design, for over for 200 students', 130 | category: 'TEACHING', 131 | significance: 'MAJOR', 132 | isFavorite: false, 133 | }, 134 | }); 135 | 136 | const activity2 = await prisma.activity.upsert({ 137 | where: { id: activity1.id + 1 }, 138 | update: {}, 139 | create: { 140 | user: { 141 | connect: { 142 | email: 'a3@b.com', 143 | }, 144 | }, 145 | year: 2020, 146 | semester: 'FALL', 147 | dateModified: new Date('2020-11-20T21:23:57.736Z'), 148 | name: 'Client Project', 149 | description: 'completed the client design project for Autodesk', 150 | category: 'SERVICE', 151 | significance: 'SIGNIFICANT', 152 | isFavorite: false, 153 | }, 154 | }); 155 | 156 | const activity3 = await prisma.activity.upsert({ 157 | where: { id: activity2.id + 1 }, 158 | update: {}, 159 | create: { 160 | user: { 161 | connect: { 162 | email: 'a6@b.com', 163 | }, 164 | }, 165 | year: 2022, 166 | semester: 'FALL', 167 | dateModified: new Date('2022-11-20T21:23:57.736Z'), 168 | name: 'Directed Study', 169 | description: 'Animation simulation using Houdini', 170 | category: 'RESEARCH', 171 | significance: 'MINOR', 172 | isFavorite: false, 173 | }, 174 | }); 175 | 176 | const activity4 = await prisma.activity.upsert({ 177 | where: { id: activity3.id + 1 }, 178 | update: {}, 179 | create: { 180 | user: { 181 | connect: { 182 | email: 'a8@b.com', 183 | }, 184 | }, 185 | year: 2023, 186 | semester:'SUMMER2', 187 | dateModified: new Date('2022-08-20T21:23:57.736Z'), 188 | name: 'New Course', 189 | description: 'ARTG 5240', 190 | category: 'TEACHING', 191 | significance: 'MAJOR', 192 | isFavorite: true, 193 | }, 194 | }); 195 | 196 | 197 | const activity5 = await prisma.activity.upsert({ 198 | where: { id: activity4.id + 1 }, 199 | update: {}, 200 | create: { 201 | user: { 202 | connect: { 203 | email: 'a@b.com', 204 | }, 205 | }, 206 | year: 2022, 207 | semester: 'FALL', 208 | dateModified: new Date('2022-10-20T21:23:57.736Z'), 209 | name: 'Field Trip', 210 | description: 'led a field trip to the MFA', 211 | category: 'TEACHING', 212 | significance: 'SIGNIFICANT', 213 | isFavorite: false, 214 | }, 215 | }); 216 | 217 | const activity6 = await prisma.activity.upsert({ 218 | where: { id: activity5.id + 1 }, 219 | update: {}, 220 | create: { 221 | user: { 222 | connect: { 223 | email: 'a8@b.com', 224 | }, 225 | }, 226 | year: 2021, 227 | semester: 'SPRING', 228 | dateModified: new Date('2022-10-20T21:23:57.736Z'), 229 | name: 'Led a minor teaching activity', 230 | description: 'something that I will edit later', 231 | category: 'TEACHING', 232 | significance: 'MINOR', 233 | isFavorite: false, 234 | }, 235 | }); 236 | 237 | const activity7 = await prisma.activity.upsert({ 238 | where: { id: activity6.id + 1 }, 239 | update: {}, 240 | create: { 241 | user: { 242 | connect: { 243 | email: 'a8@b.com', 244 | }, 245 | }, 246 | year: 2022, 247 | semester: 'SUMMER1', 248 | dateModified: new Date('2022-10-20T21:23:57.736Z'), 249 | name: 'Cure cancer', 250 | description: 'where is my money?', 251 | category: 'RESEARCH', 252 | significance: 'SIGNIFICANT', 253 | isFavorite: true, 254 | }, 255 | }); 256 | } 257 | 258 | async function createProfessorInfoData() { 259 | const info1 = await prisma.professorInfo.upsert({ 260 | where: { id: 1 }, 261 | update: {}, 262 | create: { 263 | user: { 264 | connect: { 265 | email: 'a@b.com', 266 | }, 267 | }, 268 | position: 'Tenured Faculty', 269 | teachingPercent: 0.4, 270 | researchPercent: 0.4, 271 | servicePercent: 0.2, 272 | sabbatical: SabbaticalOption.NO, 273 | }, 274 | }); 275 | 276 | const info2 = await prisma.professorInfo.upsert({ 277 | where: { id: 2 }, 278 | update: {}, 279 | create: { 280 | user: { 281 | connect: { 282 | email: 'a2@b.com', 283 | }, 284 | }, 285 | position: 'Tenure Track Faculty', 286 | teachingPercent: 0.4, 287 | researchPercent: 0.5, 288 | servicePercent: 0.1, 289 | sabbatical: SabbaticalOption.SEMESTER, 290 | }, 291 | }); 292 | 293 | const info3 = await prisma.professorInfo.upsert({ 294 | where: { id: 3 }, 295 | update: {}, 296 | create: { 297 | user: { 298 | connect: { 299 | email: 'a3@b.com', 300 | }, 301 | }, 302 | position: 'Teaching Faculty', 303 | teachingPercent: 0.8, 304 | researchPercent: 0.1, 305 | servicePercent: 0.1, 306 | sabbatical: SabbaticalOption.YEAR, 307 | }, 308 | }); 309 | 310 | const info4 = await prisma.professorInfo.upsert({ 311 | where: { id: 4 }, 312 | update: {}, 313 | create: { 314 | user: { 315 | connect: { 316 | email: 'a4@b.com', 317 | }, 318 | }, 319 | position: 'Tenure Track Faculty', 320 | teachingPercent: 0.4, 321 | researchPercent: 0.5, 322 | servicePercent: 0.1, 323 | sabbatical: SabbaticalOption.NO, 324 | teachingReleaseExplanation: 325 | 'Went on teaching release for Fall semester because of maternity leave', 326 | }, 327 | }); 328 | } 329 | 330 | async function main() { 331 | await createUserData(); 332 | await createActivityData(); 333 | await createProfessorInfoData(); 334 | } 335 | 336 | // execute the main function 337 | main() 338 | .catch((e) => { 339 | console.error(e); 340 | process.exit(1); 341 | }) 342 | .finally(async () => { 343 | // close Prisma Client at the end 344 | await prisma.$disconnect(); 345 | }); 346 | 347 | // code from: https://www.prisma.io/blog/nestjs-prisma-rest-api-7D056s1BmOL0#seed-the-database 348 | --------------------------------------------------------------------------------