Skip to content

Commit

Permalink
Merge pull request #15 from richard483/feature/job-crud
Browse files Browse the repository at this point in the history
Feature/job crud
  • Loading branch information
richard483 authored Oct 30, 2023
2 parents 525a2b8 + 93d28b6 commit 8dfadd6
Show file tree
Hide file tree
Showing 25 changed files with 2,570 additions and 1,208 deletions.
2,762 changes: 1,558 additions & 1,204 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
"author": "8tech",
"private": true,
"license": "UNLICENSED",
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"init:db": "docker compose up -d && npx prisma generate && npx prisma migrate dev && npx prisma db seed",
"start": "node dist/main",
"dev": "nest start",
"start:dev": "nest start --watch",
Expand All @@ -21,7 +25,6 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@golevelup/ts-jest": "^0.3.7",
"@nestjs/common": "^9.0.0",
"@nestjs/config": "^3.0.0",
"@nestjs/core": "^9.0.0",
Expand All @@ -42,6 +45,7 @@
"webpack": "^5.87.0"
},
"devDependencies": {
"@golevelup/ts-jest": "^0.3.7",
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.4.3",
Expand All @@ -59,8 +63,8 @@
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "29.5.0",
"prettier": "^2.3.2",
"prisma": "^5.0.0",
"prettier": "^2.8.8",
"prisma": "^5.4.1",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "29.1.0",
Expand Down
47 changes: 47 additions & 0 deletions prisma/migrations/20231008125414_/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "hasGoogleAccount" BOOLEAN NOT NULL DEFAULT false,
ALTER COLUMN "password" DROP NOT NULL,
ALTER COLUMN "roles" SET DEFAULT ARRAY['USER']::"Role"[];

-- CreateTable
CREATE TABLE "JobVacancy" (
"id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"companyId" TEXT NOT NULL,

CONSTRAINT "JobVacancy_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Company" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "Company_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Contract" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"jobId" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL,
"template" VARCHAR NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "Contract_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "JobVacancy" ADD CONSTRAINT "JobVacancy_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "Contract" ADD CONSTRAINT "Contract_jobId_fkey" FOREIGN KEY ("jobId") REFERENCES "JobVacancy"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
-- DropForeignKey
ALTER TABLE "JobVacancy" DROP CONSTRAINT "JobVacancy_companyId_fkey";

-- AlterTable
ALTER TABLE "JobVacancy" ALTER COLUMN "companyId" DROP NOT NULL;

-- AddForeignKey
ALTER TABLE "JobVacancy" ADD CONSTRAINT "JobVacancy_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE SET NULL ON UPDATE CASCADE;
34 changes: 34 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ generator client {
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
// comment the directUrl for development in windows
directUrl = env("DIRECT_URL")
}

Expand All @@ -23,6 +24,39 @@ model User {
hasGoogleAccount Boolean @default(false)
}

model JobVacancy {
id String @id @default(uuid())
title String
description String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// company Id is optional for current development purposes
company Company? @relation(fields: [companyId], references: [id])
companyId String?
contracts Contract[]
}

model Company {
id String @id @default(uuid())
name String
description String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
jobs JobVacancy[]
}

model Contract {
id String @id @default(uuid())
userId String
jobId String
title String
description String
template String @db.VarChar()
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
JobVacancy JobVacancy? @relation(fields: [jobId], references: [id])
}

enum Role {
ADMIN
USER
Expand Down
44 changes: 44 additions & 0 deletions prisma/seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { PrismaClient } from '@prisma/client';
import { genSaltSync, hashSync } from 'bcrypt';

const prisma = new PrismaClient();

function hashPassword(password: string) {
const salt = genSaltSync(10);
const hashedPassword = hashSync(password, salt);
return hashedPassword;
}
async function main() {
const admin = await prisma.user.upsert({
where: { email: 'admin@email.com' },
update: {},
create: {
email: 'admin@email.com',
userName: 'Admin',
password: hashPassword('Admin123_'),
roles: ['ADMIN', 'USER'],
},
});

const defaultUser = await prisma.user.upsert({
where: { email: 'default.user@email.com' },
update: {},
create: {
email: 'default.user@email.com',
userName: 'Default User',
password: hashPassword('User123_'),
roles: ['USER'],
},
});
console.log({ admin, defaultUser });
}

main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
3 changes: 2 additions & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { UsersModule } from './users/users.module';
import { ConfigModule } from '@nestjs/config';
import { JobModule } from './job/job.module';

@Module({
imports: [ConfigModule.forRoot(), AuthModule, UsersModule],
imports: [ConfigModule.forRoot(), AuthModule, UsersModule, JobModule],
controllers: [AppController],
providers: [AppService],
})
Expand Down
2 changes: 2 additions & 0 deletions src/auth/jwt/jwt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export class JwtStrategy extends PassportStrategy(Strategy) {

private static extractJwtFromCookie(req) {
let token = null;
// TODO: need fix here to return unauthorized if no cookie or token
// the bug can be reproduced by sending request without login first (tried on createjob)
req.headers.cookie.split(';').forEach((element) => {
element = element.split('=');
element[0].trim() === 'EToken' ? (token = element[1].trim()) : null;
Expand Down
34 changes: 34 additions & 0 deletions src/contract/contract.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
Body,
Controller,
HttpStatus,
Post,
Res,
UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { Roles } from '../auth/roles/role.decorator';
import { JwtAuthGuard } from '../auth/jwt/jwt-auth.guard';
import { RoleGuard } from '../auth/roles/role.guard';
import { Role } from '../auth/roles/role.enum';
import { ContractService } from './contract.service';
import { ContractCreateDto } from './dto/contract-create.dto';

@ApiTags('Contract')
@Controller('contract')
export class ContractController {
constructor(private contractService: ContractService) {}

@ApiBearerAuth()
@Roles(Role.USER)
@UseGuards(JwtAuthGuard, RoleGuard)
@Post('create')
async createContract(@Res() res, @Body() contract: ContractCreateDto) {
try {
const response = await this.contractService.create(contract);
return res.status(HttpStatus.OK).json({ response });
} catch (error) {
return res.status(error.status).json({ error: error.message });
}
}
}
12 changes: 12 additions & 0 deletions src/contract/contract.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { ContractService } from './contract.service';
import { ContractRepository } from './contract.repository';
import { ContractController } from './contract.controller';

@Module({
providers: [ContractService, PrismaService, ContractRepository],
exports: [ContractService],
controllers: [ContractController],
})
export class ContractModule {}
17 changes: 17 additions & 0 deletions src/contract/contract.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';

@Injectable()
export class ContractRepository {
public model;

constructor(private prisma: PrismaService) {
this.model = this.prisma.contract;
}

async create(contract: any): Promise<any> {
return this.prisma.contract.create({
data: contract,
});
}
}
12 changes: 12 additions & 0 deletions src/contract/contract.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Injectable } from '@nestjs/common';
import { ContractRepository } from './contract.repository';
import { IContract } from './interface/contract.interface';

@Injectable()
export class ContractService {
constructor(private contractRepository: ContractRepository) {}

async create(user: any): Promise<IContract> {
return this.contractRepository.create(user);
}
}
29 changes: 29 additions & 0 deletions src/contract/dto/contract-create.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';

export class ContractCreateDto {
@ApiProperty()
@IsNotEmpty()
@IsString()
readonly userId: string;

@ApiProperty()
@IsNotEmpty()
@IsString()
readonly jobId: string;

@ApiProperty()
@IsNotEmpty()
@IsString()
readonly title: string;

@ApiProperty()
@IsNotEmpty()
@IsString()
readonly description: string;

@ApiProperty()
@IsNotEmpty()
@IsString()
readonly template: string;
}
10 changes: 10 additions & 0 deletions src/contract/interface/contract.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export interface IContract {
id: string;
userId: string;
jobId: string;
title: string;
description: string;
template: string;
createdAt: Date;
updatedAt: Date;
}
Loading

0 comments on commit 8dfadd6

Please sign in to comment.