First commit
Some checks failed
Main / changes (push) Successful in 34s
Main / deploy (push) Has been cancelled
Main / build-and-test (push) Has been cancelled

This commit is contained in:
2026-07-17 12:28:28 -05:00
commit 9d9cdbaebc
183 changed files with 9015 additions and 0 deletions

25
api/.eslintrc.js Normal file
View File

@@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

58
api/.gitignore vendored Normal file
View File

@@ -0,0 +1,58 @@
# compiled output
/dist
/node_modules
/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
local.settings.json

4
api/.prettierrc Normal file
View File

@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

0
api/README.md Normal file
View File

8
api/nest-cli.json Normal file
View File

@@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

87
api/package.json Normal file
View File

@@ -0,0 +1,87 @@
{
"name": "api",
"version": "1.2.0",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"typeorm": "npm run build && npx typeorm -d dist/database/data-source.js",
"migration:generate": "npm run typeorm -- migration:generate",
"migration:run": "npm run typeorm -- migration:run",
"migration:revert": "npm run typeorm -- migration:revert",
"migration:show": "npm run typeorm -- migration:show",
"seed": "npm run build && typeorm-extension seed:run -d ./database/data-source.ts"
},
"dependencies": {
"@nestjs/axios": "^4.0.0",
"@nestjs/common": "^11.0.11",
"@nestjs/config": "^4.0.1",
"@nestjs/core": "^11.0.11",
"@nestjs/platform-express": "^11.0.11",
"@nestjs/typeorm": "^11.0.1",
"@noahspan/noahspan-modules": "^1.2.11",
"axios": "^1.7.9",
"better-sqlite3": "^12.2.0",
"reflect-metadata": "^0.2.2",
"rxjs": "7.8.2",
"uuid": "^11.1.1"
},
"devDependencies": {
"@angular-devkit/schematics": "^19.1.4",
"@nestjs/cli": "^11.0.5",
"@nestjs/schematics": "^11.0.2",
"@nestjs/testing": "^11.0.11",
"@schematics/angular": "^19.1.4",
"@types/express": "^4.17.17",
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"prettier": "^3.0.0",
"source-map-support": "^0.5.21",
"supertest": "^6.3.3",
"ts-jest": "^29.1.0",
"ts-loader": "^9.4.3",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.3"
},
"files": [
"dist"
],
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});

56
api/src/app.module.ts Normal file
View File

@@ -0,0 +1,56 @@
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
import { HttpExceptionFilter } from './filters/http-exception.filter';
import { ProjectModule } from './project/project.module';
import { AuthModule, MsGraphModule } from '@noahspan/noahspan-modules'
import { ConfigModule, ConfigService } from '@nestjs/config';
import configuration from './config/configuration';
import { TypeOrmModule } from '@nestjs/typeorm';
import { dataSourceOptions } from './database/data-source';
import { SkillModule } from './skill/skill.module';
import { ProfileModule } from './profile/profile.module';
import { ExperienceModule } from './experience/experience.module';
@Module({
imports: [
AuthModule.registerAsync({
inject: [ConfigService],
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
return {
audience: configService.get<string>('audience'),
issuerUrl: configService.get<string>('issuer'),
jwksUri: configService.get<string>('jwksUri')
}
}
}),
ConfigModule.forRoot({
isGlobal: true,
load: [configuration]
}),
ExperienceModule,
MsGraphModule.registerAsync({
inject: [ConfigService],
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => {
return {
authority: configService.get<string>('authority'),
clientId: configService.get<string>('clientId'),
clientSecret: configService.get<string>('clientSecret'),
tenantId: configService.get<string>('tenantId')
}
}
}),
ProfileModule,
ProjectModule,
SkillModule,
TypeOrmModule.forRoot(dataSourceOptions),
],
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter
}
]
})
export class AppModule {}

View File

@@ -0,0 +1,10 @@
export default () => ({
azureStorageConnectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
audience: process.env.AUDIENCE,
authority: process.env.AUTHORITY,
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
issuer: process.env.ISSUER_URL,
jwksUri: process.env.JWKS_URI,
tenantId: process.env.TENANT_ID
})

View File

@@ -0,0 +1,22 @@
import { DataSource, DataSourceOptions } from 'typeorm';
import { config } from 'dotenv';
import { ConfigService } from '@nestjs/config';
import { SeederOptions } from 'typeorm-extension';
config();
const configService: ConfigService = new ConfigService();
export const dataSourceOptions: DataSourceOptions & SeederOptions = {
type: 'better-sqlite3',
database: configService.get<string>('DB_PATH'),
entities: ['../**/*.entity.js'],
migrations: ['dist/database/migrations/**/*.js'],
migrationsRun: false,
seeds: ['dist/database/seeds/**/*.ts'],
synchronize: configService.get<boolean>('DB_SYNC')
}
const dataSource: DataSource = new DataSource(dataSourceOptions);
export default dataSource;

View File

@@ -0,0 +1,60 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class InitialMigration1782738482901 implements MigrationInterface {
name = 'InitialMigration1782738482901'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "statuses" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL)`);
await queryRunner.query(`CREATE TABLE "skill_categories" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar)`);
await queryRunner.query(`CREATE TABLE "skill_levels" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar)`);
await queryRunner.query(`CREATE TABLE "skill_ranks" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "weight" integer NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar)`);
await queryRunner.query(`CREATE TABLE "skills" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "categoryId" varchar NOT NULL, "levelId" integer NOT NULL, "rankId" integer NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar, CONSTRAINT "REL_168133e3158020b53392549d3e" UNIQUE ("rankId"))`);
await queryRunner.query(`CREATE TABLE "projects" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "iconName" varchar NOT NULL, "order" integer NOT NULL, "summary" varchar NOT NULL, "repoUrl" varchar, "siteUrl" varchar, "statusId" integer NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar, CONSTRAINT "REL_5a833dfa646b99b852c67dd859" UNIQUE ("statusId"))`);
await queryRunner.query(`CREATE TABLE "experiences" ("id" varchar PRIMARY KEY NOT NULL, "profileId" varchar NOT NULL, "companyName" varchar NOT NULL, "companyGeneric" varchar NOT NULL, "title" varchar NOT NULL, "startDate" datetime NOT NULL, "endDate" datetime, "summary" varchar, "statusId" integer NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar, CONSTRAINT "REL_23dfcd8f2d1b6585b848f840d5" UNIQUE ("statusId"))`);
await queryRunner.query(`CREATE TABLE "profiles" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "headline" varchar NOT NULL, "summary" varchar NOT NULL, "statusId" integer NOT NULL, "userId" varchar NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar, CONSTRAINT "REL_a0e37cd676d13d116f73d494ee" UNIQUE ("statusId"))`);
await queryRunner.query(`CREATE TABLE "temporary_skills" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "categoryId" varchar NOT NULL, "levelId" integer NOT NULL, "rankId" integer NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar, CONSTRAINT "REL_168133e3158020b53392549d3e" UNIQUE ("rankId"), CONSTRAINT "FK_06d267f85858229c10a01a08ad7" FOREIGN KEY ("categoryId") REFERENCES "skill_categories" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_168133e3158020b53392549d3ee" FOREIGN KEY ("rankId") REFERENCES "skill_ranks" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_cad5a64685c1be599c10bb7fc7b" FOREIGN KEY ("levelId") REFERENCES "skill_levels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
await queryRunner.query(`INSERT INTO "temporary_skills"("id", "name", "categoryId", "levelId", "rankId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy") SELECT "id", "name", "categoryId", "levelId", "rankId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy" FROM "skills"`);
await queryRunner.query(`DROP TABLE "skills"`);
await queryRunner.query(`ALTER TABLE "temporary_skills" RENAME TO "skills"`);
await queryRunner.query(`CREATE TABLE "temporary_projects" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "iconName" varchar NOT NULL, "order" integer NOT NULL, "summary" varchar NOT NULL, "repoUrl" varchar, "siteUrl" varchar, "statusId" integer NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar, CONSTRAINT "REL_5a833dfa646b99b852c67dd859" UNIQUE ("statusId"), CONSTRAINT "FK_5a833dfa646b99b852c67dd8593" FOREIGN KEY ("statusId") REFERENCES "statuses" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
await queryRunner.query(`INSERT INTO "temporary_projects"("id", "name", "iconName", "order", "summary", "repoUrl", "siteUrl", "statusId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy") SELECT "id", "name", "iconName", "order", "summary", "repoUrl", "siteUrl", "statusId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy" FROM "projects"`);
await queryRunner.query(`DROP TABLE "projects"`);
await queryRunner.query(`ALTER TABLE "temporary_projects" RENAME TO "projects"`);
await queryRunner.query(`CREATE TABLE "temporary_experiences" ("id" varchar PRIMARY KEY NOT NULL, "profileId" varchar NOT NULL, "companyName" varchar NOT NULL, "companyGeneric" varchar NOT NULL, "title" varchar NOT NULL, "startDate" datetime NOT NULL, "endDate" datetime, "summary" varchar, "statusId" integer NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar, CONSTRAINT "REL_23dfcd8f2d1b6585b848f840d5" UNIQUE ("statusId"), CONSTRAINT "FK_be01c61f0c549f2187b5c05c349" FOREIGN KEY ("profileId") REFERENCES "profiles" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_23dfcd8f2d1b6585b848f840d5e" FOREIGN KEY ("statusId") REFERENCES "statuses" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
await queryRunner.query(`INSERT INTO "temporary_experiences"("id", "profileId", "companyName", "companyGeneric", "title", "startDate", "endDate", "summary", "statusId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy") SELECT "id", "profileId", "companyName", "companyGeneric", "title", "startDate", "endDate", "summary", "statusId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy" FROM "experiences"`);
await queryRunner.query(`DROP TABLE "experiences"`);
await queryRunner.query(`ALTER TABLE "temporary_experiences" RENAME TO "experiences"`);
await queryRunner.query(`CREATE TABLE "temporary_profiles" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "headline" varchar NOT NULL, "summary" varchar NOT NULL, "statusId" integer NOT NULL, "userId" varchar NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar, CONSTRAINT "REL_a0e37cd676d13d116f73d494ee" UNIQUE ("statusId"), CONSTRAINT "FK_a0e37cd676d13d116f73d494ee6" FOREIGN KEY ("statusId") REFERENCES "statuses" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
await queryRunner.query(`INSERT INTO "temporary_profiles"("id", "name", "headline", "summary", "statusId", "userId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy") SELECT "id", "name", "headline", "summary", "statusId", "userId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy" FROM "profiles"`);
await queryRunner.query(`DROP TABLE "profiles"`);
await queryRunner.query(`ALTER TABLE "temporary_profiles" RENAME TO "profiles"`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "profiles" RENAME TO "temporary_profiles"`);
await queryRunner.query(`CREATE TABLE "profiles" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "headline" varchar NOT NULL, "summary" varchar NOT NULL, "statusId" integer NOT NULL, "userId" varchar NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar, CONSTRAINT "REL_a0e37cd676d13d116f73d494ee" UNIQUE ("statusId"))`);
await queryRunner.query(`INSERT INTO "profiles"("id", "name", "headline", "summary", "statusId", "userId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy") SELECT "id", "name", "headline", "summary", "statusId", "userId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy" FROM "temporary_profiles"`);
await queryRunner.query(`DROP TABLE "temporary_profiles"`);
await queryRunner.query(`ALTER TABLE "experiences" RENAME TO "temporary_experiences"`);
await queryRunner.query(`CREATE TABLE "experiences" ("id" varchar PRIMARY KEY NOT NULL, "profileId" varchar NOT NULL, "companyName" varchar NOT NULL, "companyGeneric" varchar NOT NULL, "title" varchar NOT NULL, "startDate" datetime NOT NULL, "endDate" datetime, "summary" varchar, "statusId" integer NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar, CONSTRAINT "REL_23dfcd8f2d1b6585b848f840d5" UNIQUE ("statusId"))`);
await queryRunner.query(`INSERT INTO "experiences"("id", "profileId", "companyName", "companyGeneric", "title", "startDate", "endDate", "summary", "statusId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy") SELECT "id", "profileId", "companyName", "companyGeneric", "title", "startDate", "endDate", "summary", "statusId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy" FROM "temporary_experiences"`);
await queryRunner.query(`DROP TABLE "temporary_experiences"`);
await queryRunner.query(`ALTER TABLE "projects" RENAME TO "temporary_projects"`);
await queryRunner.query(`CREATE TABLE "projects" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "iconName" varchar NOT NULL, "order" integer NOT NULL, "summary" varchar NOT NULL, "repoUrl" varchar, "siteUrl" varchar, "statusId" integer NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar, CONSTRAINT "REL_5a833dfa646b99b852c67dd859" UNIQUE ("statusId"))`);
await queryRunner.query(`INSERT INTO "projects"("id", "name", "iconName", "order", "summary", "repoUrl", "siteUrl", "statusId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy") SELECT "id", "name", "iconName", "order", "summary", "repoUrl", "siteUrl", "statusId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy" FROM "temporary_projects"`);
await queryRunner.query(`DROP TABLE "temporary_projects"`);
await queryRunner.query(`ALTER TABLE "skills" RENAME TO "temporary_skills"`);
await queryRunner.query(`CREATE TABLE "skills" ("id" varchar PRIMARY KEY NOT NULL, "name" varchar NOT NULL, "categoryId" varchar NOT NULL, "levelId" integer NOT NULL, "rankId" integer NOT NULL, "created" datetime NOT NULL DEFAULT (datetime('now')), "createdBy" varchar, "updated" datetime NOT NULL DEFAULT (datetime('now')), "updatedBy" varchar, "deleted" datetime, "deletedBy" varchar, CONSTRAINT "REL_168133e3158020b53392549d3e" UNIQUE ("rankId"))`);
await queryRunner.query(`INSERT INTO "skills"("id", "name", "categoryId", "levelId", "rankId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy") SELECT "id", "name", "categoryId", "levelId", "rankId", "created", "createdBy", "updated", "updatedBy", "deleted", "deletedBy" FROM "temporary_skills"`);
await queryRunner.query(`DROP TABLE "temporary_skills"`);
await queryRunner.query(`DROP TABLE "profiles"`);
await queryRunner.query(`DROP TABLE "experiences"`);
await queryRunner.query(`DROP TABLE "projects"`);
await queryRunner.query(`DROP TABLE "skills"`);
await queryRunner.query(`DROP TABLE "skill_ranks"`);
await queryRunner.query(`DROP TABLE "skill_levels"`);
await queryRunner.query(`DROP TABLE "skill_categories"`);
await queryRunner.query(`DROP TABLE "statuses"`);
}
}

View File

@@ -0,0 +1,20 @@
import { Seeder } from 'typeorm-extension';
import { DataSource } from 'typeorm';
import { SkillLevelEntity } from '../../skill/skill-level.entity'
export default class SkillLevelsSeeder implements Seeder {
public async run(dataSource: DataSource): Promise<void> {
const repository = dataSource.getRepository(SkillLevelEntity);
const skillLevels = [
{
name: 'Experienced'
},
{
name: 'Familiar'
}
]
await repository.save(skillLevels);
}
}

View File

@@ -0,0 +1,26 @@
import { Seeder } from 'typeorm-extension';
import { DataSource } from 'typeorm';
import { SkillRankEntity } from '../../skill/skill-rank.entity'
export default class SkillRanksSeeder implements Seeder {
public async run(dataSource: DataSource): Promise<void> {
const repository = dataSource.getRepository(SkillRankEntity);
const skillRanks = [
{
name: 'High',
weight: 3
},
{
name: 'Medium',
weight: 2
},
{
name: 'Low',
weight: 1
}
]
await repository.save(skillRanks);
}
}

View File

@@ -0,0 +1,20 @@
import { Seeder } from 'typeorm-extension';
import { DataSource } from 'typeorm';
import { StatusEntity } from '../../status/status.entity'
export default class StatusesSeeder implements Seeder {
public async run(dataSource: DataSource): Promise<void> {
const repository = dataSource.getRepository(StatusEntity);
const statuses = [
{
name: 'Draft'
},
{
name: 'Published'
}
]
await repository.save(statuses);
}
}

View File

@@ -0,0 +1,69 @@
import { Body, Controller, Delete, Get, HttpException, Param, Post, Put } from "@nestjs/common";
import { ExperienceService } from "./experience.service";
import { CustomError } from "@noahspan/noahspan-modules";
import { ExperienceDto } from "./experience.dto";
@Controller('experiences')
export class ExperienceController {
constructor(private readonly experienceService: ExperienceService) {}
@Get(':id')
async find(@Param('id') id: string) {
try {
return await this.experienceService.find(id);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Get()
async findAll() {
try {
return await this.experienceService.findAll();
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Post()
async create(@Body() experienceDto: ExperienceDto): Promise<ExperienceDto> {
try {
return await this.experienceService.create(experienceDto);
} catch (error) {
const customError = error as CustomError;
console.log(error)
throw new HttpException(customError.message, customError.statusCode)
}
}
@Put(':id')
async update(
@Param('id') id: string,
@Body() ExperienceDto: ExperienceDto
) {
try {
return await this.experienceService.update(id, ExperienceDto);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode)
}
}
@Delete(':id')
async delete(
@Param('id') id: string,
) {
try {
return await this.experienceService.delete(id);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode)
}
}
}

View File

@@ -0,0 +1,13 @@
export class ExperienceDto {
companyName: string;
companyNameGeneric: string;
location: string;
title: string;
startDate: Date;
endDate?: Date;
summary: string;
skills?: [];
createdBy?: string;
updatedBy?: string;
deletedBy?: string;
}

View File

@@ -0,0 +1,63 @@
import { ProfileEntity } from '../profile/profile.entity'
import { SkillEntity } from 'src/skill/skill.entity';
import { StatusEntity } from 'src/status/status.entity';
import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, ManyToOne, OneToMany, OneToOne, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
@Entity({ name: 'experiences' })
export class ExperienceEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
profileId: string;
@Column()
companyName: string;
@Column()
companyGeneric: string
@Column()
title: string;
@Column()
startDate: Date;
@Column({ nullable: true })
endDate: Date;
@Column({ nullable: true })
summary: string | null
@Column()
statusId: number;
@CreateDateColumn()
created: Date;
@Column({ nullable: true })
createdBy: string;
@UpdateDateColumn()
updated: Date;
@Column({ nullable: true })
updatedBy: string;
@DeleteDateColumn()
deleted: Date;
@Column({ nullable: true })
deletedBy: string;
@OneToMany(() => SkillEntity, (skill: SkillEntity) => skill.name )
skills: SkillEntity[];
@ManyToOne(() => ProfileEntity, (profile: ProfileEntity) => profile.experiences)
@JoinColumn({ name: 'profileId' })
profile: ProfileEntity;
@OneToOne(() => StatusEntity, (status: StatusEntity) => status.name )
@JoinColumn({ name: 'statusId' })
status: StatusEntity;
}

View File

@@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from "@nestjs/typeorm";
import { ExperienceEntity } from "./experience.entity";
import { ExperienceController } from "./experience.controller";
import { ExperienceService } from "./experience.service";
@Module({
imports: [
TypeOrmModule.forFeature([ExperienceEntity])
],
controllers: [ExperienceController],
exports: [ExperienceService],
providers: [ExperienceService]
})
export class ExperienceModule {}

View File

@@ -0,0 +1,51 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from "@nestjs/typeorm";
import { ExperienceService } from "./experience.service"
import { ExperienceEntity } from './experience.entity'
describe('ExperienceService', () => {
let service: ExperienceService;
const mockExperienceRepository = {
delete: jest.fn(),
find: jest.fn(),
findOneBy: jest.fn(),
save: jest.fn()
}
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ExperienceService,
{
provide: getRepositoryToken(ExperienceEntity),
useValue: mockExperienceRepository
}
]
}).compile();
service = module.get<ExperienceService>(ExperienceService);
})
it('should be defined', () => {
expect(service).toBeDefined();
})
it('find => should find one experience by id', async () => {
})
it('findAll => should find all experiences', () => {
})
it('create => should create a new experience', () => {
})
it('update => should update an experience', () => {
})
it('delete => should delete an experience')
})

View File

@@ -0,0 +1,43 @@
import { Injectable } from '@nestjs/common';
import { ExperienceEntity } from './experience.entity';
import { ExperienceDto } from './experience.dto';
import { InjectRepository } from "@nestjs/typeorm";
import { DeleteResult, Repository } from "typeorm";
@Injectable()
export class ExperienceService {
constructor(
@InjectRepository(ExperienceEntity) private readonly experienceRepository: Repository<ExperienceEntity>
) {}
async find(id: string): Promise<ExperienceEntity> {
return await this.experienceRepository.findOne({
where: { id: id },
relations: ['profile', 'status']
})
}
async findAll(): Promise<ExperienceEntity[]> {
return await this.experienceRepository.find({
relations: ['profile', 'status']
});
}
async create(experience: ExperienceDto): Promise<ExperienceDto> {
return await this.experienceRepository.save(experience);
}
async update(
id: string,
experienceDto: ExperienceDto
): Promise<ExperienceDto> {
const experienceEntity: ExperienceEntity = await this.experienceRepository.findOneBy({ id })
const experienceEntityUpdated = Object.assign(experienceEntity, experienceDto)
return await this.experienceRepository.save(experienceEntityUpdated)
}
async delete(id: string): Promise<DeleteResult> {
return await this.experienceRepository.delete({ id })
}
}

View File

@@ -0,0 +1,25 @@
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException
} from '@nestjs/common';
import { Request, Response } from 'express';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(excpetion: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
const status = excpetion.getStatus();
response.status(status).json({
name: excpetion.cause,
message: excpetion.message,
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url
});
}
}

3
api/src/index.css Normal file
View File

@@ -0,0 +1,3 @@
body {
background-color: #f2f2f2;
}

27
api/src/main.ts Normal file
View File

@@ -0,0 +1,27 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpService } from '@nestjs/axios';
import { HttpExceptionFilter } from './filters/http-exception.filter';
import { InternalServerErrorException } from '@nestjs/common';
async function bootstrap() {
const httpService = new HttpService();
const app = await NestFactory.create(AppModule);
app.enableCors();
app.setGlobalPrefix('api');
app.useGlobalFilters(new HttpExceptionFilter());
httpService.axiosRef.interceptors.response.use(
(response) => {
return response;
},
(error) => {
console.error('Internal server error exception', error);
throw new InternalServerErrorException();
}
);
await app.listen(3000);
}
bootstrap();

View File

@@ -0,0 +1,87 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
Param,
Post,
Put,
UseGuards
} from '@nestjs/common';
import { ProfileDto } from './profile.dto';
import { ProfileEntity } from './profile.entity';
import { ProfileService } from './profile.service';
import { AuthGuard, CustomError, Public } from '@noahspan/noahspan-modules';
@Controller('profiles')
export class ProfileController {
constructor(private readonly profileService: ProfileService) {}
@Get(':id')
// @UseGuards(AuthGuard)
async find(
@Param('id') id: string,
) {
try {
return await this.profileService.find(id);
} catch (error) {
const customError = error as CustomError;
console.log(error)
throw new HttpException(customError.message, customError.statusCode);
}
}
@Get()
async findAll() {
try {
return await this.profileService.findAll();
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Post()
// @UseGuards(AuthGuard)
async create(@Body() profileDto: ProfileDto) {
try {
console.log(profileDto)
return await this.profileService.create(profileDto);
} catch (error) {
const customError = error as CustomError;
console.log(customError)
throw new HttpException(customError.message, customError.statusCode);
}
}
@Put(':id')
@UseGuards(AuthGuard)
async update(
@Param('id') id: string,
@Body() profileDto: ProfileDto
) {
try {
return await this.profileService.update(id, profileDto);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Delete(':id')
@UseGuards(AuthGuard)
async delete(
@Param('id') id: string
) {
try {
return await this.profileService.delete(id);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
}

View File

@@ -0,0 +1,10 @@
export class ProfileDto {
name: string;
headline: string;
summary: string;
userId: string;
statusId: number;
createdBy?: string;
updatedBy?: string;
deletedBy?: string;
}

View File

@@ -0,0 +1,53 @@
import { ExperienceEntity } from 'src/experience/experience.entity';
import { SkillEntity } from 'src/skill/skill.entity';
import { StatusEntity } from 'src/status/status.entity';
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn, UpdateDateColumn, DeleteDateColumn, OneToOne, JoinColumn } from 'typeorm';
@Entity({ name: 'profiles' })
export class ProfileEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column()
headline: string;
@Column()
summary: string;
@Column()
statusId: number;
@Column()
userId: string;
@CreateDateColumn()
created: Date;
@Column({ nullable: true })
createdBy: string;
@UpdateDateColumn()
updated: Date;
@Column({ nullable: true })
updatedBy: string
@DeleteDateColumn()
deleted: Date;
@Column({ nullable: true })
deletedBy: string;
@OneToMany(() => ExperienceEntity, (experience: ExperienceEntity) => experience.profile)
experiences: ExperienceEntity[]
@OneToMany(() => SkillEntity, (skill: SkillEntity) => skill.name )
skills: SkillEntity[];
@OneToOne(() => StatusEntity, (status: StatusEntity) => status.name )
@JoinColumn({ name: 'statusId' })
status: StatusEntity;
}

View File

@@ -0,0 +1,15 @@
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { ProfileController } from "./profile.controller";
import { ProfileEntity } from "./profile.entity";
import { ProfileService } from "./profile.service";
@Module({
imports: [
TypeOrmModule.forFeature([ProfileEntity])
],
controllers: [ProfileController],
exports: [ProfileService],
providers: [ProfileService]
})
export class ProfileModule {}

View File

View File

@@ -0,0 +1,46 @@
import { Injectable } from '@nestjs/common';
import { ProfileEntity } from './profile.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { DeleteResult, Repository } from 'typeorm';
import { ProfileDto } from './profile.dto';
@Injectable()
export class ProfileService {
constructor(
@InjectRepository(ProfileEntity) private readonly profileRepository: Repository<ProfileEntity>,
) {}
async find(id: string): Promise<ProfileEntity> {
const profile = await this.profileRepository.findOne({
where: { id: id },
relations: ['status']
});
console.log(profile);
return profile;
}
async findAll(): Promise<ProfileEntity[]> {
return await this.profileRepository.find({
relations: ['status']
});
}
async create(profile: ProfileDto): Promise<ProfileDto> {
return await this.profileRepository.save(profile);
}
async update(
id: string,
profileDto: ProfileDto
): Promise<ProfileDto> {
const profileEntity: ProfileEntity = await this.profileRepository.findOneBy({ id })
const profileEntityUpdated = Object.assign(profileEntity, profileDto)
return await this.profileRepository.save(profileEntityUpdated);
}
async delete(id: string): Promise<DeleteResult> {
return await this.profileRepository.delete({ id });
}
}

View File

@@ -0,0 +1,70 @@
import { Test, TestingModule } from "@nestjs/testing";
import { ProjectController } from "./project.controller"
import { ProjectService } from "./project.service";
describe('ProjectController', () => {
let controller: ProjectController;
const mockProjectService = {
create: jest.fn(),
delete: jest.fn(),
find: jest.fn(),
findAll: jest.fn(),
update: jest.fn()
}
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ProjectController],
providers: [
{
provide: ProjectService,
useValue: mockProjectService
}
]
}).compile();
controller = module.get<ProjectController>(ProjectController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
it('find => should find a project by id', async () => {
const id: string = ''
});
it('find => should fail to find a project by id', async () => {
const id: string = ''
});
it('fildAll => should fail to find all projects', async () => {
});
it('create => should create a new project', async () => {
})
it('create => should fail to create a new project', async () => {
})
it('update => should update an existing project', async () => {
})
it('update => should fail to update an existing project', async () => {
})
it('delete => should delete an existing project', async () => {
})
it('delete => should failt to delete an existing project', async () => {
})
})

View File

@@ -0,0 +1,87 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
Param,
Post,
Put,
UseGuards
} from '@nestjs/common';
import { ProjectDto } from './project.dto';
import { ProjectEntity } from './project.entity';
import { ProjectService } from './project.service';
import { AuthGuard, CustomError, Public } from '@noahspan/noahspan-modules';
@Controller('projects')
export class ProjectController {
constructor(private readonly projectService: ProjectService) {}
@Get(':id')
@UseGuards(AuthGuard)
async find(
@Param('id') id: string,
) {
try {
return await this.projectService.find(id);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Get()
async findAll() {
try {
return await this.projectService.findAll();
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Post()
// @UseGuards(AuthGuard)
async create(@Body() projectDto: ProjectDto) {
try {
console.log(projectDto)
return await this.projectService.create(projectDto);
} catch (error) {
const customError = error as CustomError;
console.log(customError)
throw new HttpException(customError.message, customError.statusCode);
}
}
@Put(':id')
@UseGuards(AuthGuard)
async update(
@Param('id') id: string,
@Body() projectDto: ProjectDto
) {
try {
return await this.projectService.update(id, projectDto);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Delete(':id')
@UseGuards(AuthGuard)
async delete(
@Param('id') id: string
) {
try {
return await this.projectService.delete(id);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
}

View File

@@ -0,0 +1,13 @@
export class ProjectDto {
id: string;
name: string;
icon: string;
order: number;
summary: string;
repoUrl?: string;
siteUrl?: string;
skills?: [];
createdBy?: string;
updatedBy?: string;
deletedBy?: string;
}

View File

@@ -0,0 +1,55 @@
import { SkillEntity } from 'src/skill/skill.entity';
import { StatusEntity } from 'src/status/status.entity';
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, OneToOne, JoinColumn, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
@Entity({ name: 'projects' })
export class ProjectEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string
@Column()
iconName: string
@Column()
order: number;
@Column()
summary: string;
@Column({ nullable: true })
repoUrl?: string;
@Column({ nullable: true })
siteUrl?: string;
@Column()
statusId: string;
@CreateDateColumn()
created: Date;
@Column({ nullable: true })
createdBy: string;
@UpdateDateColumn()
updated: Date;
@Column({ nullable: true })
updatedBy: string;
@DeleteDateColumn()
deleted: Date;
@Column({ nullable: true })
deletedBy: string
@OneToMany(() => SkillEntity, (skill: SkillEntity) => skill.name )
skills: SkillEntity[];
@OneToOne(() => StatusEntity, (status: StatusEntity) => status.name )
@JoinColumn({ name: 'statusId' })
status: StatusEntity;
}

View File

@@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { ProjectController } from './project.controller';
import { ProjectService } from './project.service';
import { ProjectEntity } from './project.entity';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
ProjectModule,
TypeOrmModule.forFeature([ProjectEntity])
],
controllers: [ProjectController],
providers: [ProjectService]
})
export class ProjectModule {}

View File

@@ -0,0 +1,54 @@
import { Test, TestingModule } from "@nestjs/testing";
import { ProjectService } from "./project.service"
import { ProjectEntity } from "./project.entity";
import { getRepositoryToken } from "@nestjs/typeorm";
describe('ProjectService', () => {
let service: ProjectService;
const mockProjectRepository = {
delete: jest.fn(),
find: jest.fn(),
findOneBy: jest.fn(),
save: jest.fn()
}
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ProjectService,
{
provide: getRepositoryToken(ProjectEntity),
useValue: mockProjectRepository
}
]
}).compile();
service = module.get<ProjectService>(ProjectService);
})
it('should be defined', () => {
expect(service).toBeDefined();
})
it('find => should find one project by id', async () => {
const id: string = ''
})
it('findAll => should find all projects', async () => {
})
it('create => should create a new project', async () => {
})
it('update => should update a project', async () => {
})
it('delete => should delete a project', async () => {
})
})

View File

@@ -0,0 +1,46 @@
import { Injectable } from '@nestjs/common';
import { ProjectEntity } from './project.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { DeleteResult, Repository } from 'typeorm';
import { ProjectDto } from './project.dto';
@Injectable()
export class ProjectService {
constructor(
@InjectRepository(ProjectEntity) private readonly projectRepository: Repository<ProjectEntity>,
) {}
async find(id: string): Promise<ProjectEntity> {
const project = await this.projectRepository.findOne({
where: { id: id },
relations: ['status']
});
console.log(project);
return project;
}
async findAll(): Promise<ProjectEntity[]> {
return await this.projectRepository.find({
relations: ['status']
});
}
async create(project: ProjectDto): Promise<ProjectDto> {
return await this.projectRepository.save(project);
}
async update(
id: string,
projectDto: ProjectDto
): Promise<ProjectDto> {
const projectEntity: ProjectEntity = await this.projectRepository.findOneBy({ id })
const projectEntityUpdated = Object.assign(projectEntity, projectDto)
return await this.projectRepository.save(projectEntityUpdated);
}
async delete(id: string): Promise<DeleteResult> {
return await this.projectRepository.delete({ id });
}
}

View File

@@ -0,0 +1,89 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
Param,
Post,
Put,
UseGuards
} from '@nestjs/common';
import { SkillCategoryDto } from './skill-category.dto';
import { SkillCategoryEntity } from './skill-category.entity';
import { SkillCategoryService } from './skill-category.service';
import { AuthGuard, CustomError, Public } from '@noahspan/noahspan-modules';
@Controller('skill-categories')
export class SkillCategoryController {
constructor(private readonly skillCategoryService: SkillCategoryService) {}
@Get(':id')
@UseGuards(AuthGuard)
async find(
@Param('id') id: string,
) {
try {
return await this.skillCategoryService.find(id);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Get()
async findAll() {
try {
return await this.skillCategoryService.findAll();
} catch (error) {
console.log(error)
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Post()
// @UseGuards(AuthGuard)
async create(@Body() skillCategoryDto: SkillCategoryDto) {
try {
console.log(skillCategoryDto)
return await this.skillCategoryService.create(skillCategoryDto);
} catch (error) {
console.log(error)
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Put(':id')
@UseGuards(AuthGuard)
async update(
@Param('id') id: string,
@Body() skillCategoryDto: SkillCategoryDto
) {
try {
return await this.skillCategoryService.update(id, skillCategoryDto);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Delete(':id')
// @UseGuards(AuthGuard)
async delete(
@Param('id') id: string
) {
try {
return await this.skillCategoryService.delete(id);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
}

View File

@@ -0,0 +1,6 @@
export class SkillCategoryDto {
name: string;
createdBy?: string;
updatedBy?: string;
deletedBy?: string;
}

View File

@@ -0,0 +1,33 @@
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, JoinColumn, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
import { SkillEntity } from 'src/skill/skill.entity';
import { SkillLevelEntity } from 'src/skill/skill-level.entity';
@Entity({ name: 'skill_categories' })
export class SkillCategoryEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@CreateDateColumn()
created: Date;
@Column({ nullable: true })
createdBy: string;
@UpdateDateColumn()
updated: Date;
@Column({ nullable: true })
updatedBy: string;
@DeleteDateColumn()
deleted: Date;
@Column({ nullable: true })
deletedBy: string;
@OneToMany(() => SkillEntity, (skill: SkillEntity) => skill.name)
skills: SkillEntity[];
}

View File

@@ -0,0 +1,38 @@
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { SkillCategoryDto } from './skill-category.dto';
import { SkillCategoryEntity } from './skill-category.entity';
import { DeleteResult, Repository } from 'typeorm';
@Injectable()
export class SkillCategoryService {
constructor(
@InjectRepository(SkillCategoryEntity) private readonly skillCategoryRepository: Repository<SkillCategoryEntity>
) {}
async find(id: string): Promise<SkillCategoryEntity> {
return await this.skillCategoryRepository.findOneBy({ id })
}
async findAll(): Promise<SkillCategoryEntity[]> {
return await this.skillCategoryRepository.find();
}
async create(skillCategory: SkillCategoryDto): Promise<SkillCategoryDto> {
return await this.skillCategoryRepository.save(skillCategory)
}
async update(
id: string,
skillCategoryDto: SkillCategoryDto
): Promise<SkillCategoryDto> {
const skillCategoryEntity: SkillCategoryEntity = await this.skillCategoryRepository.findOneBy({ id })
const skillEntityUpdated = Object.assign(skillCategoryEntity, skillCategoryDto)
return await this.skillCategoryRepository.save(skillEntityUpdated)
}
async delete(id: string): Promise<DeleteResult> {
return await this.skillCategoryRepository.delete({ id })
}
}

View File

@@ -0,0 +1,24 @@
import {
Controller,
Get,
HttpException,
} from '@nestjs/common';
import { SkillLevelService } from './skill-level.service';
import { AuthGuard, CustomError, Public } from '@noahspan/noahspan-modules';
@Controller('skill-levels')
export class SkillLevelController {
constructor(private readonly skillLevelService: SkillLevelService) {}
@Get()
async findAll() {
try {
return await this.skillLevelService.findAll();
} catch (error) {
console.log(error)
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
}

View File

@@ -0,0 +1,33 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, ManyToMany, JoinTable, OneToMany, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
import { SkillEntity } from './skill.entity';
@Entity({ name: 'skill_levels' })
export class SkillLevelEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@CreateDateColumn()
created: Date;
@Column({ nullable: true })
createdBy: string;
@UpdateDateColumn()
updated: Date;
@Column({ nullable: true })
updatedBy: string;
@DeleteDateColumn()
deleted: Date;
@Column({ nullable: true })
deletedBy: string;
@OneToMany(() => SkillEntity, (skill: SkillEntity) => skill.name)
skills: SkillEntity[];
}

View File

@@ -0,0 +1,18 @@
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { SkillDto } from './skill.dto';
import { SkillEntity } from './skill.entity';
import { DeleteResult, Repository } from 'typeorm';
import { SkillLevelEntity } from "./skill-level.entity";
import { SkillRankEntity } from "./skill-rank.entity";
@Injectable()
export class SkillLevelService {
constructor(
@InjectRepository(SkillLevelEntity) private readonly skillLevelRepository: Repository<SkillLevelEntity>
) {}
async findAll(): Promise<SkillLevelEntity[]> {
return await this.skillLevelRepository.find();
}
}

View File

@@ -0,0 +1,24 @@
import {
Controller,
Get,
HttpException,
} from '@nestjs/common';
import { SkillRankService } from './skill-rank.service';
import { CustomError } from '@noahspan/noahspan-modules';
@Controller('skill-ranks')
export class SkillRankController {
constructor(private readonly skillRankService: SkillRankService) {}
@Get()
async findAll() {
try {
return await this.skillRankService.findAll();
} catch (error) {
console.log(error)
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
}

View File

@@ -0,0 +1,31 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm'
@Entity({ name: 'skill_ranks' })
export class SkillRankEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
weight: number;
@CreateDateColumn()
created: Date;
@Column({ nullable: true })
createdBy: string;
@UpdateDateColumn()
updated: Date;
@Column({ nullable: true })
updatedBy: string;
@DeleteDateColumn()
deleted: Date;
@Column({ nullable: true })
deletedBy: string;
}

View File

@@ -0,0 +1,18 @@
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { SkillDto } from './skill.dto';
import { SkillEntity } from './skill.entity';
import { DeleteResult, Repository } from 'typeorm';
import { SkillLevelEntity } from "./skill-level.entity";
import { SkillRankEntity } from "./skill-rank.entity";
@Injectable()
export class SkillRankService {
constructor(
@InjectRepository(SkillRankEntity) private readonly skillRankRepository: Repository<SkillRankEntity>
) {}
async findAll(): Promise<SkillRankEntity[]> {
return await this.skillRankRepository.find();
}
}

View File

@@ -0,0 +1,27 @@
import { Test, TestingModule } from "@nestjs/testing";
import { getRepositoryToken } from "@nestjs/typeorm";
import { ProjectService } from "src/project/project.service";
import { SkillSerivce } from './skill.service'
import { SkillEntity } from "./skill.entity";
describe('SkillService', () => {
let service: SkillService;
const mockSkillRepository = {
}
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ProjectService,
{
provide: getRepositoryToken(SkillEntity),
useValue: mockSkillRepository
}
]
}).compile();
service = module.get<SkillService>(SkillService);
})
})

View File

@@ -0,0 +1,110 @@
import {
Body,
Controller,
Delete,
Get,
HttpException,
Param,
Post,
Put,
UseGuards
} from '@nestjs/common';
import { SkillDto } from './skill.dto';
import { SkillEntity } from './skill.entity';
import { SkillService } from './skill.service';
import { AuthGuard, CustomError, Public } from '@noahspan/noahspan-modules';
@Controller('skills')
export class SkillController {
constructor(private readonly skillService: SkillService) {}
@Get(':id')
// @UseGuards(AuthGuard)
async find(
@Param('id') id: string,
) {
try {
return await this.skillService.find(id);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Get()
async findAll() {
try {
return await this.skillService.findAll();
} catch (error) {
console.log(error)
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Post()
// @UseGuards(AuthGuard)
async create(@Body() skillDto: SkillDto) {
try {
console.log(skillDto)
return await this.skillService.create(skillDto);
} catch (error) {
const customError = error as CustomError;
console.log(customError)
throw new HttpException(customError.message, customError.statusCode);
}
}
@Put(':id')
@UseGuards(AuthGuard)
async update(
@Param('id') id: string,
@Body() projectDto: SkillDto
) {
try {
return await this.skillService.update(id, projectDto);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Delete(':id')
@UseGuards(AuthGuard)
async delete(
@Param('id') id: string
) {
try {
return await this.skillService.delete(id);
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode);
}
}
@Get('levels')
async levels() {
try {
return await this.skillService.getLevels();
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode)
}
}
@Get('ranks')
async ranks() {
try {
return await this.skillService.getRanks();
} catch (error) {
const customError = error as CustomError;
throw new HttpException(customError.message, customError.statusCode)
}
}
}

View File

@@ -0,0 +1,11 @@
import { SkillLevelEntity } from "./skill-level.entity";
export class SkillDto {
categoryId: string;
name: string;
levelId: string;
rankId: string;
createdBy?: string;
updatedBy?: string;
deletedBy?: string;
}

View File

@@ -0,0 +1,52 @@
import { SkillCategoryEntity } from 'src/skill/skill-category.entity';
import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, ManyToOne, ManyToMany, OneToOne, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
import { SkillLevelEntity } from './skill-level.entity';
import { SkillRankEntity } from './skill-rank.entity';
@Entity({ name: 'skills' })
export class SkillEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@Column()
categoryId: string;
@Column()
levelId: string
@Column()
rankId: string;
@CreateDateColumn()
created: Date;
@Column({ nullable: true })
createdBy: string;
@UpdateDateColumn()
updated: Date;
@Column({ nullable: true })
updatedBy: string;
@DeleteDateColumn()
deleted: Date;
@Column({ nullable: true })
deletedBy: string;
@ManyToOne(() => SkillCategoryEntity, (category: SkillCategoryEntity) => category.name)
@JoinColumn({ name: 'categoryId' })
category: SkillCategoryEntity;
@OneToOne(() => SkillRankEntity, (rank: SkillRankEntity) => rank.name)
@JoinColumn({ name: 'rankId' })
rank: SkillRankEntity;
@ManyToOne(() => SkillLevelEntity, (level: SkillLevelEntity) => level.name)
@JoinColumn({ name: 'levelId' })
level: SkillLevelEntity;
}

View File

@@ -0,0 +1,24 @@
import { Module } from '@nestjs/common';
import { SkillController } from './skill.controller';
import { SkillService } from './skill.service';
import { SkillEntity } from './skill.entity';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { SkillLevelEntity } from './skill-level.entity';
import { SkillCategoryEntity } from './skill-category.entity';
import { SkillCategoryController } from './skill-category.controller';
import { SkillCategoryService } from './skill-category.service';
import { SkillRankEntity } from './skill-rank.entity';
import { SkillLevelController } from './skill-level.controller';
import { SkillLevelService } from './skill-level.service';
import { SkillRankController } from './skill-rank.controller';
import { SkillRankService } from './skill-rank.service';
@Module({
imports: [
TypeOrmModule.forFeature([SkillCategoryEntity, SkillEntity, SkillLevelEntity, SkillRankEntity])
],
controllers: [SkillCategoryController, SkillController, SkillLevelController, SkillRankController],
providers: [SkillCategoryService, SkillService, SkillLevelService, SkillRankService]
})
export class SkillModule {}

View File

@@ -0,0 +1,53 @@
import { Test, TestingModule } from "@nestjs/testing";
import { getRepositoryToken } from "@nestjs/typeorm";
import { SkillService } from './skill.service';
import { SkillEntity } from "./skill.entity";
describe('SkillService', () => {
let service: SkillService;
const mockSkillRepository = {
delete: jest.fn(),
find: jest.fn(),
findOneBy: jest.fn(),
save: jest.fn()
}
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
SkillService,
{
provide: getRepositoryToken(SkillEntity),
useValue: mockSkillRepository
}
]
}).compile();
service = module.get<SkillService>(SkillService);
})
it('should be defined', () => {
expect(service).toBeDefined();
})
it('find => should find one skill by id', async () => {
})
it('findAll => should fild all skills', async () => {
})
it('create => should create a new skill', async () => {
})
it('update => should update a skill', async () => {
})
it('delete => should delete a skill', async () => {
})
})

View File

@@ -0,0 +1,57 @@
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { SkillDto } from './skill.dto';
import { SkillEntity } from './skill.entity';
import { DeleteResult, Repository } from 'typeorm';
import { SkillLevelEntity } from "./skill-level.entity";
import { SkillRankEntity } from "./skill-rank.entity";
@Injectable()
export class SkillService {
constructor(
@InjectRepository(SkillEntity) private readonly skillRepository: Repository<SkillEntity>,
@InjectRepository(SkillLevelEntity) private readonly skillLevelRepository: Repository<SkillLevelEntity>,
@InjectRepository(SkillRankEntity) private readonly skillRankRepository: Repository<SkillRankEntity>
) {}
async find(id: string): Promise<SkillEntity> {
return await this.skillRepository.findOne({
where: { id: id },
relations: ['category', 'rank', 'level']
})
}
async findAll(): Promise<SkillEntity[]> {
return await this.skillRepository.find({
relations: ['category', 'rank', 'level']
});
}
async create(skill: SkillDto): Promise<SkillDto> {
return await this.skillRepository.save(skill)
}
async update(
id: string,
skillDto: SkillDto
): Promise<SkillDto> {
const skillEntity: SkillEntity = await this.skillRepository.findOneBy({ id })
const skillEntityUpdated = Object.assign(skillEntity, skillDto)
return await this.skillRepository.save(skillEntityUpdated)
}
async delete(id: string): Promise<DeleteResult> {
return await this.skillRepository.delete({ id })
}
async getLevels(): Promise<SkillLevelEntity[]> {
const levels = await this.skillLevelRepository.find()
console.log(levels);
return levels
}
async getRanks(): Promise<SkillRankEntity[]> {
return await this.skillRankRepository.find()
}
}

View File

@@ -0,0 +1,10 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity({ name: 'statuses' })
export class StatusEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
}

24
api/test/app.e2e-spec.ts Normal file
View File

@@ -0,0 +1,24 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

9
api/test/jest-e2e.json Normal file
View File

@@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

4
api/tsconfig.build.json Normal file
View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

21
api/tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}