diff --git a/.gitignore b/.gitignore index ac4b1b3..ed2ffd5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,9 @@ +.vscode/* +node_modules +**/.env +**/.env.* +**/*.env .DS_Store -*.auto.tfvars \ No newline at end of file +.dapr +**/**/secrets.json +.prod \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0b0017e --- /dev/null +++ b/Dockerfile @@ -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"] \ No newline at end of file diff --git a/api/src/config/configuration.ts b/api/src/config/configuration.ts index b53cc89..8c39b2b 100644 --- a/api/src/config/configuration.ts +++ b/api/src/config/configuration.ts @@ -6,5 +6,11 @@ export default () => ({ clientSecret: process.env.CLIENT_SECRET, issuer: process.env.ISSUER_URL, 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 }) \ No newline at end of file diff --git a/api/src/database/data-source.ts b/api/src/database/data-source.ts index e401963..31950bc 100644 --- a/api/src/database/data-source.ts +++ b/api/src/database/data-source.ts @@ -12,7 +12,7 @@ export const dataSourceOptions: DataSourceOptions & SeederOptions = { database: configService.get('DB_PATH'), entities: ['../**/*.entity.js'], migrations: ['dist/database/migrations/**/*.js'], - migrationsRun: false, + migrationsRun: configService.get('DB_MIGRATIONS_RUN'), seeds: ['dist/database/seeds/**/*.ts'], synchronize: configService.get('DB_SYNC') } diff --git a/api/src/database/migrations/1782738482901-initial_migration.ts b/api/src/database/migrations/1784577734670-initial_migration.ts similarity index 52% rename from api/src/database/migrations/1782738482901-initial_migration.ts rename to api/src/database/migrations/1784577734670-initial_migration.ts index 901c874..d507028 100644 --- a/api/src/database/migrations/1782738482901-initial_migration.ts +++ b/api/src/database/migrations/1784577734670-initial_migration.ts @@ -1,56 +1,122 @@ import { MigrationInterface, QueryRunner } from "typeorm"; -export class InitialMigration1782738482901 implements MigrationInterface { - name = 'InitialMigration1782738482901' +export class InitialMigration1784577734670 implements MigrationInterface { + name = 'InitialMigration1784577734670' public async up(queryRunner: QueryRunner): Promise { 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(`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 "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 "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 "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 "profiles_skills_skills" ("profilesId" varchar NOT NULL, "skillsId" varchar NOT NULL, PRIMARY KEY ("profilesId", "skillsId"))`); + 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(`CREATE TABLE "experiences_skills_skills" ("experiencesId" varchar NOT NULL, "skillsId" varchar NOT NULL, PRIMARY KEY ("experiencesId", "skillsId"))`); + 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(`CREATE TABLE "projects_skills_skills" ("projectsId" varchar NOT NULL, "skillsId" varchar NOT NULL, PRIMARY KEY ("projectsId", "skillsId"))`); + await queryRunner.query(`CREATE INDEX "IDX_d8128e9b444108a09d81529ea0" ON "projects_skills_skills" ("projectsId") `); + await queryRunner.query(`CREATE INDEX "IDX_5291aa5a2295552d375007fefe" ON "projects_skills_skills" ("skillsId") `); + 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(`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"`); + 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 { - 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(`DROP INDEX "IDX_5291aa5a2295552d375007fefe"`); + await queryRunner.query(`DROP INDEX "IDX_d8128e9b444108a09d81529ea0"`); + await queryRunner.query(`ALTER TABLE "projects_skills_skills" RENAME TO "temporary_projects_skills_skills"`); + await queryRunner.query(`CREATE TABLE "projects_skills_skills" ("projectsId" varchar NOT NULL, "skillsId" varchar NOT NULL, PRIMARY KEY ("projectsId", "skillsId"))`); + await queryRunner.query(`INSERT INTO "projects_skills_skills"("projectsId", "skillsId") SELECT "projectsId", "skillsId" FROM "temporary_projects_skills_skills"`); + await queryRunner.query(`DROP TABLE "temporary_projects_skills_skills"`); + await queryRunner.query(`CREATE INDEX "IDX_5291aa5a2295552d375007fefe" ON "projects_skills_skills" ("skillsId") `); + 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(`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(`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(`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(`DROP TABLE "temporary_skills"`); - await queryRunner.query(`DROP TABLE "profiles"`); - await queryRunner.query(`DROP TABLE "experiences"`); + 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)`); + 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 "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_levels"`); await queryRunner.query(`DROP TABLE "skill_categories"`); diff --git a/api/src/experience/experience.controller.ts b/api/src/experience/experience.controller.ts index 2d49045..a9e96b5 100644 --- a/api/src/experience/experience.controller.ts +++ b/api/src/experience/experience.controller.ts @@ -31,6 +31,7 @@ export class ExperienceController { @Post() async create(@Body() experienceDto: ExperienceDto): Promise { + console.log(experienceDto) try { return await this.experienceService.create(experienceDto); } catch (error) { @@ -45,6 +46,7 @@ export class ExperienceController { @Param('id') id: string, @Body() ExperienceDto: ExperienceDto ) { + console.log(ExperienceDto) try { return await this.experienceService.update(id, ExperienceDto); } catch (error) { diff --git a/api/src/experience/experience.dto.ts b/api/src/experience/experience.dto.ts index 05112ed..70af0ea 100644 --- a/api/src/experience/experience.dto.ts +++ b/api/src/experience/experience.dto.ts @@ -1,3 +1,5 @@ +import { SkillEntity } from "src/skill/skill.entity"; + export class ExperienceDto { companyName: string; companyNameGeneric: string; @@ -6,7 +8,7 @@ export class ExperienceDto { startDate: Date; endDate?: Date; summary: string; - skills?: []; + skills?: SkillEntity[]; createdBy?: string; updatedBy?: string; deletedBy?: string; diff --git a/api/src/experience/experience.entity.ts b/api/src/experience/experience.entity.ts index 0bb8d3a..5bad570 100644 --- a/api/src/experience/experience.entity.ts +++ b/api/src/experience/experience.entity.ts @@ -1,7 +1,7 @@ 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'; +import { Entity, PrimaryGeneratedColumn, Column, JoinColumn, ManyToOne, OneToMany, OneToOne, CreateDateColumn, UpdateDateColumn, DeleteDateColumn, ManyToMany, JoinTable } from 'typeorm'; @Entity({ name: 'experiences' }) export class ExperienceEntity { @@ -50,14 +50,15 @@ export class ExperienceEntity { @Column({ nullable: true }) deletedBy: string; - @OneToMany(() => SkillEntity, (skill: SkillEntity) => skill.name ) - skills: SkillEntity[]; + @ManyToOne(() => StatusEntity, (status: StatusEntity) => status.name ) + @JoinColumn({ name: 'statusId' }) + status: StatusEntity; @ManyToOne(() => ProfileEntity, (profile: ProfileEntity) => profile.experiences) @JoinColumn({ name: 'profileId' }) - profile: ProfileEntity; + profile: ProfileEntity; - @OneToOne(() => StatusEntity, (status: StatusEntity) => status.name ) - @JoinColumn({ name: 'statusId' }) - status: StatusEntity; + @ManyToMany(() => SkillEntity, (skill: SkillEntity) => skill.name ) + @JoinTable() + skills: SkillEntity[]; } \ No newline at end of file diff --git a/api/src/experience/experience.service.ts b/api/src/experience/experience.service.ts index 2bc5953..cc93a75 100644 --- a/api/src/experience/experience.service.ts +++ b/api/src/experience/experience.service.ts @@ -13,13 +13,13 @@ export class ExperienceService { async find(id: string): Promise { return await this.experienceRepository.findOne({ where: { id: id }, - relations: ['profile', 'status'] + relations: ['profile', 'skills', 'status'] }) } async findAll(): Promise { return await this.experienceRepository.find({ - relations: ['profile', 'status'] + relations: ['profile', 'skills', 'status'] }); } diff --git a/api/src/index.css b/api/src/index.css deleted file mode 100644 index 05ebb04..0000000 --- a/api/src/index.css +++ /dev/null @@ -1,3 +0,0 @@ -body { - background-color: #f2f2f2; -} \ No newline at end of file diff --git a/api/src/profile/profile.controller.ts b/api/src/profile/profile.controller.ts index 6faf421..329293b 100644 --- a/api/src/profile/profile.controller.ts +++ b/api/src/profile/profile.controller.ts @@ -38,7 +38,7 @@ export class ProfileController { return await this.profileService.findAll(); } catch (error) { const customError = error as CustomError; - + console.log(error) throw new HttpException(customError.message, customError.statusCode); } } diff --git a/api/src/profile/profile.dto.ts b/api/src/profile/profile.dto.ts index 18de0ad..d0147f3 100644 --- a/api/src/profile/profile.dto.ts +++ b/api/src/profile/profile.dto.ts @@ -1,9 +1,12 @@ +import { SkillEntity } from "src/skill/skill.entity"; + export class ProfileDto { name: string; headline: string; + skills: SkillEntity[]; + statusId: number; summary: string; userId: string; - statusId: number; createdBy?: string; updatedBy?: string; deletedBy?: string; diff --git a/api/src/profile/profile.entity.ts b/api/src/profile/profile.entity.ts index 7dcd4b9..36a57cd 100644 --- a/api/src/profile/profile.entity.ts +++ b/api/src/profile/profile.entity.ts @@ -1,7 +1,7 @@ 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'; +import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn, UpdateDateColumn, DeleteDateColumn, OneToOne, JoinColumn, ManyToOne, ManyToMany, JoinTable } from 'typeorm'; @Entity({ name: 'profiles' }) export class ProfileEntity { @@ -44,10 +44,11 @@ export class ProfileEntity { @OneToMany(() => ExperienceEntity, (experience: ExperienceEntity) => experience.profile) experiences: ExperienceEntity[] - @OneToMany(() => SkillEntity, (skill: SkillEntity) => skill.name ) - skills: SkillEntity[]; - - @OneToOne(() => StatusEntity, (status: StatusEntity) => status.name ) + @ManyToOne(() => StatusEntity, (status: StatusEntity) => status.name ) @JoinColumn({ name: 'statusId' }) status: StatusEntity; + + @ManyToMany(() => SkillEntity, (skill: SkillEntity) => skill.name ) + @JoinTable() + skills: SkillEntity[]; } \ No newline at end of file diff --git a/api/src/profile/profile.service.ts b/api/src/profile/profile.service.ts index accc256..27a5425 100644 --- a/api/src/profile/profile.service.ts +++ b/api/src/profile/profile.service.ts @@ -13,7 +13,7 @@ export class ProfileService { async find(id: string): Promise { const profile = await this.profileRepository.findOne({ where: { id: id }, - relations: ['status'] + relations: ['skills', 'status'] }); console.log(profile); @@ -22,7 +22,7 @@ export class ProfileService { async findAll(): Promise { return await this.profileRepository.find({ - relations: ['status'] + relations: ['skills', 'status'] }); } diff --git a/api/src/project/project.dto.ts b/api/src/project/project.dto.ts index b4870bb..d163121 100644 --- a/api/src/project/project.dto.ts +++ b/api/src/project/project.dto.ts @@ -1,3 +1,5 @@ +import { SkillEntity } from "src/skill/skill.entity"; + export class ProjectDto { id: string; name: string; @@ -6,7 +8,7 @@ export class ProjectDto { summary: string; repoUrl?: string; siteUrl?: string; - skills?: []; + skills?: SkillEntity[]; createdBy?: string; updatedBy?: string; deletedBy?: string; diff --git a/api/src/project/project.entity.ts b/api/src/project/project.entity.ts index f3fab90..74e9178 100644 --- a/api/src/project/project.entity.ts +++ b/api/src/project/project.entity.ts @@ -1,6 +1,6 @@ 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'; +import { Entity, PrimaryGeneratedColumn, Column, OneToMany, OneToOne, JoinColumn, CreateDateColumn, UpdateDateColumn, DeleteDateColumn, ManyToOne, ManyToMany, JoinTable } from 'typeorm'; @Entity({ name: 'projects' }) export class ProjectEntity { @@ -46,10 +46,11 @@ export class ProjectEntity { @Column({ nullable: true }) deletedBy: string - @OneToMany(() => SkillEntity, (skill: SkillEntity) => skill.name ) - skills: SkillEntity[]; - - @OneToOne(() => StatusEntity, (status: StatusEntity) => status.name ) + @ManyToOne(() => StatusEntity, (status: StatusEntity) => status.name ) @JoinColumn({ name: 'statusId' }) - status: StatusEntity; + status: StatusEntity; + + @ManyToMany(() => SkillEntity, (skill: SkillEntity) => skill.name ) + @JoinTable() + skills: SkillEntity[]; } diff --git a/api/src/project/project.service.ts b/api/src/project/project.service.ts index 213c0c1..2351314 100644 --- a/api/src/project/project.service.ts +++ b/api/src/project/project.service.ts @@ -13,16 +13,15 @@ export class ProjectService { async find(id: string): Promise { const project = await this.projectRepository.findOne({ where: { id: id }, - relations: ['status'] + relations: ['skills', 'status'] }); - console.log(project); return project; } async findAll(): Promise { return await this.projectRepository.find({ - relations: ['status'] + relations: ['skills', 'status'] }); } diff --git a/api/src/skill/skill-level.entity.ts b/api/src/skill/skill-level.entity.ts index 1db45a1..9a05fb9 100644 --- a/api/src/skill/skill-level.entity.ts +++ b/api/src/skill/skill-level.entity.ts @@ -26,8 +26,5 @@ export class SkillLevelEntity { @Column({ nullable: true }) deletedBy: string; - - @OneToMany(() => SkillEntity, (skill: SkillEntity) => skill.name) - skills: SkillEntity[]; } diff --git a/api/src/skill/skill.entity.ts b/api/src/skill/skill.entity.ts index 2d722b3..ac34953 100644 --- a/api/src/skill/skill.entity.ts +++ b/api/src/skill/skill.entity.ts @@ -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 { SkillLevelEntity } from './skill-level.entity'; import { SkillRankEntity } from './skill-rank.entity'; +import { ExperienceEntity } from 'src/experience/experience.entity'; @Entity({ name: 'skills' }) export class SkillEntity { @@ -42,11 +43,14 @@ export class SkillEntity { @JoinColumn({ name: 'categoryId' }) category: SkillCategoryEntity; - @OneToOne(() => SkillRankEntity, (rank: SkillRankEntity) => rank.name) + @ManyToOne(() => SkillRankEntity, (rank: SkillRankEntity) => rank.name) @JoinColumn({ name: 'rankId' }) rank: SkillRankEntity; @ManyToOne(() => SkillLevelEntity, (level: SkillLevelEntity) => level.name) @JoinColumn({ name: 'levelId' }) level: SkillLevelEntity; + + @ManyToMany(() => ExperienceEntity, (experience: ExperienceEntity) => experience.skills) + experiences: ExperienceEntity[] } \ No newline at end of file diff --git a/client/src/components/SkillCategoryForm/SkillCategoryForm.tsx b/client/src/components/SkillCategoryForm/SkillCategoryForm.tsx index b28fe9e..cda9566 100644 --- a/client/src/components/SkillCategoryForm/SkillCategoryForm.tsx +++ b/client/src/components/SkillCategoryForm/SkillCategoryForm.tsx @@ -9,11 +9,15 @@ import { AxiosError, AxiosResponse } from "axios"; import { ScreenSize } from "../../enums/screenSize"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faSave, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { useAppContext } from "../../hooks/appContext/UseAppContext"; const SkillCategoryForm = ({ isDrawerOpen, mode, onOpenClose, categoryId }: SkillCategoryFormProps) => { const [state, dispatch] = useReducer(reducer, initialState); + const appContext = useAppContext(); const defaultValues = { - name: '' + name: '', + createdBy: '', + updatedBy: '' } const methods = useForm({ defaultValues: defaultValues @@ -26,14 +30,25 @@ const SkillCategoryForm = ({ isDrawerOpen, mode, onOpenClose, categoryId }: Skil onOpenClose(FormMode.CANCEL) } - const onSubmit = async (data: unknown) => { + const onSubmit = async (data: any) => { try { dispatch({ type: 'SET_IS_LOADING', payload: true }) 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 { - await httpClient.put(`api/skill-categories`) + const newData = { + ...data, + updatedBy: appContext.state.userProfile.userPrincipalName + } + + await httpClient.put(`api/skill-categories`, newData) } methods.reset(defaultValues); diff --git a/client/src/components/SkillForm/SkillForm.tsx b/client/src/components/SkillForm/SkillForm.tsx index dfb23f5..7397ded 100644 --- a/client/src/components/SkillForm/SkillForm.tsx +++ b/client/src/components/SkillForm/SkillForm.tsx @@ -9,14 +9,18 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faSave, faXmark } from '@fortawesome/free-solid-svg-icons'; import { ScreenSize } from '../../enums/screenSize'; import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints'; +import { useAppContext } from '../../hooks/appContext/UseAppContext'; const SkillForm = ({ isDrawerOpen, mode, onOpenClose, skillId }: SkillFormProps) => { const [state, dispatch] = useReducer(reducer, initialState); + const appContext = useAppContext(); const defaultValues = { name: '', categoryId: '', levelId: '', - rankId: '' + rankId: '', + createdBy: '', + updatedBy: '' } const methods = useForm({ defaultValues: defaultValues @@ -29,14 +33,25 @@ const SkillForm = ({ isDrawerOpen, mode, onOpenClose, skillId }: SkillFormProps) onOpenClose(FormMode.CANCEL); }; - const onSubmit = async (data: unknown) => { + const onSubmit = async (data: any) => { try { dispatch({ type: 'SET_IS_LOADING', payload: true }); console.log(data) 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 { - 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); diff --git a/client/src/components/experienceForm/ExperienceForm.tsx b/client/src/components/experienceForm/ExperienceForm.tsx index c52bcb9..31a80da 100644 --- a/client/src/components/experienceForm/ExperienceForm.tsx +++ b/client/src/components/experienceForm/ExperienceForm.tsx @@ -12,14 +12,17 @@ import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints'; import { Profile } from '../profiles/Profile.interface'; import { useProfiles } from '../../hooks/profiles/UseProfiles'; import { useAppContext } from '../../hooks/appContext/UseAppContext'; -import { Listbox, ListboxButton, ListboxOption, ListboxOptions } from '@headlessui/react'; 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 [state, dispatch] = useReducer(reducer, initialState); const appContext = useAppContext(); const { profiles } = useProfiles(); const { skills } = useSkills(); + const experienceSkills: Skill[] = [] const defaultValues = { profileId: '', companyName: '', @@ -27,7 +30,7 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper title: '', startDate: '', endDate: '', - skills: [], + skills: experienceSkills, summary: '', statusId: 1, createdBy: '', @@ -36,9 +39,23 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper const methods = useForm({ defaultValues: defaultValues }); - // const watchRepoName = methods.watch(['repoName']) + 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 = () => { methods.reset(defaultValues); dispatch({ type: 'SET_IS_DISABLED', payload: false }); @@ -91,6 +108,10 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper `api/experiences/${experienceId}` ); const experience = response.data; + + if (experience.skills) { + dispatch({ type: 'SET_SELECTED_SKILLS', payload: experience.skills }) + } methods.reset(experience); } catch (error) { @@ -127,7 +148,9 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper }, [profiles]); useEffect(() => { - dispatch({ type: 'SET_SKILL_OPTIONS', payload: skills }) + if (skills) { + dispatch({ type: 'SET_SKILL_OPTIONS', payload: skills }) + } }, [skills]) return ( @@ -296,23 +319,12 @@ const ExperienceForm = ({ isDrawerOpen, mode, onOpenClose, experienceId }: Exper ( - // - - - {state.skillOptions?.map((skill) => ( - - {skill.name} - - ))} - - + render={() => ( + )} /> diff --git a/client/src/components/experienceForm/ExperienceFormState.interface.ts b/client/src/components/experienceForm/ExperienceFormState.interface.ts index 4868aed..1994505 100644 --- a/client/src/components/experienceForm/ExperienceFormState.interface.ts +++ b/client/src/components/experienceForm/ExperienceFormState.interface.ts @@ -6,5 +6,6 @@ export interface ExperienceFormState { isDisabled: boolean; isLoading: boolean; profileOptions: Profile[]; - skillOptions: Skill[] | undefined; + selectedSkills: Skill[]; + skillOptions: Skill[]; } \ No newline at end of file diff --git a/client/src/components/experienceForm/reducer.ts b/client/src/components/experienceForm/reducer.ts index 4a90b83..4c4c33a 100644 --- a/client/src/components/experienceForm/reducer.ts +++ b/client/src/components/experienceForm/reducer.ts @@ -7,13 +7,15 @@ type Action = | { type: 'SET_IS_DISABLED'; payload: boolean } | { type: 'SET_IS_LOADING'; payload: boolean } | { 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 = { error: undefined, isDisabled: false, isLoading: true, profileOptions: [], + selectedSkills: [], skillOptions: [] }; @@ -46,6 +48,12 @@ export const reducer = ( profileOptions: action.payload } } + case 'SET_SELECTED_SKILLS': { + return { + ...state, + selectedSkills: action.payload + } + } case 'SET_SKILL_OPTIONS': { return { ...state, diff --git a/client/src/components/experiences/Experience.interface.ts b/client/src/components/experiences/Experience.interface.ts index 2dff29f..e3a982e 100644 --- a/client/src/components/experiences/Experience.interface.ts +++ b/client/src/components/experiences/Experience.interface.ts @@ -12,7 +12,7 @@ export interface Experience { startDate: Date; endDate?: Date; summary?: string; - skills: Skill[]; + skills?: Skill[]; profile: Profile; status: Status; } \ No newline at end of file diff --git a/client/src/components/experiences/Experiences.tsx b/client/src/components/experiences/Experiences.tsx index c87477a..9ef336f 100644 --- a/client/src/components/experiences/Experiences.tsx +++ b/client/src/components/experiences/Experiences.tsx @@ -17,6 +17,7 @@ import { useUserRole } from '../../hooks/userRole/UseUserRole'; import Alert from '../../alert/Alert'; import { CellContext, ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, useReactTable } from '@tanstack/react-table'; import { Profile } from '../profiles/Profile.interface'; +import { Skill } from '../skills/Skill.interface'; interface ActionsProps { id: string; @@ -166,6 +167,17 @@ const Experiences = () => { accessorKey: 'summary', header: 'Summary' }, + { + id: 'skills', + accessorKey: 'skills', + header: 'Skills', + cell: (info: CellContext) => { + const skills = info.getValue() as Skill[]; + const skillsString: string = skills.map((skill) => skill.name).join(', ') + + return skillsString + } + }, { id: 'status', accessorKey: 'status', diff --git a/client/src/components/multiSelectDropdown/MultiSelectDropdown.tsx b/client/src/components/multiSelectDropdown/MultiSelectDropdown.tsx index 195ea22..24cfa3c 100644 --- a/client/src/components/multiSelectDropdown/MultiSelectDropdown.tsx +++ b/client/src/components/multiSelectDropdown/MultiSelectDropdown.tsx @@ -1,32 +1,33 @@ import { useEffect, useRef, useState } from "react"; 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 [selectedValues, setSelectedValues] = useState([]) + // const [selectedValues, setSelectedValues] = useState([]) const dropdownRef = useRef(null) const handleToggleOption = (value: any) => { - let updated; + // let updated; - if (selectedValues.includes(value)) { - updated = selectedValues.filter((item) => item !== value); - } else { - updated = [...selectedValues, value]; - } + // if (selectedValues.includes(value)) { + // updated = selectedValues.filter((item) => item !== value); + // } else { + // updated = [...selectedValues, value]; + // } - setSelectedValues(updated); - if (onChange) onChange(updated) + // setSelectedValues(updated); + // if (onChange) onChange(updated) } 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(() => { @@ -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" onClick={() => setIsOpen(!isOpen)} > - {selectedValues.length === 0 ? ( + {selectedSkills.length === 0 ? ( ) : (
- {selectedValues.map((val) => { - const option = options.find((o: any) => o.value === val); + {selectedSkills.map((selectedSkill: Skill) => { + const option = skills.find((skill: Skill) => skill.id === selectedSkill.id); return ( -
- {option?.label || val} - +
+ {option?.name || selectedSkill.name}
); })} @@ -75,16 +70,16 @@ const MultiSelectDropdown = ({ options }: MultiSelectDropdownProps) => { 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" > - {options.map((option) => ( -
  • + {skills.map((skill) => ( +
  • ))} diff --git a/client/src/components/multiSelectDropdown/MultiSelectDropdownProps.ts b/client/src/components/multiSelectDropdown/MultiSelectDropdownProps.ts index d52c1f3..6a88666 100644 --- a/client/src/components/multiSelectDropdown/MultiSelectDropdownProps.ts +++ b/client/src/components/multiSelectDropdown/MultiSelectDropdownProps.ts @@ -1,3 +1,7 @@ +import { Skill } from "../skills/Skill.interface"; + export interface MultiSelectDropdownProps { - options: { label: string; value: string }[]; + onChange: (selectedSkill: Skill) => void; + skills: Skill[]; + selectedSkills: Skill[]; } \ No newline at end of file diff --git a/client/src/components/profileForm/ProfileForm.tsx b/client/src/components/profileForm/ProfileForm.tsx index 6b59bde..9f89032 100644 --- a/client/src/components/profileForm/ProfileForm.tsx +++ b/client/src/components/profileForm/ProfileForm.tsx @@ -10,15 +10,21 @@ import { faSave, faXmark } from '@fortawesome/free-solid-svg-icons'; import { ScreenSize } from '../../enums/screenSize'; import { useBreakpoints } from '../../hooks/breakpoints/UseBreakpoints'; 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 [state, dispatch] = useReducer(reducer, initialState); const appContext = useAppContext(); + const { skills } = useSkills(); + const projectSkills: Skill[] = [] const defaultValues = { name: '', headline: '', summary: '', userId: '', + skills: projectSkills, statusId: 1 } const methods = useForm({ @@ -27,6 +33,20 @@ const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileForm // const watchRepoName = methods.watch(['repoName']) 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 = () => { methods.reset(defaultValues); dispatch({ type: 'SET_IS_DISABLED', payload: false }); @@ -80,9 +100,13 @@ const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileForm const response: AxiosResponse = await httpClient.get( `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) { const axiosError = error as AxiosError; console.log(axiosError) @@ -97,6 +121,12 @@ const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileForm } }, [profileId]); + useEffect(() => { + if (skills) { + dispatch({ type: 'SET_SKILL_OPTIONS', payload: skills }) + } + }, [skills]) + return (
    {}} checked={isDrawerOpen} /> @@ -175,6 +205,22 @@ const ProfileForm = ({ isDrawerOpen, mode, onOpenClose, profileId }: ProfileForm )} />
    +
    + Skills +
    +
    + ( + + )} + /> +