adding skills to experiences, profiles, and projects
Some checks failed
Main / changes (push) Successful in 37s
Main / deploy (push) Has been cancelled
Main / build-and-test (push) Has been cancelled

This commit is contained in:
2026-07-20 19:28:37 -05:00
parent 9d9cdbaebc
commit e5219761c2
40 changed files with 15075 additions and 162 deletions

9
.gitignore vendored
View File

@@ -1,2 +1,9 @@
.vscode/*
node_modules
**/.env
**/.env.*
**/*.env
.DS_Store .DS_Store
*.auto.tfvars .dapr
**/**/secrets.json
.prod

16
Dockerfile Normal file
View File

@@ -0,0 +1,16 @@
FROM node:24
WORKDIR app
COPY ./api/dist ./api/dist
COPY ./api/package.json package-lock.json ./api
COPY ./client/dist ./client/dist
WORKDIR api
RUN npm ci
WORKDIR /
EXPOSE 3000
CMD ["node", "./app/api/dist/main.js"]
# ENTRYPOINT ["tail", "-f", "/dev/null"]

View File

@@ -6,5 +6,11 @@ export default () => ({
clientSecret: process.env.CLIENT_SECRET, clientSecret: process.env.CLIENT_SECRET,
issuer: process.env.ISSUER_URL, issuer: process.env.ISSUER_URL,
jwksUri: process.env.JWKS_URI, jwksUri: process.env.JWKS_URI,
tenantId: process.env.TENANT_ID tenantId: process.env.TENANT_ID,
db: {
sync: process.env.DB_SYNC,
migrationsRun: process.env.DB_MIGRATIONS_RUN,
path: process.env.DB_PATH
},
dbPath: process.env.DB_PATH
}) })

View File

@@ -12,7 +12,7 @@ export const dataSourceOptions: DataSourceOptions & SeederOptions = {
database: configService.get<string>('DB_PATH'), database: configService.get<string>('DB_PATH'),
entities: ['../**/*.entity.js'], entities: ['../**/*.entity.js'],
migrations: ['dist/database/migrations/**/*.js'], migrations: ['dist/database/migrations/**/*.js'],
migrationsRun: false, migrationsRun: configService.get<boolean>('DB_MIGRATIONS_RUN'),
seeds: ['dist/database/seeds/**/*.ts'], seeds: ['dist/database/seeds/**/*.ts'],
synchronize: configService.get<boolean>('DB_SYNC') synchronize: configService.get<boolean>('DB_SYNC')
} }

View File

@@ -1,56 +1,122 @@
import { MigrationInterface, QueryRunner } from "typeorm"; import { MigrationInterface, QueryRunner } from "typeorm";
export class InitialMigration1782738482901 implements MigrationInterface { export class InitialMigration1784577734670 implements MigrationInterface {
name = 'InitialMigration1782738482901' name = 'InitialMigration1784577734670'
public async up(queryRunner: QueryRunner): Promise<void> { 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 "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_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_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 "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 "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)`);
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)`);
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 "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)`);
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 "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)`);
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(`CREATE TABLE "profiles_skills_skills" ("profilesId" varchar NOT NULL, "skillsId" varchar NOT NULL, PRIMARY KEY ("profilesId", "skillsId"))`);
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(`CREATE INDEX "IDX_8401800ec9c15b8f383ac8d9c0" ON "profiles_skills_skills" ("profilesId") `);
await queryRunner.query(`DROP TABLE "skills"`); await queryRunner.query(`CREATE INDEX "IDX_36a9448df496235d81a7ceb089" ON "profiles_skills_skills" ("skillsId") `);
await queryRunner.query(`ALTER TABLE "temporary_skills" RENAME TO "skills"`); await queryRunner.query(`CREATE TABLE "experiences_skills_skills" ("experiencesId" varchar NOT NULL, "skillsId" varchar NOT NULL, PRIMARY KEY ("experiencesId", "skillsId"))`);
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(`CREATE INDEX "IDX_54957c4ce34cebce0c6f152b87" ON "experiences_skills_skills" ("experiencesId") `);
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(`CREATE INDEX "IDX_b39ff48a29419ea48077b619c4" ON "experiences_skills_skills" ("skillsId") `);
await queryRunner.query(`DROP TABLE "projects"`); await queryRunner.query(`CREATE TABLE "projects_skills_skills" ("projectsId" varchar NOT NULL, "skillsId" varchar NOT NULL, PRIMARY KEY ("projectsId", "skillsId"))`);
await queryRunner.query(`ALTER TABLE "temporary_projects" RENAME TO "projects"`); await queryRunner.query(`CREATE INDEX "IDX_d8128e9b444108a09d81529ea0" ON "projects_skills_skills" ("projectsId") `);
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(`CREATE INDEX "IDX_5291aa5a2295552d375007fefe" ON "projects_skills_skills" ("skillsId") `);
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(`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 "FK_a0e37cd676d13d116f73d494ee6" FOREIGN KEY ("statusId") REFERENCES "statuses" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`);
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(`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(`DROP TABLE "profiles"`);
await queryRunner.query(`ALTER TABLE "temporary_profiles" RENAME TO "profiles"`); await queryRunner.query(`ALTER TABLE "temporary_profiles" RENAME TO "profiles"`);
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 "FK_23dfcd8f2d1b6585b848f840d5e" FOREIGN KEY ("statusId") REFERENCES "statuses" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_be01c61f0c549f2187b5c05c349" FOREIGN KEY ("profileId") REFERENCES "profiles" ("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_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 "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 "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(`DROP INDEX "IDX_8401800ec9c15b8f383ac8d9c0"`);
await queryRunner.query(`DROP INDEX "IDX_36a9448df496235d81a7ceb089"`);
await queryRunner.query(`CREATE TABLE "temporary_profiles_skills_skills" ("profilesId" varchar NOT NULL, "skillsId" varchar NOT NULL, CONSTRAINT "FK_8401800ec9c15b8f383ac8d9c0d" FOREIGN KEY ("profilesId") REFERENCES "profiles" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_36a9448df496235d81a7ceb089d" FOREIGN KEY ("skillsId") REFERENCES "skills" ("id") ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY ("profilesId", "skillsId"))`);
await queryRunner.query(`INSERT INTO "temporary_profiles_skills_skills"("profilesId", "skillsId") SELECT "profilesId", "skillsId" FROM "profiles_skills_skills"`);
await queryRunner.query(`DROP TABLE "profiles_skills_skills"`);
await queryRunner.query(`ALTER TABLE "temporary_profiles_skills_skills" RENAME TO "profiles_skills_skills"`);
await queryRunner.query(`CREATE INDEX "IDX_8401800ec9c15b8f383ac8d9c0" ON "profiles_skills_skills" ("profilesId") `);
await queryRunner.query(`CREATE INDEX "IDX_36a9448df496235d81a7ceb089" ON "profiles_skills_skills" ("skillsId") `);
await queryRunner.query(`DROP INDEX "IDX_54957c4ce34cebce0c6f152b87"`);
await queryRunner.query(`DROP INDEX "IDX_b39ff48a29419ea48077b619c4"`);
await queryRunner.query(`CREATE TABLE "temporary_experiences_skills_skills" ("experiencesId" varchar NOT NULL, "skillsId" varchar NOT NULL, CONSTRAINT "FK_54957c4ce34cebce0c6f152b875" FOREIGN KEY ("experiencesId") REFERENCES "experiences" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_b39ff48a29419ea48077b619c46" FOREIGN KEY ("skillsId") REFERENCES "skills" ("id") ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY ("experiencesId", "skillsId"))`);
await queryRunner.query(`INSERT INTO "temporary_experiences_skills_skills"("experiencesId", "skillsId") SELECT "experiencesId", "skillsId" FROM "experiences_skills_skills"`);
await queryRunner.query(`DROP TABLE "experiences_skills_skills"`);
await queryRunner.query(`ALTER TABLE "temporary_experiences_skills_skills" RENAME TO "experiences_skills_skills"`);
await queryRunner.query(`CREATE INDEX "IDX_54957c4ce34cebce0c6f152b87" ON "experiences_skills_skills" ("experiencesId") `);
await queryRunner.query(`CREATE INDEX "IDX_b39ff48a29419ea48077b619c4" ON "experiences_skills_skills" ("skillsId") `);
await queryRunner.query(`DROP INDEX "IDX_d8128e9b444108a09d81529ea0"`);
await queryRunner.query(`DROP INDEX "IDX_5291aa5a2295552d375007fefe"`);
await queryRunner.query(`CREATE TABLE "temporary_projects_skills_skills" ("projectsId" varchar NOT NULL, "skillsId" varchar NOT NULL, CONSTRAINT "FK_d8128e9b444108a09d81529ea06" FOREIGN KEY ("projectsId") REFERENCES "projects" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_5291aa5a2295552d375007fefee" FOREIGN KEY ("skillsId") REFERENCES "skills" ("id") ON DELETE CASCADE ON UPDATE CASCADE, PRIMARY KEY ("projectsId", "skillsId"))`);
await queryRunner.query(`INSERT INTO "temporary_projects_skills_skills"("projectsId", "skillsId") SELECT "projectsId", "skillsId" FROM "projects_skills_skills"`);
await queryRunner.query(`DROP TABLE "projects_skills_skills"`);
await queryRunner.query(`ALTER TABLE "temporary_projects_skills_skills" RENAME TO "projects_skills_skills"`);
await queryRunner.query(`CREATE INDEX "IDX_d8128e9b444108a09d81529ea0" ON "projects_skills_skills" ("projectsId") `);
await queryRunner.query(`CREATE INDEX "IDX_5291aa5a2295552d375007fefe" ON "projects_skills_skills" ("skillsId") `);
} }
public async down(queryRunner: QueryRunner): Promise<void> { public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "profiles" RENAME TO "temporary_profiles"`); await queryRunner.query(`DROP INDEX "IDX_5291aa5a2295552d375007fefe"`);
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(`DROP INDEX "IDX_d8128e9b444108a09d81529ea0"`);
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(`ALTER TABLE "projects_skills_skills" RENAME TO "temporary_projects_skills_skills"`);
await queryRunner.query(`DROP TABLE "temporary_profiles"`); await queryRunner.query(`CREATE TABLE "projects_skills_skills" ("projectsId" varchar NOT NULL, "skillsId" varchar NOT NULL, PRIMARY KEY ("projectsId", "skillsId"))`);
await queryRunner.query(`ALTER TABLE "experiences" RENAME TO "temporary_experiences"`); await queryRunner.query(`INSERT INTO "projects_skills_skills"("projectsId", "skillsId") SELECT "projectsId", "skillsId" FROM "temporary_projects_skills_skills"`);
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(`DROP TABLE "temporary_projects_skills_skills"`);
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(`CREATE INDEX "IDX_5291aa5a2295552d375007fefe" ON "projects_skills_skills" ("skillsId") `);
await queryRunner.query(`DROP TABLE "temporary_experiences"`); await queryRunner.query(`CREATE INDEX "IDX_d8128e9b444108a09d81529ea0" ON "projects_skills_skills" ("projectsId") `);
await queryRunner.query(`DROP INDEX "IDX_b39ff48a29419ea48077b619c4"`);
await queryRunner.query(`DROP INDEX "IDX_54957c4ce34cebce0c6f152b87"`);
await queryRunner.query(`ALTER TABLE "experiences_skills_skills" RENAME TO "temporary_experiences_skills_skills"`);
await queryRunner.query(`CREATE TABLE "experiences_skills_skills" ("experiencesId" varchar NOT NULL, "skillsId" varchar NOT NULL, PRIMARY KEY ("experiencesId", "skillsId"))`);
await queryRunner.query(`INSERT INTO "experiences_skills_skills"("experiencesId", "skillsId") SELECT "experiencesId", "skillsId" FROM "temporary_experiences_skills_skills"`);
await queryRunner.query(`DROP TABLE "temporary_experiences_skills_skills"`);
await queryRunner.query(`CREATE INDEX "IDX_b39ff48a29419ea48077b619c4" ON "experiences_skills_skills" ("skillsId") `);
await queryRunner.query(`CREATE INDEX "IDX_54957c4ce34cebce0c6f152b87" ON "experiences_skills_skills" ("experiencesId") `);
await queryRunner.query(`DROP INDEX "IDX_36a9448df496235d81a7ceb089"`);
await queryRunner.query(`DROP INDEX "IDX_8401800ec9c15b8f383ac8d9c0"`);
await queryRunner.query(`ALTER TABLE "profiles_skills_skills" RENAME TO "temporary_profiles_skills_skills"`);
await queryRunner.query(`CREATE TABLE "profiles_skills_skills" ("profilesId" varchar NOT NULL, "skillsId" varchar NOT NULL, PRIMARY KEY ("profilesId", "skillsId"))`);
await queryRunner.query(`INSERT INTO "profiles_skills_skills"("profilesId", "skillsId") SELECT "profilesId", "skillsId" FROM "temporary_profiles_skills_skills"`);
await queryRunner.query(`DROP TABLE "temporary_profiles_skills_skills"`);
await queryRunner.query(`CREATE INDEX "IDX_36a9448df496235d81a7ceb089" ON "profiles_skills_skills" ("skillsId") `);
await queryRunner.query(`CREATE INDEX "IDX_8401800ec9c15b8f383ac8d9c0" ON "profiles_skills_skills" ("profilesId") `);
await queryRunner.query(`ALTER TABLE "projects" RENAME TO "temporary_projects"`); 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(`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)`);
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(`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(`DROP TABLE "temporary_projects"`);
await queryRunner.query(`ALTER TABLE "skills" RENAME TO "temporary_skills"`); 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(`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)`);
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(`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 "temporary_skills"`);
await queryRunner.query(`DROP TABLE "profiles"`); await queryRunner.query(`ALTER TABLE "experiences" RENAME TO "temporary_experiences"`);
await queryRunner.query(`DROP TABLE "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)`);
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 "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)`);
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(`DROP INDEX "IDX_5291aa5a2295552d375007fefe"`);
await queryRunner.query(`DROP INDEX "IDX_d8128e9b444108a09d81529ea0"`);
await queryRunner.query(`DROP TABLE "projects_skills_skills"`);
await queryRunner.query(`DROP INDEX "IDX_b39ff48a29419ea48077b619c4"`);
await queryRunner.query(`DROP INDEX "IDX_54957c4ce34cebce0c6f152b87"`);
await queryRunner.query(`DROP TABLE "experiences_skills_skills"`);
await queryRunner.query(`DROP INDEX "IDX_36a9448df496235d81a7ceb089"`);
await queryRunner.query(`DROP INDEX "IDX_8401800ec9c15b8f383ac8d9c0"`);
await queryRunner.query(`DROP TABLE "profiles_skills_skills"`);
await queryRunner.query(`DROP TABLE "projects"`); await queryRunner.query(`DROP TABLE "projects"`);
await queryRunner.query(`DROP TABLE "skills"`); await queryRunner.query(`DROP TABLE "skills"`);
await queryRunner.query(`DROP TABLE "experiences"`);
await queryRunner.query(`DROP TABLE "profiles"`);
await queryRunner.query(`DROP TABLE "skill_ranks"`); await queryRunner.query(`DROP TABLE "skill_ranks"`);
await queryRunner.query(`DROP TABLE "skill_levels"`); await queryRunner.query(`DROP TABLE "skill_levels"`);
await queryRunner.query(`DROP TABLE "skill_categories"`); await queryRunner.query(`DROP TABLE "skill_categories"`);

View File

@@ -31,6 +31,7 @@ export class ExperienceController {
@Post() @Post()
async create(@Body() experienceDto: ExperienceDto): Promise<ExperienceDto> { async create(@Body() experienceDto: ExperienceDto): Promise<ExperienceDto> {
console.log(experienceDto)
try { try {
return await this.experienceService.create(experienceDto); return await this.experienceService.create(experienceDto);
} catch (error) { } catch (error) {
@@ -45,6 +46,7 @@ export class ExperienceController {
@Param('id') id: string, @Param('id') id: string,
@Body() ExperienceDto: ExperienceDto @Body() ExperienceDto: ExperienceDto
) { ) {
console.log(ExperienceDto)
try { try {
return await this.experienceService.update(id, ExperienceDto); return await this.experienceService.update(id, ExperienceDto);
} catch (error) { } catch (error) {

View File

@@ -1,3 +1,5 @@
import { SkillEntity } from "src/skill/skill.entity";
export class ExperienceDto { export class ExperienceDto {
companyName: string; companyName: string;
companyNameGeneric: string; companyNameGeneric: string;
@@ -6,7 +8,7 @@ export class ExperienceDto {
startDate: Date; startDate: Date;
endDate?: Date; endDate?: Date;
summary: string; summary: string;
skills?: []; skills?: SkillEntity[];
createdBy?: string; createdBy?: string;
updatedBy?: string; updatedBy?: string;
deletedBy?: string; deletedBy?: string;

View File

@@ -1,7 +1,7 @@
import { ProfileEntity } from '../profile/profile.entity' import { ProfileEntity } from '../profile/profile.entity'
import { SkillEntity } from 'src/skill/skill.entity'; import { SkillEntity } from 'src/skill/skill.entity';
import { StatusEntity } from 'src/status/status.entity'; import { StatusEntity } from 'src/status/status.entity';
import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, ManyToOne, OneToMany, OneToOne, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm'; import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, ManyToOne, OneToMany, OneToOne, CreateDateColumn, UpdateDateColumn, DeleteDateColumn, ManyToMany, JoinTable } from 'typeorm';
@Entity({ name: 'experiences' }) @Entity({ name: 'experiences' })
export class ExperienceEntity { export class ExperienceEntity {
@@ -50,14 +50,15 @@ export class ExperienceEntity {
@Column({ nullable: true }) @Column({ nullable: true })
deletedBy: string; deletedBy: string;
@OneToMany(() => SkillEntity, (skill: SkillEntity) => skill.name ) @ManyToOne(() => StatusEntity, (status: StatusEntity) => status.name )
skills: SkillEntity[]; @JoinColumn({ name: 'statusId' })
status: StatusEntity;
@ManyToOne(() => ProfileEntity, (profile: ProfileEntity) => profile.experiences) @ManyToOne(() => ProfileEntity, (profile: ProfileEntity) => profile.experiences)
@JoinColumn({ name: 'profileId' }) @JoinColumn({ name: 'profileId' })
profile: ProfileEntity; profile: ProfileEntity;
@OneToOne(() => StatusEntity, (status: StatusEntity) => status.name ) @ManyToMany(() => SkillEntity, (skill: SkillEntity) => skill.name )
@JoinColumn({ name: 'statusId' }) @JoinTable()
status: StatusEntity; skills: SkillEntity[];
} }

View File

@@ -13,13 +13,13 @@ export class ExperienceService {
async find(id: string): Promise<ExperienceEntity> { async find(id: string): Promise<ExperienceEntity> {
return await this.experienceRepository.findOne({ return await this.experienceRepository.findOne({
where: { id: id }, where: { id: id },
relations: ['profile', 'status'] relations: ['profile', 'skills', 'status']
}) })
} }
async findAll(): Promise<ExperienceEntity[]> { async findAll(): Promise<ExperienceEntity[]> {
return await this.experienceRepository.find({ return await this.experienceRepository.find({
relations: ['profile', 'status'] relations: ['profile', 'skills', 'status']
}); });
} }

View File

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

View File

@@ -38,7 +38,7 @@ export class ProfileController {
return await this.profileService.findAll(); return await this.profileService.findAll();
} catch (error) { } catch (error) {
const customError = error as CustomError; const customError = error as CustomError;
console.log(error)
throw new HttpException(customError.message, customError.statusCode); throw new HttpException(customError.message, customError.statusCode);
} }
} }

View File

@@ -1,9 +1,12 @@
import { SkillEntity } from "src/skill/skill.entity";
export class ProfileDto { export class ProfileDto {
name: string; name: string;
headline: string; headline: string;
skills: SkillEntity[];
statusId: number;
summary: string; summary: string;
userId: string; userId: string;
statusId: number;
createdBy?: string; createdBy?: string;
updatedBy?: string; updatedBy?: string;
deletedBy?: string; deletedBy?: string;

View File

@@ -1,7 +1,7 @@
import { ExperienceEntity } from 'src/experience/experience.entity'; import { ExperienceEntity } from 'src/experience/experience.entity';
import { SkillEntity } from 'src/skill/skill.entity'; import { SkillEntity } from 'src/skill/skill.entity';
import { StatusEntity } from 'src/status/status.entity'; import { StatusEntity } from 'src/status/status.entity';
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn, UpdateDateColumn, DeleteDateColumn, OneToOne, JoinColumn } from 'typeorm'; import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn, UpdateDateColumn, DeleteDateColumn, OneToOne, JoinColumn, ManyToOne, ManyToMany, JoinTable } from 'typeorm';
@Entity({ name: 'profiles' }) @Entity({ name: 'profiles' })
export class ProfileEntity { export class ProfileEntity {
@@ -44,10 +44,11 @@ export class ProfileEntity {
@OneToMany(() => ExperienceEntity, (experience: ExperienceEntity) => experience.profile) @OneToMany(() => ExperienceEntity, (experience: ExperienceEntity) => experience.profile)
experiences: ExperienceEntity[] experiences: ExperienceEntity[]
@OneToMany(() => SkillEntity, (skill: SkillEntity) => skill.name ) @ManyToOne(() => StatusEntity, (status: StatusEntity) => status.name )
skills: SkillEntity[];
@OneToOne(() => StatusEntity, (status: StatusEntity) => status.name )
@JoinColumn({ name: 'statusId' }) @JoinColumn({ name: 'statusId' })
status: StatusEntity; status: StatusEntity;
@ManyToMany(() => SkillEntity, (skill: SkillEntity) => skill.name )
@JoinTable()
skills: SkillEntity[];
} }

View File

@@ -13,7 +13,7 @@ export class ProfileService {
async find(id: string): Promise<ProfileEntity> { async find(id: string): Promise<ProfileEntity> {
const profile = await this.profileRepository.findOne({ const profile = await this.profileRepository.findOne({
where: { id: id }, where: { id: id },
relations: ['status'] relations: ['skills', 'status']
}); });
console.log(profile); console.log(profile);
@@ -22,7 +22,7 @@ export class ProfileService {
async findAll(): Promise<ProfileEntity[]> { async findAll(): Promise<ProfileEntity[]> {
return await this.profileRepository.find({ return await this.profileRepository.find({
relations: ['status'] relations: ['skills', 'status']
}); });
} }

View File

@@ -1,3 +1,5 @@
import { SkillEntity } from "src/skill/skill.entity";
export class ProjectDto { export class ProjectDto {
id: string; id: string;
name: string; name: string;
@@ -6,7 +8,7 @@ export class ProjectDto {
summary: string; summary: string;
repoUrl?: string; repoUrl?: string;
siteUrl?: string; siteUrl?: string;
skills?: []; skills?: SkillEntity[];
createdBy?: string; createdBy?: string;
updatedBy?: string; updatedBy?: string;
deletedBy?: string; deletedBy?: string;

View File

@@ -1,6 +1,6 @@
import { SkillEntity } from 'src/skill/skill.entity'; import { SkillEntity } from 'src/skill/skill.entity';
import { StatusEntity } from 'src/status/status.entity'; import { StatusEntity } from 'src/status/status.entity';
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, OneToOne, JoinColumn, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm'; import { Entity, PrimaryGeneratedColumn, Column, OneToMany, OneToOne, JoinColumn, CreateDateColumn, UpdateDateColumn, DeleteDateColumn, ManyToOne, ManyToMany, JoinTable } from 'typeorm';
@Entity({ name: 'projects' }) @Entity({ name: 'projects' })
export class ProjectEntity { export class ProjectEntity {
@@ -46,10 +46,11 @@ export class ProjectEntity {
@Column({ nullable: true }) @Column({ nullable: true })
deletedBy: string deletedBy: string
@OneToMany(() => SkillEntity, (skill: SkillEntity) => skill.name ) @ManyToOne(() => StatusEntity, (status: StatusEntity) => status.name )
skills: SkillEntity[];
@OneToOne(() => StatusEntity, (status: StatusEntity) => status.name )
@JoinColumn({ name: 'statusId' }) @JoinColumn({ name: 'statusId' })
status: StatusEntity; status: StatusEntity;
@ManyToMany(() => SkillEntity, (skill: SkillEntity) => skill.name )
@JoinTable()
skills: SkillEntity[];
} }

View File

@@ -13,16 +13,15 @@ export class ProjectService {
async find(id: string): Promise<ProjectEntity> { async find(id: string): Promise<ProjectEntity> {
const project = await this.projectRepository.findOne({ const project = await this.projectRepository.findOne({
where: { id: id }, where: { id: id },
relations: ['status'] relations: ['skills', 'status']
}); });
console.log(project);
return project; return project;
} }
async findAll(): Promise<ProjectEntity[]> { async findAll(): Promise<ProjectEntity[]> {
return await this.projectRepository.find({ return await this.projectRepository.find({
relations: ['status'] relations: ['skills', 'status']
}); });
} }

View File

@@ -26,8 +26,5 @@ export class SkillLevelEntity {
@Column({ nullable: true }) @Column({ nullable: true })
deletedBy: string; deletedBy: string;
@OneToMany(() => SkillEntity, (skill: SkillEntity) => skill.name)
skills: SkillEntity[];
} }

View File

@@ -2,6 +2,7 @@ import { SkillCategoryEntity } from 'src/skill/skill-category.entity';
import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, ManyToOne, ManyToMany, OneToOne, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm'; import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, ManyToOne, ManyToMany, OneToOne, CreateDateColumn, UpdateDateColumn, DeleteDateColumn } from 'typeorm';
import { SkillLevelEntity } from './skill-level.entity'; import { SkillLevelEntity } from './skill-level.entity';
import { SkillRankEntity } from './skill-rank.entity'; import { SkillRankEntity } from './skill-rank.entity';
import { ExperienceEntity } from 'src/experience/experience.entity';
@Entity({ name: 'skills' }) @Entity({ name: 'skills' })
export class SkillEntity { export class SkillEntity {
@@ -42,11 +43,14 @@ export class SkillEntity {
@JoinColumn({ name: 'categoryId' }) @JoinColumn({ name: 'categoryId' })
category: SkillCategoryEntity; category: SkillCategoryEntity;
@OneToOne(() => SkillRankEntity, (rank: SkillRankEntity) => rank.name) @ManyToOne(() => SkillRankEntity, (rank: SkillRankEntity) => rank.name)
@JoinColumn({ name: 'rankId' }) @JoinColumn({ name: 'rankId' })
rank: SkillRankEntity; rank: SkillRankEntity;
@ManyToOne(() => SkillLevelEntity, (level: SkillLevelEntity) => level.name) @ManyToOne(() => SkillLevelEntity, (level: SkillLevelEntity) => level.name)
@JoinColumn({ name: 'levelId' }) @JoinColumn({ name: 'levelId' })
level: SkillLevelEntity; level: SkillLevelEntity;
@ManyToMany(() => ExperienceEntity, (experience: ExperienceEntity) => experience.skills)
experiences: ExperienceEntity[]
} }

View File

@@ -9,11 +9,15 @@ import { AxiosError, AxiosResponse } from "axios";
import { ScreenSize } from "../../enums/screenSize"; import { ScreenSize } from "../../enums/screenSize";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSave, faXmark } from "@fortawesome/free-solid-svg-icons"; import { faSave, faXmark } from "@fortawesome/free-solid-svg-icons";
import { useAppContext } from "../../hooks/appContext/UseAppContext";
const SkillCategoryForm = ({ isDrawerOpen, mode, onOpenClose, categoryId }: SkillCategoryFormProps) => { const SkillCategoryForm = ({ isDrawerOpen, mode, onOpenClose, categoryId }: SkillCategoryFormProps) => {
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
const appContext = useAppContext();
const defaultValues = { const defaultValues = {
name: '' name: '',
createdBy: '',
updatedBy: ''
} }
const methods = useForm({ const methods = useForm({
defaultValues: defaultValues defaultValues: defaultValues
@@ -26,14 +30,25 @@ const SkillCategoryForm = ({ isDrawerOpen, mode, onOpenClose, categoryId }: Skil
onOpenClose(FormMode.CANCEL) onOpenClose(FormMode.CANCEL)
} }
const onSubmit = async (data: unknown) => { const onSubmit = async (data: any) => {
try { try {
dispatch({ type: 'SET_IS_LOADING', payload: true }) dispatch({ type: 'SET_IS_LOADING', payload: true })
if (!categoryId) { if (!categoryId) {
await httpClient.post(`api/skill-categories`, data); const newData = {
...data,
createdBy: appContext.state.userProfile.userPrincipalName,
updatedBy: appContext.state.userProfile.userPrincipalName
}
await httpClient.post(`api/skill-categories`, newData);
} else { } else {
await httpClient.put(`api/skill-categories`) const newData = {
...data,
updatedBy: appContext.state.userProfile.userPrincipalName
}
await httpClient.put(`api/skill-categories`, newData)
} }
methods.reset(defaultValues); methods.reset(defaultValues);

View File

@@ -9,14 +9,18 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSave, faXmark } from '@fortawesome/free-solid-svg-icons'; import { faSave, faXmark } from '@fortawesome/free-solid-svg-icons';
import { ScreenSize } from '../../enums/screenSize'; import { ScreenSize } from '../../enums/screenSize';
import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints'; import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints';
import { useAppContext } from '../../hooks/appContext/UseAppContext';
const SkillForm = ({ isDrawerOpen, mode, onOpenClose, skillId }: SkillFormProps) => { const SkillForm = ({ isDrawerOpen, mode, onOpenClose, skillId }: SkillFormProps) => {
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
const appContext = useAppContext();
const defaultValues = { const defaultValues = {
name: '', name: '',
categoryId: '', categoryId: '',
levelId: '', levelId: '',
rankId: '' rankId: '',
createdBy: '',
updatedBy: ''
} }
const methods = useForm({ const methods = useForm({
defaultValues: defaultValues defaultValues: defaultValues
@@ -29,14 +33,25 @@ const SkillForm = ({ isDrawerOpen, mode, onOpenClose, skillId }: SkillFormProps)
onOpenClose(FormMode.CANCEL); onOpenClose(FormMode.CANCEL);
}; };
const onSubmit = async (data: unknown) => { const onSubmit = async (data: any) => {
try { try {
dispatch({ type: 'SET_IS_LOADING', payload: true }); dispatch({ type: 'SET_IS_LOADING', payload: true });
console.log(data) console.log(data)
if (!skillId) { if (!skillId) {
await httpClient.post(`api/skills`, data); const newData = {
...data,
createdBy: appContext.state.userProfile.userPrincipalName,
updatedBy: appContext.state.userProfile.userPrincipalName
}
await httpClient.post(`api/skills`, newData);
} else { } else {
await httpClient.put(`api/skills/skill/${skillId}`, data); const newData = {
...data,
updatedBy: appContext.state.userProfile.userPrincipalName
}
await httpClient.put(`api/skills/skill/${skillId}`, newData);
} }
methods.reset(defaultValues); methods.reset(defaultValues);

View File

@@ -12,14 +12,17 @@ import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints';
import { Profile } from '../profiles/Profile.interface'; import { Profile } from '../profiles/Profile.interface';
import { useProfiles } from '../../hooks/profiles/UseProfiles'; import { useProfiles } from '../../hooks/profiles/UseProfiles';
import { useAppContext } from '../../hooks/appContext/UseAppContext'; import { useAppContext } from '../../hooks/appContext/UseAppContext';
import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react';
import { useSkills } from '../../hooks/skills/UseSkills'; import { useSkills } from '../../hooks/skills/UseSkills';
import MultiSelectDropdown from '../multiSelectDropdown/MultiSelectDropdown';
import { Skill } from '../skills/Skill.interface';
import { Experience } from '../experiences/Experience.interface';
const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: ExperienceFormProps) => { const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: ExperienceFormProps) => {
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
const appContext = useAppContext(); const appContext = useAppContext();
const { profiles } = useProfiles(); const { profiles } = useProfiles();
const { skills } = useSkills(); const { skills } = useSkills();
const experienceSkills: Skill[] = []
const defaultValues = { const defaultValues = {
profileId: '', profileId: '',
companyName: '', companyName: '',
@@ -27,7 +30,7 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper
title: '', title: '',
startDate: '', startDate: '',
endDate: '', endDate: '',
skills: [], skills: experienceSkills,
summary: '', summary: '',
statusId: 1, statusId: 1,
createdBy: '', createdBy: '',
@@ -36,9 +39,23 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper
const methods = useForm({ const methods = useForm({
defaultValues: defaultValues defaultValues: defaultValues
}); });
// const watchRepoName = methods.watch(['repoName'])
const { screenSize } = useBreakpoints(); const { screenSize } = useBreakpoints();
const onSetSelectedSkills = (selectedSkill: Skill) => {
const selectedSkillExists = state.selectedSkills.find((skill) => skill.id === selectedSkill.id)
let newSelectedSkills: Skill[];
if (selectedSkillExists) {
newSelectedSkills = state.selectedSkills.filter((skill) => skill.id !== selectedSkillExists.id)
} else {
newSelectedSkills = [...state.selectedSkills, selectedSkill]
}
dispatch({ type: 'SET_SELECTED_SKILLS', payload: newSelectedSkills })
methods.setValue('skills', newSelectedSkills)
}
const onCancel = () => { const onCancel = () => {
methods.reset(defaultValues); methods.reset(defaultValues);
dispatch({ type: 'SET_IS_DISABLED', payload: false }); dispatch({ type: 'SET_IS_DISABLED', payload: false });
@@ -92,6 +109,10 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper
); );
const experience = response.data; const experience = response.data;
if (experience.skills) {
dispatch({ type: 'SET_SELECTED_SKILLS', payload: experience.skills })
}
methods.reset(experience); methods.reset(experience);
} catch (error) { } catch (error) {
const axiosError = error as AxiosError; const axiosError = error as AxiosError;
@@ -127,7 +148,9 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper
}, [profiles]); }, [profiles]);
useEffect(() => { useEffect(() => {
if (skills) {
dispatch({ type: 'SET_SKILL_OPTIONS', payload: skills }) dispatch({ type: 'SET_SKILL_OPTIONS', payload: skills })
}
}, [skills]) }, [skills])
return ( return (
@@ -296,23 +319,12 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper
<Controller <Controller
name="skills" name="skills"
control={methods.control} control={methods.control}
render={({ field: { onChange, value } }) => ( render={() => (
// <input <MultiSelectDropdown
// type='text' skills={state.skillOptions}
// className='input w-full' onChange={onSetSelectedSkills}
// disabled={state.isDisabled} selectedSkills={state.selectedSkills}
// onChange={onChange} />
// value={value}
// />
<Listbox value={value} onChange={onChange}>
<ListboxOptions anchor='bottom'>
{state.skillOptions?.map((skill) => (
<ListboxOption key={skill.id} value={skill.id}>
{skill.name}
</ListboxOption>
))}
</ListboxOptions>
</Listbox>
)} )}
/> />
</div> </div>

View File

@@ -6,5 +6,6 @@ export interface ExperienceFormState {
isDisabled: boolean; isDisabled: boolean;
isLoading: boolean; isLoading: boolean;
profileOptions: Profile[]; profileOptions: Profile[];
skillOptions: Skill[] | undefined; selectedSkills: Skill[];
skillOptions: Skill[];
} }

View File

@@ -7,13 +7,15 @@ type Action =
| { type: 'SET_IS_DISABLED'; payload: boolean } | { type: 'SET_IS_DISABLED'; payload: boolean }
| { type: 'SET_IS_LOADING'; payload: boolean } | { type: 'SET_IS_LOADING'; payload: boolean }
| { type: 'SET_PROFILE_OPTIONS'; payload: Profile[] } | { type: 'SET_PROFILE_OPTIONS'; payload: Profile[] }
| { type: 'SET_SKILL_OPTIONS'; payload: Skill[] | undefined }; | { type: 'SET_SELECTED_SKILLS'; payload: Skill[] }
| { type: 'SET_SKILL_OPTIONS'; payload: Skill[] };
export const initialState: ExperienceFormState = { export const initialState: ExperienceFormState = {
error: undefined, error: undefined,
isDisabled: false, isDisabled: false,
isLoading: true, isLoading: true,
profileOptions: [], profileOptions: [],
selectedSkills: [],
skillOptions: [] skillOptions: []
}; };
@@ -46,6 +48,12 @@ export const reducer = (
profileOptions: action.payload profileOptions: action.payload
} }
} }
case 'SET_SELECTED_SKILLS': {
return {
...state,
selectedSkills: action.payload
}
}
case 'SET_SKILL_OPTIONS': { case 'SET_SKILL_OPTIONS': {
return { return {
...state, ...state,

View File

@@ -12,7 +12,7 @@ export interface Experience {
startDate: Date; startDate: Date;
endDate?: Date; endDate?: Date;
summary?: string; summary?: string;
skills: Skill[]; skills?: Skill[];
profile: Profile; profile: Profile;
status: Status; status: Status;
} }

View File

@@ -17,6 +17,7 @@ import { useUserRole } from '../../hooks/userRole/UseUserRole';
import Alert from '../../alert/Alert'; import Alert from '../../alert/Alert';
import { CellContext, ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, useReactTable } from '@tanstack/react-table'; import { CellContext, ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, useReactTable } from '@tanstack/react-table';
import { Profile } from '../profiles/Profile.interface'; import { Profile } from '../profiles/Profile.interface';
import { Skill } from '../skills/Skill.interface';
interface ActionsProps { interface ActionsProps {
id: string; id: string;
@@ -166,6 +167,17 @@ const Experiences = () => {
accessorKey: 'summary', accessorKey: 'summary',
header: 'Summary' header: 'Summary'
}, },
{
id: 'skills',
accessorKey: 'skills',
header: 'Skills',
cell: (info: CellContext<Experience, unknown>) => {
const skills = info.getValue() as Skill[];
const skillsString: string = skills.map((skill) => skill.name).join(', ')
return skillsString
}
},
{ {
id: 'status', id: 'status',
accessorKey: 'status', accessorKey: 'status',

View File

@@ -1,32 +1,33 @@
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { MultiSelectDropdownProps } from "./MultiSelectDropdownProps"; import { MultiSelectDropdownProps } from "./MultiSelectDropdownProps";
import { Skill } from "../skills/Skill.interface";
const MultiSelectDropdown = ({ options }: MultiSelectDropdownProps) => { const MultiSelectDropdown = ({ onChange, skills, selectedSkills }: MultiSelectDropdownProps) => {
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const [selectedValues, setSelectedValues] = useState([]) // const [selectedValues, setSelectedValues] = useState([])
const dropdownRef = useRef(null) const dropdownRef = useRef(null)
const handleToggleOption = (value: any) => { const handleToggleOption = (value: any) => {
let updated; // let updated;
if (selectedValues.includes(value)) { // if (selectedValues.includes(value)) {
updated = selectedValues.filter((item) => item !== value); // updated = selectedValues.filter((item) => item !== value);
} else { // } else {
updated = [...selectedValues, value]; // updated = [...selectedValues, value];
} // }
setSelectedValues(updated); // setSelectedValues(updated);
if (onChange) onChange(updated) // if (onChange) onChange(updated)
} }
const handleRemoveBadge = (event, value) => { const handleRemoveBadge = (event, value) => {
event.stopPropagation(); // event.stopPropagation();
const updated = selectedValues.filter((item) => item !== value); // const updated = selectedValues.filter((item) => item !== value);
setSelectedValues(updated) // setSelectedValues(updated)
if (onChange) onchange(updated) // if (onChange) onchange(updated)
} }
useEffect(() => { useEffect(() => {
@@ -50,21 +51,15 @@ const MultiSelectDropdown = ({ options }: MultiSelectDropdownProps) => {
className="select select-bordered w-full h-auto min-h-12 flex flex-wrap items-center gap-1 p-2 bg-base-100 text-left cursor-pointer" className="select select-bordered w-full h-auto min-h-12 flex flex-wrap items-center gap-1 p-2 bg-base-100 text-left cursor-pointer"
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
> >
{selectedValues.length === 0 ? ( {selectedSkills.length === 0 ? (
<span className="text-base-content/50"></span> <span className="text-base-content/50"></span>
) : ( ) : (
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{selectedValues.map((val) => { {selectedSkills.map((selectedSkill: Skill) => {
const option = options.find((o: any) => o.value === val); const option = skills.find((skill: Skill) => skill.id === selectedSkill.id);
return ( return (
<div key={val} className="badge badge-primary gap-1 py-3 px-2"> <div key={selectedSkill.id} className="badge badge-primary gap-1 py-3 px-2">
{option?.label || val} {option?.name || selectedSkill.name}
<button
onClick={(event) => handleRemoveBadge(event, val)}
className="btn btn-ghost btn-xs p-0 min-h-0 h-auto text-primary-content hover:bg-transparent"
>
</button>
</div> </div>
); );
})} })}
@@ -75,16 +70,16 @@ const MultiSelectDropdown = ({ options }: MultiSelectDropdownProps) => {
tabIndex={0} tabIndex={0}
className="dropdown-content menu p-2 shadow-lg bg-base-100 rounded-box w-full max-h-60 overflow-y-auto z-[1] border border-base-200" className="dropdown-content menu p-2 shadow-lg bg-base-100 rounded-box w-full max-h-60 overflow-y-auto z-[1] border border-base-200"
> >
{options.map((option) => ( {skills.map((skill) => (
<li key={option.value} className="p-0"> <li key={skill.id} className="p-0">
<label className="label cursor-pointer justify-start gap-3 px-4 py-2 hover:bg-base-200 rounded-lg w-full"> <label className="label cursor-pointer justify-start gap-3 px-4 py-2 hover:bg-base-200 rounded-lg w-full">
<input <input
type="checkbox" type="checkbox"
className="checkbox checkbox-primary checkbox-sm" className="checkbox checkbox-primary checkbox-sm"
checked={selectedValues.includes(option.value)} checked={selectedSkills.includes(skill)}
onChange={() => handleToggleOption(option.value)} onChange={() => onChange(skill)}
/> />
<span className="label-text text-base-content">{option.label}</span> <span className="label-text text-base-content">{skill.name}</span>
</label> </label>
</li> </li>
))} ))}

View File

@@ -1,3 +1,7 @@
import { Skill } from "../skills/Skill.interface";
export interface MultiSelectDropdownProps { export interface MultiSelectDropdownProps {
options: { label: string; value: string }[]; onChange: (selectedSkill: Skill) => void;
skills: Skill[];
selectedSkills: Skill[];
} }

View File

@@ -10,15 +10,21 @@ import { faSave, faXmark } from '@fortawesome/free-solid-svg-icons';
import { ScreenSize } from '../../enums/screenSize'; import { ScreenSize } from '../../enums/screenSize';
import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints'; import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints';
import { useAppContext } from '../../hooks/appContext/UseAppContext'; import { useAppContext } from '../../hooks/appContext/UseAppContext';
import { Skill } from '../skills/Skill.interface';
import { useSkills } from '../../hooks/skills/UseSkills';
import MultiSelectDropdown from '../multiSelectDropdown/MultiSelectDropdown';
const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileFormProps) => { const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileFormProps) => {
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
const appContext = useAppContext(); const appContext = useAppContext();
const { skills } = useSkills();
const projectSkills: Skill[] = []
const defaultValues = { const defaultValues = {
name: '', name: '',
headline: '', headline: '',
summary: '', summary: '',
userId: '', userId: '',
skills: projectSkills,
statusId: 1 statusId: 1
} }
const methods = useForm({ const methods = useForm({
@@ -27,6 +33,20 @@ const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileForm
// const watchRepoName = methods.watch(['repoName']) // const watchRepoName = methods.watch(['repoName'])
const { screenSize } = useBreakpoints(); const { screenSize } = useBreakpoints();
const onSetSelectedSkills = (selectedSkill: Skill) => {
const selectedSkillExists = state.selectedSkills.find((skill) => skill.id === selectedSkill.id)
let newSelectedSkills: Skill[];
if (selectedSkillExists) {
newSelectedSkills = state.selectedSkills.filter((skill) => skill.id !== selectedSkillExists.id)
} else {
newSelectedSkills = [...state.selectedSkills, selectedSkill]
}
dispatch({ type: 'SET_SELECTED_SKILLS', payload: newSelectedSkills })
methods.setValue('skills', newSelectedSkills)
}
const onCancel = () => { const onCancel = () => {
methods.reset(defaultValues); methods.reset(defaultValues);
dispatch({ type: 'SET_IS_DISABLED', payload: false }); dispatch({ type: 'SET_IS_DISABLED', payload: false });
@@ -80,9 +100,13 @@ const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileForm
const response: AxiosResponse = await httpClient.get( const response: AxiosResponse = await httpClient.get(
`api/profiles/${profileId}` `api/profiles/${profileId}`
); );
const entry = response.data; const profile = response.data;
methods.reset(entry); if (profile.skills) {
dispatch({ type: 'SET_SELECTED_SKILLS', payload: profile.skills })
}
methods.reset(profile);
} catch (error) { } catch (error) {
const axiosError = error as AxiosError; const axiosError = error as AxiosError;
console.log(axiosError) console.log(axiosError)
@@ -97,6 +121,12 @@ const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileForm
} }
}, [profileId]); }, [profileId]);
useEffect(() => {
if (skills) {
dispatch({ type: 'SET_SKILL_OPTIONS', payload: skills })
}
}, [skills])
return ( return (
<div className='drawer drawer-end'> <div className='drawer drawer-end'>
<input type='checkbox' className='drawer-toggle' onChange={() => {}} checked={isDrawerOpen} /> <input type='checkbox' className='drawer-toggle' onChange={() => {}} checked={isDrawerOpen} />
@@ -175,6 +205,22 @@ const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileForm
)} )}
/> />
</div> </div>
<div className={`col-span-3 self-center after:content-['*'] after:ms-0.5`}>
<span>Skills</span>
</div>
<div className='col-span-9'>
<Controller
name="skills"
control={methods.control}
render={() => (
<MultiSelectDropdown
skills={state.skillOptions}
onChange={onSetSelectedSkills}
selectedSkills={state.selectedSkills}
/>
)}
/>
</div>
<div className='col-span-12 justify-self-end self-center'> <div className='col-span-12 justify-self-end self-center'>
<button <button
className='btn' className='btn'

View File

@@ -1,5 +1,9 @@
import { Skill } from "../skills/Skill.interface";
export interface ProfileFormState { export interface ProfileFormState {
error: string | undefined; error: string | undefined;
isDisabled: boolean; isDisabled: boolean;
isLoading: boolean; isLoading: boolean;
selectedSkills: Skill[];
skillOptions: Skill[];
} }

View File

@@ -1,15 +1,20 @@
import { Profile } from "../profiles/Profile.interface"; import { Profile } from "../profiles/Profile.interface";
import { Skill } from "../skills/Skill.interface";
import { ProfileFormState } from "./ProfileFormState.interface" import { ProfileFormState } from "./ProfileFormState.interface"
type Action = type Action =
| { type: 'SET_ERROR'; payload: string | undefined } | { type: 'SET_ERROR'; payload: string | undefined }
| { type: 'SET_IS_DISABLED'; payload: boolean } | { type: 'SET_IS_DISABLED'; payload: boolean }
| { type: 'SET_IS_LOADING'; payload: boolean }; | { type: 'SET_IS_LOADING'; payload: boolean }
| { type: 'SET_SELECTED_SKILLS'; payload: Skill[] }
| { type: 'SET_SKILL_OPTIONS'; payload: Skill[] };
export const initialState: ProfileFormState = { export const initialState: ProfileFormState = {
error: undefined, error: undefined,
isDisabled: false, isDisabled: false,
isLoading: true isLoading: true,
selectedSkills: [],
skillOptions: []
}; };
export const reducer = ( export const reducer = (
@@ -34,6 +39,18 @@ export const reducer = (
...state, ...state,
isLoading: action.payload isLoading: action.payload
}; };
}
case 'SET_SELECTED_SKILLS': {
return {
...state,
selectedSkills: action.payload
}
}
case 'SET_SKILL_OPTIONS': {
return {
...state,
skillOptions: action.payload
}
} }
default: { default: {
return state; return state;

View File

@@ -16,6 +16,7 @@ import { UserRole } from '../../enums/userRole';
import { useUserRole } from '../../hooks/userRole/UseUserRole'; import { useUserRole } from '../../hooks/userRole/UseUserRole';
import Alert from '../../alert/Alert'; import Alert from '../../alert/Alert';
import { CellContext, ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, useReactTable } from '@tanstack/react-table'; import { CellContext, ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, useReactTable } from '@tanstack/react-table';
import { Skill } from '../skills/Skill.interface';
interface ActionsProps { interface ActionsProps {
id: string; id: string;
@@ -144,6 +145,17 @@ const Profiles = () => {
accessorKey: 'summary', accessorKey: 'summary',
header: 'Summary' header: 'Summary'
}, },
{
id: 'skills',
accessorKey: 'skills',
header: 'Skills',
cell: (info: CellContext<Profile, unknown>) => {
const skills = info.getValue() as Skill[];
const skillsString: string = skills.map((skill) => skill.name).join(', ')
return skillsString
}
},
{ {
id: 'status', id: 'status',
accessorKey: 'status', accessorKey: 'status',

View File

@@ -11,10 +11,15 @@ import { ScreenSize } from '../../enums/screenSize';
import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints'; import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints';
import { useProfiles } from '../../hooks/profiles/UseProfiles'; import { useProfiles } from '../../hooks/profiles/UseProfiles';
import { Profile } from '../profiles/Profile.interface'; import { Profile } from '../profiles/Profile.interface';
import { Skill } from '../skills/Skill.interface';
import { useSkills } from '../../hooks/skills/UseSkills';
import MultiSelectDropdown from '../multiSelectDropdown/MultiSelectDropdown';
const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectFormProps) => { const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectFormProps) => {
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
const { profiles } = useProfiles() const { profiles } = useProfiles();
const { skills } = useSkills();
const projectSkills: Skill[] = []
const defaultValues = { const defaultValues = {
profileId: '', profileId: '',
name: '', name: '',
@@ -23,6 +28,7 @@ const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectForm
repoUrl: '', repoUrl: '',
siteUrl: '', siteUrl: '',
order: '', order: '',
skills: projectSkills,
statusId: 1 statusId: 1
} }
const methods = useForm({ const methods = useForm({
@@ -31,6 +37,20 @@ const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectForm
// const watchRepoName = methods.watch(['repoName']) // const watchRepoName = methods.watch(['repoName'])
const { screenSize } = useBreakpoints(); const { screenSize } = useBreakpoints();
const onSetSelectedSkills = (selectedSkill: Skill) => {
const selectedSkillExists = state.selectedSkills.find((skill) => skill.id === selectedSkill.id)
let newSelectedSkills: Skill[];
if (selectedSkillExists) {
newSelectedSkills = state.selectedSkills.filter((skill) => skill.id !== selectedSkillExists.id)
} else {
newSelectedSkills = [...state.selectedSkills, selectedSkill]
}
dispatch({ type: 'SET_SELECTED_SKILLS', payload: newSelectedSkills })
methods.setValue('skills', newSelectedSkills)
}
const onCancel = () => { const onCancel = () => {
methods.reset(defaultValues); methods.reset(defaultValues);
dispatch({ type: 'SET_IS_DISABLED', payload: false }); dispatch({ type: 'SET_IS_DISABLED', payload: false });
@@ -44,7 +64,7 @@ const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectForm
if (!projectId) { if (!projectId) {
await httpClient.post(`api/projects`, data); await httpClient.post(`api/projects`, data);
} else { } else {
await httpClient.put(`api/projects/project/${projectId}`, data); await httpClient.put(`api/projects/${projectId}`, data);
} }
methods.reset(defaultValues); methods.reset(defaultValues);
@@ -77,11 +97,15 @@ const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectForm
dispatch({ type: 'SET_IS_LOADING', payload: true }); dispatch({ type: 'SET_IS_LOADING', payload: true });
const response: AxiosResponse = await httpClient.get( const response: AxiosResponse = await httpClient.get(
`api/projects/project/${projectId}` `api/projects/${projectId}`
); );
const entry = response.data; const project = response.data;
methods.reset(entry); if (project.skills) {
dispatch({ type: 'SET_SELECTED_SKILLS', payload: project.skills })
}
methods.reset(project);
} catch (error) { } catch (error) {
const axiosError = error as AxiosError; const axiosError = error as AxiosError;
console.log(axiosError) console.log(axiosError)
@@ -107,13 +131,20 @@ const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectForm
summary: '', summary: '',
userId: '', userId: '',
experiences: [], experiences: [],
skills: [] skills: [],
status: null
}) })
dispatch({ type: 'SET_PROFILE_OPTIONS', payload: newProfileOptions }); dispatch({ type: 'SET_PROFILE_OPTIONS', payload: newProfileOptions });
} }
}, [profiles]); }, [profiles]);
useEffect(() => {
if (skills) {
dispatch({ type: 'SET_SKILL_OPTIONS', payload: skills })
}
}, [skills])
return ( return (
<div className='drawer drawer-end'> <div className='drawer drawer-end'>
<input type='checkbox' className='drawer-toggle' onChange={() => {}} checked={isDrawerOpen} /> <input type='checkbox' className='drawer-toggle' onChange={() => {}} checked={isDrawerOpen} />
@@ -272,6 +303,22 @@ const ProjectForm = ({ isDrawerOpen, mode, onOpenClose, projectId }: ProjectForm
)} )}
/> />
</div> </div>
<div className={`col-span-3 self-center after:content-['*'] after:ms-0.5`}>
<span>Skills</span>
</div>
<div className='col-span-9'>
<Controller
name="skills"
control={methods.control}
render={() => (
<MultiSelectDropdown
skills={state.skillOptions}
onChange={onSetSelectedSkills}
selectedSkills={state.selectedSkills}
/>
)}
/>
</div>
<div className='col-span-12 justify-self-end self-center'> <div className='col-span-12 justify-self-end self-center'>
<button <button
className='btn' className='btn'

View File

@@ -1,8 +1,11 @@
import { Profile } from "../profiles/Profile.interface"; import { Profile } from "../profiles/Profile.interface";
import { Skill } from "../skills/Skill.interface";
export interface ProjectFormState { export interface ProjectFormState {
error: string | undefined; error: string | undefined;
isDisabled: boolean; isDisabled: boolean;
isLoading: boolean; isLoading: boolean;
profileOptions: Profile[]; profileOptions: Profile[];
selectedSkills: Skill[];
skillOptions: Skill[];
} }

View File

@@ -1,17 +1,22 @@
import { Profile } from "../profiles/Profile.interface"; import { Profile } from "../profiles/Profile.interface";
import { Skill } from "../skills/Skill.interface";
import { ProjectFormState } from "./ProjectFormState.interface" import { ProjectFormState } from "./ProjectFormState.interface"
type Action = type Action =
| { type: 'SET_ERROR'; payload: string | undefined } | { type: 'SET_ERROR'; payload: string | undefined }
| { type: 'SET_IS_DISABLED'; payload: boolean } | { type: 'SET_IS_DISABLED'; payload: boolean }
| { type: 'SET_IS_LOADING'; payload: boolean } | { type: 'SET_IS_LOADING'; payload: boolean }
| { type: 'SET_PROFILE_OPTIONS'; payload: Profile[] }; | { type: 'SET_PROFILE_OPTIONS'; payload: Profile[] }
| { type: 'SET_SELECTED_SKILLS'; payload: Skill[] }
| { type: 'SET_SKILL_OPTIONS'; payload: Skill[] };
export const initialState: ProjectFormState = { export const initialState: ProjectFormState = {
error: undefined, error: undefined,
isDisabled: false, isDisabled: false,
isLoading: true, isLoading: true,
profileOptions: [] profileOptions: [],
selectedSkills: [],
skillOptions: []
}; };
export const reducer = ( export const reducer = (
@@ -43,6 +48,18 @@ export const reducer = (
profileOptions: action.payload profileOptions: action.payload
} }
} }
case 'SET_SELECTED_SKILLS': {
return {
...state,
selectedSkills: action.payload
}
}
case 'SET_SKILL_OPTIONS': {
return {
...state,
skillOptions: action.payload
}
}
default: { default: {
return state; return state;
} }

View File

@@ -1,20 +1,4 @@
import { Project } from './Project.interface'; import { Project } from './Project.interface';
// import {
// Alert,
// Box,
// Button,
// Card,
// CardActions,
// CardContent,
// CardHeader,
// Container,
// Grid,
// Icon,
// IconName,
// Skeleton,
// Stack,
// Typography
// } from '@noahspan/noahspan-components';
import { FormMode } from '../../enums/formMode'; import { FormMode } from '../../enums/formMode';
import { useEffect, useReducer } from 'react'; import { useEffect, useReducer } from 'react';
import { initialState, reducer } from './reducer'; import { initialState, reducer } from './reducer';
@@ -32,6 +16,7 @@ import { UserRole } from '../../enums/userRole';
import { useUserRole } from '../../hooks/userRole/UseUserRole'; import { useUserRole } from '../../hooks/userRole/UseUserRole';
import Alert from '../../alert/Alert'; import Alert from '../../alert/Alert';
import { CellContext, ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, useReactTable } from '@tanstack/react-table'; import { CellContext, ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, useReactTable } from '@tanstack/react-table';
import { Skill } from '../skills/Skill.interface';
interface ActionsProps { interface ActionsProps {
id: string; id: string;
@@ -169,6 +154,17 @@ const Projects = () => {
accessorKey: 'summary', accessorKey: 'summary',
header: 'Summary' header: 'Summary'
}, },
{
id: 'skills',
accessorKey: 'skills',
header: 'Skills',
cell: (info: CellContext<Project, unknown>) => {
const skills = info.getValue() as Skill[];
const skillsString: string = skills.map((skill) => skill.name).join(', ')
return skillsString
}
},
{ {
id: 'status', id: 'status',
accessorKey: 'status', accessorKey: 'status',

BIN
database/portfolio.db Normal file

Binary file not shown.

70
docker-compose.yaml Normal file
View File

@@ -0,0 +1,70 @@
services:
azurite:
container_name: azurite-portfolio
image: mcr.microsoft.com/azure-storage/azurite
ports:
- '10000:10000'
- '10001:10001'
- '10002:10002'
command: 'azurite --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --loose --skipApiVersionCheck'
healthcheck:
test: ["CMD", "nc", "-z", "127.0.0.1", "10000"]
volumes:
- ./azurite-data:/data
# seed:
# image: mcr.microsoft.com/azure-cli
# depends_on:
# azurite:
# condition: service_healthy
# volumes:
# - ./seed-data:/data
# entrypoint: >
# sh -c "
# az storage container create --name mycontainer --connection-string 'DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite:10000/devstoreaccount1;' &&
# az storage blob upload-batch --destination mycontainer --source /data --connection-string 'DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite:10000/devstoreaccount1;'
# "
restore:
container_name: restore
image: litestream/litestream:0.5.2
volumes:
- ./database/portfolio.db:/mnt/data/portfolio.db
- ./database/litestream/litestream.yml:/mnt/litestream/litestream.yml
- backup:/mnt/data/backup
- data:/mnt/data
command: restore -config /mnt/litestream/litestream.yml -if-db-not-exists -if-replica-exists /mnt/data/portfolio.db
app:
container_name: portfolio
build:
context: .
ports:
- '3000:3000'
env_file:
- ./api/.env
environment:
- DB_PATH=../../mnt/data/portfolio.db
volumes:
- data:/mnt/data
depends_on:
restore:
condition: service_completed_successfully
replicate:
container_name: replicate
image: litestream/litestream:0.5.2
volumes:
- ./database/litestream/litestream.yml:/mnt/litestream/litestream.yml
- backup:/mnt
- data:/mnt/data
command: replicate -config /mnt/litestream/litestream.yml
depends_on:
app:
condition: service_started
volumes:
azurite-data:
backup:
data:
# seed-data:

14535
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@
}, },
"workspaces": [ "workspaces": [
"api", "api",
"cms", "client",
"static-wfe" "static-wfe"
] ]
} }