From 39e08a419912e4a37304ff1303548bcdc9f8bb90 Mon Sep 17 00:00:00 2001 From: Michael Haufe Date: Wed, 23 Oct 2024 20:48:24 +0000 Subject: [PATCH] - styling: datatables now have striped rows - refactor: renamed enpoints to align with ReqType enum - refactor: parent component properties moved to Belongs relations --- components/WorkboxDataView.vue | 109 -- components/XDataTable.vue | 2 +- middleware/org-solution-check.global.ts | 4 +- migrations/.snapshot-cathedral.json | 1592 ++++++++--------- migrations/Migration20241020164456.ts | 236 +++ migrations/Migration20241022160037.ts | 104 ++ pages/index.client.vue | 4 +- pages/new-organization.vue | 4 +- .../[solution-slug]/edit-entry.client.vue | 4 +- .../environment/assumptions.client.vue | 10 +- .../environment/components.client.vue | 10 +- .../environment/constraints.client.vue | 12 +- .../environment/effects.client.vue | 10 +- .../environment/glossary.client.vue | 10 +- .../environment/invariants.client.vue | 10 +- .../goals/functionality.client.vue | 10 +- .../goals/limitations.client.vue | 10 +- .../goals/obstacles.client.vue | 10 +- .../[solution-slug]/goals/outcomes.client.vue | 10 +- .../goals/rationale.client.vue | 12 +- .../goals/scenarios.client.vue | 16 +- .../goals/stakeholders.client.vue | 24 +- .../[solution-slug]/index.client.vue | 6 +- .../project/roles-personnel.client.vue | 10 +- .../system/components.client.vue | 19 +- .../system/functionality.client.vue | 12 +- .../system/scenarios.client.vue | 54 +- .../[solution-slug]/workbox.client.vue | 110 +- .../[organization-slug]/edit-entry.client.vue | 4 +- pages/o/[organization-slug]/index.client.vue | 8 +- .../new-solution.client.vue | 6 +- pages/o/[organization-slug]/users.vue | 2 +- server/api/appusers/[id].delete.ts | 3 +- .../[id].delete.ts | 0 .../{assumptions => assumption}/[id].get.ts | 1 - .../{assumptions => assumption}/[id].put.ts | 2 +- .../{assumptions => assumption}/index.get.ts | 0 .../{assumptions => assumption}/index.post.ts | 0 server/api/audit-log/deleted.get.ts | 3 +- server/api/auth/[...].ts | 2 +- .../[id].delete.ts | 0 .../{constraints => constraint}/[id].get.ts | 1 - .../{constraints => constraint}/[id].put.ts | 2 +- .../{constraints => constraint}/index.get.ts | 0 .../{constraints => constraint}/index.post.ts | 0 server/api/{effects => effect}/[id].delete.ts | 0 server/api/{effects => effect}/[id].get.ts | 1 - server/api/{effects => effect}/[id].put.ts | 2 +- server/api/{effects => effect}/index.get.ts | 0 server/api/{effects => effect}/index.post.ts | 0 .../[id].delete.ts | 0 .../[id].get.ts | 2 - .../[id].put.ts | 22 +- .../index.get.ts | 0 .../index.post.ts | 6 +- .../[id].delete.ts | 0 .../[id].get.ts | 1 - .../[id].put.ts | 2 +- .../index.get.ts | 0 .../index.post.ts | 0 .../[id].delete.ts | 0 .../[id].get.ts | 1 - .../[id].put.ts | 2 +- .../index.get.ts | 0 .../index.post.ts | 7 +- .../{invariants => invariant}/[id].delete.ts | 0 .../api/{invariants => invariant}/[id].get.ts | 1 - .../api/{invariants => invariant}/[id].put.ts | 2 +- .../{invariants => invariant}/index.get.ts | 0 .../{invariants => invariant}/index.post.ts | 0 .../[id].delete.ts | 0 .../[id].get.ts | 1 - .../[id].put.ts | 2 +- .../index.get.ts | 0 .../index.post.ts | 0 server/api/{limits => limit}/[id].delete.ts | 0 server/api/{limits => limit}/[id].get.ts | 1 - server/api/{limits => limit}/[id].put.ts | 2 +- server/api/{limits => limit}/index.get.ts | 0 server/api/{limits => limit}/index.post.ts | 0 .../[id].delete.ts | 0 .../[id].get.ts | 1 - .../[id].put.ts | 0 .../index.get.ts | 0 .../index.post.ts | 0 .../{obstacles => obstacle}/[id].delete.ts | 0 .../api/{obstacles => obstacle}/[id].get.ts | 1 - .../api/{obstacles => obstacle}/[id].put.ts | 2 +- .../api/{obstacles => obstacle}/index.get.ts | 0 .../api/{obstacles => obstacle}/index.post.ts | 0 .../[id].delete.ts | 0 .../[id].get.ts | 0 .../[id].put.ts | 2 +- .../index.get.ts | 0 .../index.post.ts | 0 .../api/{outcomes => outcome}/[id].delete.ts | 0 server/api/{outcomes => outcome}/[id].get.ts | 1 - server/api/{outcomes => outcome}/[id].put.ts | 2 +- server/api/{outcomes => outcome}/index.get.ts | 0 .../api/{outcomes => outcome}/index.post.ts | 0 server/api/parse-requirement/follows.get.ts | 30 + .../index.get.ts | 1 - .../index.post.ts | 27 +- server/api/{persons => person}/[id].delete.ts | 0 server/api/{persons => person}/[id].get.ts | 1 - server/api/{persons => person}/[id].put.ts | 2 +- server/api/{persons => person}/index.get.ts | 0 server/api/{persons => person}/index.post.ts | 0 .../{solutions => solution}/[id].delete.ts | 0 .../api/{solutions => solution}/[id].get.ts | 0 .../api/{solutions => solution}/[id].put.ts | 2 +- .../api/{solutions => solution}/index.get.ts | 0 .../api/{solutions => solution}/index.post.ts | 1 - .../[id].delete.ts | 0 .../{stakeholders => stakeholder}/[id].get.ts | 1 - .../{stakeholders => stakeholder}/[id].put.ts | 24 +- .../index.get.ts | 0 .../index.post.ts | 8 +- .../[id].delete.ts | 0 .../[id].get.ts | 1 - .../[id].put.ts | 27 +- server/api/system-component/index.get.ts | 42 + .../index.post.ts | 10 +- server/api/system-components/index.get.ts | 23 - .../{use-cases => use-case}/[id].delete.ts | 0 .../api/{use-cases => use-case}/[id].get.ts | 1 - .../api/{use-cases => use-case}/[id].put.ts | 20 +- .../api/{use-cases => use-case}/index.get.ts | 8 +- .../api/{use-cases => use-case}/index.post.ts | 12 +- .../[id].delete.ts | 0 .../{user-stories => user-story}/[id].get.ts | 1 - .../{user-stories => user-story}/[id].put.ts | 20 +- .../{user-stories => user-story}/index.get.ts | 2 +- .../index.post.ts | 12 +- server/domain/application/AuditLog.ts | 8 +- server/domain/relations/Characterizes.ts | 2 +- .../domain/relations/RequirementRelation.ts | 6 +- server/domain/relations/index.ts | 1 - .../requirements/EnvironmentComponent.ts | 11 +- server/domain/requirements/GlossaryTerm.ts | 11 +- .../domain/requirements/ParsedRequirement.ts | 8 +- server/domain/requirements/ReqType.ts | 4 + server/domain/requirements/Stakeholder.ts | 7 - server/domain/requirements/SystemComponent.ts | 7 - server/domain/types/index.ts | 12 +- server/utils/findAllSolutionRequirements.ts | 45 +- server/utils/groupBy.ts | 10 + utils/snakeCaseToSlug.ts | 7 + utils/snakeCaseToTitle.ts | 8 + 149 files changed, 1623 insertions(+), 1322 deletions(-) delete mode 100644 components/WorkboxDataView.vue create mode 100644 migrations/Migration20241020164456.ts create mode 100644 migrations/Migration20241022160037.ts rename server/api/{assumptions => assumption}/[id].delete.ts (100%) rename server/api/{assumptions => assumption}/[id].get.ts (93%) rename server/api/{assumptions => assumption}/[id].put.ts (96%) rename server/api/{assumptions => assumption}/index.get.ts (100%) rename server/api/{assumptions => assumption}/index.post.ts (100%) rename server/api/{constraints => constraint}/[id].delete.ts (100%) rename server/api/{constraints => constraint}/[id].get.ts (93%) rename server/api/{constraints => constraint}/[id].put.ts (96%) rename server/api/{constraints => constraint}/index.get.ts (100%) rename server/api/{constraints => constraint}/index.post.ts (100%) rename server/api/{effects => effect}/[id].delete.ts (100%) rename server/api/{effects => effect}/[id].get.ts (92%) rename server/api/{effects => effect}/[id].put.ts (96%) rename server/api/{effects => effect}/index.get.ts (100%) rename server/api/{effects => effect}/index.post.ts (100%) rename server/api/{environment-components => environment-component}/[id].delete.ts (100%) rename server/api/{environment-components => environment-component}/[id].get.ts (84%) rename server/api/{environment-components => environment-component}/[id].put.ts (64%) rename server/api/{environment-components => environment-component}/index.get.ts (100%) rename server/api/{environment-components => environment-component}/index.post.ts (89%) rename server/api/{functional-behaviors => functional-behavior}/[id].delete.ts (100%) rename server/api/{functional-behaviors => functional-behavior}/[id].get.ts (93%) rename server/api/{functional-behaviors => functional-behavior}/[id].put.ts (96%) rename server/api/{functional-behaviors => functional-behavior}/index.get.ts (100%) rename server/api/{functional-behaviors => functional-behavior}/index.post.ts (100%) rename server/api/{glossary-terms => glossary-term}/[id].delete.ts (100%) rename server/api/{glossary-terms => glossary-term}/[id].get.ts (93%) rename server/api/{glossary-terms => glossary-term}/[id].put.ts (96%) rename server/api/{glossary-terms => glossary-term}/index.get.ts (100%) rename server/api/{glossary-terms => glossary-term}/index.post.ts (76%) rename server/api/{invariants => invariant}/[id].delete.ts (100%) rename server/api/{invariants => invariant}/[id].get.ts (93%) rename server/api/{invariants => invariant}/[id].put.ts (96%) rename server/api/{invariants => invariant}/index.get.ts (100%) rename server/api/{invariants => invariant}/index.post.ts (100%) rename server/api/{justifications => justification}/[id].delete.ts (100%) rename server/api/{justifications => justification}/[id].get.ts (93%) rename server/api/{justifications => justification}/[id].put.ts (96%) rename server/api/{justifications => justification}/index.get.ts (100%) rename server/api/{justifications => justification}/index.post.ts (100%) rename server/api/{limits => limit}/[id].delete.ts (100%) rename server/api/{limits => limit}/[id].get.ts (92%) rename server/api/{limits => limit}/[id].put.ts (96%) rename server/api/{limits => limit}/index.get.ts (100%) rename server/api/{limits => limit}/index.post.ts (100%) rename server/api/{non-functional-behaviors => non-functional-behavior}/[id].delete.ts (100%) rename server/api/{non-functional-behaviors => non-functional-behavior}/[id].get.ts (93%) rename server/api/{non-functional-behaviors => non-functional-behavior}/[id].put.ts (100%) rename server/api/{non-functional-behaviors => non-functional-behavior}/index.get.ts (100%) rename server/api/{non-functional-behaviors => non-functional-behavior}/index.post.ts (100%) rename server/api/{obstacles => obstacle}/[id].delete.ts (100%) rename server/api/{obstacles => obstacle}/[id].get.ts (92%) rename server/api/{obstacles => obstacle}/[id].put.ts (96%) rename server/api/{obstacles => obstacle}/index.get.ts (100%) rename server/api/{obstacles => obstacle}/index.post.ts (100%) rename server/api/{organizations => organization}/[id].delete.ts (100%) rename server/api/{organizations => organization}/[id].get.ts (100%) rename server/api/{organizations => organization}/[id].put.ts (94%) rename server/api/{organizations => organization}/index.get.ts (100%) rename server/api/{organizations => organization}/index.post.ts (100%) rename server/api/{outcomes => outcome}/[id].delete.ts (100%) rename server/api/{outcomes => outcome}/[id].get.ts (92%) rename server/api/{outcomes => outcome}/[id].put.ts (96%) rename server/api/{outcomes => outcome}/index.get.ts (100%) rename server/api/{outcomes => outcome}/index.post.ts (100%) create mode 100644 server/api/parse-requirement/follows.get.ts rename server/api/{parse-requirements => parse-requirement}/index.get.ts (92%) rename server/api/{parse-requirements => parse-requirement}/index.post.ts (92%) rename server/api/{persons => person}/[id].delete.ts (100%) rename server/api/{persons => person}/[id].get.ts (92%) rename server/api/{persons => person}/[id].put.ts (96%) rename server/api/{persons => person}/index.get.ts (100%) rename server/api/{persons => person}/index.post.ts (100%) rename server/api/{solutions => solution}/[id].delete.ts (100%) rename server/api/{solutions => solution}/[id].get.ts (100%) rename server/api/{solutions => solution}/[id].put.ts (94%) rename server/api/{solutions => solution}/index.get.ts (100%) rename server/api/{solutions => solution}/index.post.ts (94%) rename server/api/{stakeholders => stakeholder}/[id].delete.ts (100%) rename server/api/{stakeholders => stakeholder}/[id].get.ts (93%) rename server/api/{stakeholders => stakeholder}/[id].put.ts (67%) rename server/api/{stakeholders => stakeholder}/index.get.ts (100%) rename server/api/{stakeholders => stakeholder}/index.post.ts (88%) rename server/api/{system-components => system-component}/[id].delete.ts (100%) rename server/api/{system-components => system-component}/[id].get.ts (93%) rename server/api/{system-components => system-component}/[id].put.ts (50%) create mode 100644 server/api/system-component/index.get.ts rename server/api/{system-components => system-component}/index.post.ts (75%) delete mode 100644 server/api/system-components/index.get.ts rename server/api/{use-cases => use-case}/[id].delete.ts (100%) rename server/api/{use-cases => use-case}/[id].get.ts (92%) rename server/api/{use-cases => use-case}/[id].put.ts (82%) rename server/api/{use-cases => use-case}/index.get.ts (82%) rename server/api/{use-cases => use-case}/index.post.ts (77%) rename server/api/{user-stories => user-story}/[id].delete.ts (100%) rename server/api/{user-stories => user-story}/[id].get.ts (92%) rename server/api/{user-stories => user-story}/[id].put.ts (77%) rename server/api/{user-stories => user-story}/index.get.ts (92%) rename server/api/{user-stories => user-story}/index.post.ts (71%) create mode 100644 server/utils/groupBy.ts create mode 100644 utils/snakeCaseToSlug.ts create mode 100644 utils/snakeCaseToTitle.ts diff --git a/components/WorkboxDataView.vue b/components/WorkboxDataView.vue deleted file mode 100644 index 75d72785..00000000 --- a/components/WorkboxDataView.vue +++ /dev/null @@ -1,109 +0,0 @@ - - \ No newline at end of file diff --git a/components/XDataTable.vue b/components/XDataTable.vue index 161b718d..2374d8a3 100644 --- a/components/XDataTable.vue +++ b/components/XDataTable.vue @@ -183,7 +183,7 @@ const onEditDialogCancel = () => { + :loading="props.loading" stripedRows> diff --git a/middleware/org-solution-check.global.ts b/middleware/org-solution-check.global.ts index e83757be..8576375c 100644 --- a/middleware/org-solution-check.global.ts +++ b/middleware/org-solution-check.global.ts @@ -7,14 +7,14 @@ export default defineNuxtRouteMiddleware(async (to, from) => { const { organizationslug, solutionslug } = to.params if (organizationslug) { - const organizations = await $fetch('/api/organizations', { + const organizations = await $fetch('/api/organization', { query: { slug: organizationslug } }) if (!(organizations ?? []).length) { return navigateTo('/') } else if (solutionslug) { - const solutions = await $fetch('/api/solutions', { + const solutions = await $fetch('/api/solution', { query: { organizationSlug: organizationslug, slug: solutionslug diff --git a/migrations/.snapshot-cathedral.json b/migrations/.snapshot-cathedral.json index 3f44f3d9..ce4f994f 100644 --- a/migrations/.snapshot-cathedral.json +++ b/migrations/.snapshot-cathedral.json @@ -1,840 +1,774 @@ { - "namespaces": [ - "public" - ], - "name": "public", - "tables": [ + "namespaces": [ + "public" + ], + "name": "public", + "tables": [ + { + "columns": { + "id": { + "name": "id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "uuid" + }, + "name": { + "name": "name", + "type": "varchar(254)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 254, + "mappedType": "string" + }, + "email": { + "name": "email", + "type": "varchar(254)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 254, + "mappedType": "string" + }, + "creation_date": { + "name": "creation_date", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + }, + "last_login_date": { + "name": "last_login_date", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 6, + "mappedType": "datetime" + }, + "is_system_admin": { + "name": "is_system_admin", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "boolean" + } + }, + "name": "app_user", + "schema": "public", + "indexes": [ + { + "keyName": "app_user_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "uuid" + }, + "entity_id": { + "name": "entity_id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "uuid" + }, + "entity_name": { + "name": "entity_name", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 255, + "mappedType": "string" + }, + "type": { + "name": "type", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "enumItems": [ + "create", + "update", + "delete", + "update_early", + "delete_early" + ], + "mappedType": "enum" + }, + "entity": { + "name": "entity", + "type": "jsonb", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "json" + }, + "created_at": { + "name": "created_at", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "mappedType": "datetime" + } + }, + "name": "audit_log", + "schema": "public", + "indexes": [ + { + "keyName": "audit_log_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": {}, + "nativeEnums": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "uuid" + }, + "req_type": { + "name": "req_type", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "enumItems": [ + "actor", + "assumption", + "behavior", + "component", + "constraint", + "effect", + "environment_component", + "example", + "functional_behavior", + "functionality", + "glossary_term", + "goal", + "hint", + "invariant", + "justification", + "limit", + "meta_requirement", + "noise", + "non_functional_behavior", + "obstacle", + "organization", + "outcome", + "parsed_requirement", + "person", + "product", + "requirement", + "responsibility", + "role", + "scenario", + "silence", + "solution", + "stakeholder", + "system_component", + "task", + "test_case", + "use_case", + "user_story" + ], + "mappedType": "enum" + }, + "name": { + "name": "name", + "type": "varchar(100)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 100, + "mappedType": "string" + }, + "description": { + "name": "description", + "type": "varchar(1000)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 1000, + "mappedType": "string" + }, + "last_modified": { + "name": "last_modified", + "type": "timestamptz", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "length": 6, + "default": "now()", + "mappedType": "datetime" + }, + "modified_by_id": { + "name": "modified_by_id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "'ac594919-50e3-438a-b9bc-efb8a8654243'", + "mappedType": "uuid" + }, + "is_silence": { + "name": "is_silence", + "type": "boolean", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "default": "false", + "mappedType": "boolean" + }, + "priority": { + "name": "priority", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "enumItems": [ + "MUST", + "SHOULD", + "COULD", + "WONT" + ], + "mappedType": "enum" + }, + "email": { + "name": "email", + "type": "varchar(254)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 254, + "mappedType": "string" + }, + "primary_actor_id": { + "name": "primary_actor_id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "uuid" + }, + "slug": { + "name": "slug", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 255, + "mappedType": "string" + }, + "segmentation": { + "name": "segmentation", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "enumItems": [ + "Client", + "Vendor" + ], + "mappedType": "enum" + }, + "category": { + "name": "category", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "enumItems": [ + "Business Rule", + "Physical Law", + "Engineering Decision", + "Key Stakeholder", + "Shadow Influencer", + "Fellow Traveler", + "Observer" + ], + "mappedType": "enum" + }, + "availability": { + "name": "availability", + "type": "int", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "integer" + }, + "influence": { + "name": "influence", + "type": "int", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "integer" + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 255, + "mappedType": "string" + }, + "level": { + "name": "level", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 255, + "mappedType": "string" + }, + "goal_in_context": { + "name": "goal_in_context", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 255, + "mappedType": "string" + }, + "precondition_id": { + "name": "precondition_id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "uuid" + }, + "trigger_id": { + "name": "trigger_id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "uuid" + }, + "main_success_scenario": { + "name": "main_success_scenario", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 255, + "mappedType": "string" + }, + "success_guarantee_id": { + "name": "success_guarantee_id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "uuid" + }, + "extensions": { + "name": "extensions", + "type": "varchar(255)", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "length": 255, + "mappedType": "string" + }, + "functional_behavior_id": { + "name": "functional_behavior_id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "uuid" + }, + "outcome_id": { + "name": "outcome_id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "uuid" + } + }, + "name": "requirement", + "schema": "public", + "indexes": [ { - "columns": { - "id": { - "name": "id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "uuid" - }, - "name": { - "name": "name", - "type": "varchar(254)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 254, - "mappedType": "string" - }, - "email": { - "name": "email", - "type": "varchar(254)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 254, - "mappedType": "string" - }, - "creation_date": { - "name": "creation_date", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - }, - "last_login_date": { - "name": "last_login_date", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 6, - "mappedType": "datetime" - }, - "is_system_admin": { - "name": "is_system_admin", - "type": "boolean", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "boolean" - } - }, - "name": "app_user", - "schema": "public", - "indexes": [ - { - "keyName": "app_user_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {}, - "nativeEnums": {} + "columnNames": [ + "req_type" + ], + "composite": false, + "keyName": "requirement_req_type_index", + "constraint": false, + "primary": false, + "unique": false }, { - "columns": { - "id": { - "name": "id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "uuid" - }, - "entity_id": { - "name": "entity_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "uuid" - }, - "entity_name": { - "name": "entity_name", - "type": "varchar(255)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 255, - "mappedType": "string" - }, - "type": { - "name": "type", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "enumItems": [ - "create", - "update", - "delete", - "update_early", - "delete_early" - ], - "mappedType": "enum" - }, - "entity": { - "name": "entity", - "type": "jsonb", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "json" - }, - "created_at": { - "name": "created_at", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "mappedType": "datetime" - } - }, - "name": "audit_log", - "schema": "public", - "indexes": [ - { - "keyName": "audit_log_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": {}, - "nativeEnums": {} + "columnNames": [ + "slug" + ], + "composite": false, + "keyName": "requirement_slug_unique", + "constraint": true, + "primary": false, + "unique": true + }, + { + "keyName": "requirement_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "requirement_modified_by_id_foreign": { + "constraintName": "requirement_modified_by_id_foreign", + "columnNames": [ + "modified_by_id" + ], + "localTableName": "public.requirement", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.app_user", + "updateRule": "cascade" + }, + "requirement_primary_actor_id_foreign": { + "constraintName": "requirement_primary_actor_id_foreign", + "columnNames": [ + "primary_actor_id" + ], + "localTableName": "public.requirement", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.requirement", + "deleteRule": "set null", + "updateRule": "cascade" }, + "requirement_precondition_id_foreign": { + "constraintName": "requirement_precondition_id_foreign", + "columnNames": [ + "precondition_id" + ], + "localTableName": "public.requirement", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.requirement", + "deleteRule": "set null", + "updateRule": "cascade" + }, + "requirement_success_guarantee_id_foreign": { + "constraintName": "requirement_success_guarantee_id_foreign", + "columnNames": [ + "success_guarantee_id" + ], + "localTableName": "public.requirement", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.requirement", + "deleteRule": "set null", + "updateRule": "cascade" + }, + "requirement_functional_behavior_id_foreign": { + "constraintName": "requirement_functional_behavior_id_foreign", + "columnNames": [ + "functional_behavior_id" + ], + "localTableName": "public.requirement", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.requirement", + "deleteRule": "set null", + "updateRule": "cascade" + }, + "requirement_outcome_id_foreign": { + "constraintName": "requirement_outcome_id_foreign", + "columnNames": [ + "outcome_id" + ], + "localTableName": "public.requirement", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.requirement", + "deleteRule": "set null", + "updateRule": "cascade" + } + }, + "nativeEnums": {} + }, + { + "columns": { + "app_user_id": { + "name": "app_user_id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "uuid" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "uuid" + }, + "role": { + "name": "role", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "enumItems": [ + "Organization Admin", + "Organization Contributor", + "Organization Reader" + ], + "mappedType": "enum" + } + }, + "name": "app_user_organization_role", + "schema": "public", + "indexes": [ { - "columns": { - "id": { - "name": "id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "uuid" - }, - "req_type": { - "name": "req_type", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "enumItems": [ - "actor", - "assumption", - "behavior", - "component", - "constraint", - "effect", - "environment_component", - "example", - "functional_behavior", - "functionality", - "glossary_term", - "goal", - "hint", - "invariant", - "justification", - "limit", - "meta_requirement", - "noise", - "non_functional_behavior", - "obstacle", - "organization", - "outcome", - "parsed_requirement", - "person", - "product", - "requirement", - "responsibility", - "role", - "scenario", - "silence", - "solution", - "stakeholder", - "system_component", - "task", - "test_case", - "use_case", - "user_story" - ], - "mappedType": "enum" - }, - "name": { - "name": "name", - "type": "varchar(100)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 100, - "mappedType": "string" - }, - "description": { - "name": "description", - "type": "varchar(1000)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 1000, - "mappedType": "string" - }, - "last_modified": { - "name": "last_modified", - "type": "timestamptz", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "length": 6, - "default": "now()", - "mappedType": "datetime" - }, - "modified_by_id": { - "name": "modified_by_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "'ac594919-50e3-438a-b9bc-efb8a8654243'", - "mappedType": "uuid" - }, - "is_silence": { - "name": "is_silence", - "type": "boolean", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "default": "false", - "mappedType": "boolean" - }, - "priority": { - "name": "priority", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "enumItems": [ - "MUST", - "SHOULD", - "COULD", - "WONT" - ], - "mappedType": "enum" - }, - "parent_component_id": { - "name": "parent_component_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "uuid" - }, - "email": { - "name": "email", - "type": "varchar(254)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 254, - "mappedType": "string" - }, - "primary_actor_id": { - "name": "primary_actor_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "uuid" - }, - "slug": { - "name": "slug", - "type": "varchar(255)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 255, - "mappedType": "string" - }, - "segmentation": { - "name": "segmentation", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "enumItems": [ - "Client", - "Vendor" - ], - "mappedType": "enum" - }, - "category": { - "name": "category", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "enumItems": [ - "Business Rule", - "Physical Law", - "Engineering Decision", - "Key Stakeholder", - "Shadow Influencer", - "Fellow Traveler", - "Observer" - ], - "mappedType": "enum" - }, - "availability": { - "name": "availability", - "type": "int", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "integer" - }, - "influence": { - "name": "influence", - "type": "int", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "integer" - }, - "parent_component_1_id": { - "name": "parent_component_1_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "uuid" - }, - "scope": { - "name": "scope", - "type": "varchar(255)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 255, - "mappedType": "string" - }, - "level": { - "name": "level", - "type": "varchar(255)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 255, - "mappedType": "string" - }, - "goal_in_context": { - "name": "goal_in_context", - "type": "varchar(255)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 255, - "mappedType": "string" - }, - "precondition_id": { - "name": "precondition_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "uuid" - }, - "trigger_id": { - "name": "trigger_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "uuid" - }, - "main_success_scenario": { - "name": "main_success_scenario", - "type": "varchar(255)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 255, - "mappedType": "string" - }, - "success_guarantee_id": { - "name": "success_guarantee_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "uuid" - }, - "extensions": { - "name": "extensions", - "type": "varchar(255)", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "length": 255, - "mappedType": "string" - }, - "functional_behavior_id": { - "name": "functional_behavior_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "uuid" - }, - "outcome_id": { - "name": "outcome_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "uuid" - } - }, - "name": "requirement", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "req_type" - ], - "composite": false, - "keyName": "requirement_req_type_index", - "constraint": false, - "primary": false, - "unique": false - }, - { - "columnNames": [ - "slug" - ], - "composite": false, - "keyName": "requirement_slug_unique", - "constraint": true, - "primary": false, - "unique": true - }, - { - "keyName": "requirement_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "requirement_modified_by_id_foreign": { - "constraintName": "requirement_modified_by_id_foreign", - "columnNames": [ - "modified_by_id" - ], - "localTableName": "public.requirement", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.app_user", - "updateRule": "cascade" - }, - "requirement_parent_component_id_foreign": { - "constraintName": "requirement_parent_component_id_foreign", - "columnNames": [ - "parent_component_id" - ], - "localTableName": "public.requirement", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.requirement", - "deleteRule": "set null", - "updateRule": "cascade" - }, - "requirement_primary_actor_id_foreign": { - "constraintName": "requirement_primary_actor_id_foreign", - "columnNames": [ - "primary_actor_id" - ], - "localTableName": "public.requirement", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.requirement", - "deleteRule": "set null", - "updateRule": "cascade" - }, - "requirement_parent_component_1_id_foreign": { - "constraintName": "requirement_parent_component_1_id_foreign", - "columnNames": [ - "parent_component_1_id" - ], - "localTableName": "public.requirement", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.requirement", - "deleteRule": "set null", - "updateRule": "cascade" - }, - "requirement_precondition_id_foreign": { - "constraintName": "requirement_precondition_id_foreign", - "columnNames": [ - "precondition_id" - ], - "localTableName": "public.requirement", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.requirement", - "deleteRule": "set null", - "updateRule": "cascade" - }, - "requirement_success_guarantee_id_foreign": { - "constraintName": "requirement_success_guarantee_id_foreign", - "columnNames": [ - "success_guarantee_id" - ], - "localTableName": "public.requirement", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.requirement", - "deleteRule": "set null", - "updateRule": "cascade" - }, - "requirement_functional_behavior_id_foreign": { - "constraintName": "requirement_functional_behavior_id_foreign", - "columnNames": [ - "functional_behavior_id" - ], - "localTableName": "public.requirement", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.requirement", - "deleteRule": "set null", - "updateRule": "cascade" - }, - "requirement_outcome_id_foreign": { - "constraintName": "requirement_outcome_id_foreign", - "columnNames": [ - "outcome_id" - ], - "localTableName": "public.requirement", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.requirement", - "deleteRule": "set null", - "updateRule": "cascade" - } - }, - "nativeEnums": {} + "keyName": "app_user_organization_role_pkey", + "columnNames": [ + "app_user_id", + "organization_id", + "role" + ], + "composite": true, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "app_user_organization_role_app_user_id_foreign": { + "constraintName": "app_user_organization_role_app_user_id_foreign", + "columnNames": [ + "app_user_id" + ], + "localTableName": "public.app_user_organization_role", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.app_user", + "updateRule": "cascade" }, + "app_user_organization_role_organization_id_foreign": { + "constraintName": "app_user_organization_role_organization_id_foreign", + "columnNames": [ + "organization_id" + ], + "localTableName": "public.app_user_organization_role", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.requirement", + "updateRule": "cascade" + } + }, + "nativeEnums": {} + }, + { + "columns": { + "id": { + "name": "id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "mappedType": "uuid" + }, + "left_id": { + "name": "left_id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "uuid" + }, + "right_id": { + "name": "right_id", + "type": "uuid", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": true, + "mappedType": "uuid" + }, + "rel_type": { + "name": "rel_type", + "type": "text", + "unsigned": false, + "autoincrement": false, + "primary": false, + "nullable": false, + "enumItems": [ + "belongs", + "characterizes", + "constrains", + "contradicts", + "details", + "disjoins", + "duplicates", + "excepts", + "explains", + "extends", + "follows", + "repeats", + "shares" + ], + "mappedType": "enum" + } + }, + "name": "requirement_relation", + "schema": "public", + "indexes": [ { - "columns": { - "app_user_id": { - "name": "app_user_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "uuid" - }, - "organization_id": { - "name": "organization_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "uuid" - }, - "role": { - "name": "role", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "enumItems": [ - "Organization Admin", - "Organization Contributor", - "Organization Reader" - ], - "mappedType": "enum" - } - }, - "name": "app_user_organization_role", - "schema": "public", - "indexes": [ - { - "keyName": "app_user_organization_role_pkey", - "columnNames": [ - "app_user_id", - "organization_id", - "role" - ], - "composite": true, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "app_user_organization_role_app_user_id_foreign": { - "constraintName": "app_user_organization_role_app_user_id_foreign", - "columnNames": [ - "app_user_id" - ], - "localTableName": "public.app_user_organization_role", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.app_user", - "updateRule": "cascade" - }, - "app_user_organization_role_organization_id_foreign": { - "constraintName": "app_user_organization_role_organization_id_foreign", - "columnNames": [ - "organization_id" - ], - "localTableName": "public.app_user_organization_role", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.requirement", - "updateRule": "cascade" - } - }, - "nativeEnums": {} + "columnNames": [ + "rel_type" + ], + "composite": false, + "keyName": "requirement_relation_rel_type_index", + "constraint": false, + "primary": false, + "unique": false }, { - "columns": { - "id": { - "name": "id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "uuid" - }, - "left_id": { - "name": "left_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "uuid" - }, - "right_id": { - "name": "right_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "mappedType": "uuid" - }, - "rel_type": { - "name": "rel_type", - "type": "text", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": false, - "enumItems": [ - "belongs", - "characterizes", - "constrains", - "contradicts", - "details", - "disjoins", - "duplicates", - "excepts", - "explains", - "extends", - "follows", - "repeats", - "shares" - ], - "mappedType": "enum" - }, - "left_1_id": { - "name": "left_1_id", - "type": "uuid", - "unsigned": false, - "autoincrement": false, - "primary": false, - "nullable": true, - "mappedType": "uuid" - } - }, - "name": "requirement_relation", - "schema": "public", - "indexes": [ - { - "columnNames": [ - "rel_type" - ], - "composite": false, - "keyName": "requirement_relation_rel_type_index", - "constraint": false, - "primary": false, - "unique": false - }, - { - "keyName": "requirement_relation_pkey", - "columnNames": [ - "id" - ], - "composite": false, - "constraint": true, - "primary": true, - "unique": true - } - ], - "checks": [], - "foreignKeys": { - "requirement_relation_left_id_foreign": { - "constraintName": "requirement_relation_left_id_foreign", - "columnNames": [ - "left_id" - ], - "localTableName": "public.requirement_relation", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.requirement", - "updateRule": "cascade" - }, - "requirement_relation_right_id_foreign": { - "constraintName": "requirement_relation_right_id_foreign", - "columnNames": [ - "right_id" - ], - "localTableName": "public.requirement_relation", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.requirement", - "updateRule": "cascade" - }, - "requirement_relation_left_1_id_foreign": { - "constraintName": "requirement_relation_left_1_id_foreign", - "columnNames": [ - "left_1_id" - ], - "localTableName": "public.requirement_relation", - "referencedColumnNames": [ - "id" - ], - "referencedTableName": "public.requirement", - "deleteRule": "set null", - "updateRule": "cascade" - } - }, - "nativeEnums": {} + "keyName": "requirement_relation_pkey", + "columnNames": [ + "id" + ], + "composite": false, + "constraint": true, + "primary": true, + "unique": true + } + ], + "checks": [], + "foreignKeys": { + "requirement_relation_left_id_foreign": { + "constraintName": "requirement_relation_left_id_foreign", + "columnNames": [ + "left_id" + ], + "localTableName": "public.requirement_relation", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.requirement", + "deleteRule": "cascade" + }, + "requirement_relation_right_id_foreign": { + "constraintName": "requirement_relation_right_id_foreign", + "columnNames": [ + "right_id" + ], + "localTableName": "public.requirement_relation", + "referencedColumnNames": [ + "id" + ], + "referencedTableName": "public.requirement", + "deleteRule": "cascade" } - ], - "nativeEnums": {} -} \ No newline at end of file + }, + "nativeEnums": {} + } + ], + "nativeEnums": {} +} diff --git a/migrations/Migration20241020164456.ts b/migrations/Migration20241020164456.ts new file mode 100644 index 00000000..4962102f --- /dev/null +++ b/migrations/Migration20241020164456.ts @@ -0,0 +1,236 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20241020164456 extends Migration { + + override async up(): Promise { + this.addSql(`alter table "app_user_organization_role" drop constraint if exists "app_user_organization_role_organization_id_foreign";`); + + this.addSql(`alter table "solution" drop constraint if exists "solution_organization_id_foreign";`); + + this.addSql(`alter table "requirement" drop constraint if exists "requirement_solution_id_foreign";`); + + this.addSql(`alter table "requirement" drop constraint if exists "requirement_req_type_check";`); + + this.addSql(`alter table "requirement" drop constraint if exists "requirement_follows_id_foreign";`); + + this.addSql(`alter table "app_user_organization_role" drop constraint if exists "app_user_organization_role_organization_id_foreign";`); + + this.addSql(`alter table "requirement" add column "slug" varchar(255) null;`); + this.addSql(`alter table "requirement" alter column "name" type varchar(100) using ("name"::varchar(100));`); + this.addSql(`alter table "requirement" add constraint "requirement_req_type_check" check("req_type" in ('actor', 'assumption', 'behavior', 'component', 'constraint', 'effect', 'environment_component', 'example', 'functional_behavior', 'functionality', 'glossary_term', 'goal', 'hint', 'invariant', 'justification', 'limit', 'meta_requirement', 'noise', 'non_functional_behavior', 'obstacle', 'organization', 'outcome', 'parsed_requirement', 'person', 'product', 'requirement', 'responsibility', 'role', 'scenario', 'silence', 'solution', 'stakeholder', 'system_component', 'task', 'test_case', 'use_case', 'user_story'));`); + this.addSql(`alter table "requirement" rename column "statement" to "description";`); + this.addSql(`alter table "requirement" add constraint "requirement_slug_unique" unique ("slug");`); + + // Postgres v16 does not support uuid v7 + // source: https://gist.github.com/fabiolimace/515a0440e3e40efeb234e12644a6a346#file-uuidv7-sql + this.addSql(` + create or replace function uuid7() returns uuid as $$ + declare + begin + return uuid7(clock_timestamp()); + end $$ language plpgsql; + + create or replace function uuid7(p_timestamp timestamp with time zone) returns uuid as $$ + declare + + v_time double precision := null; + + v_unix_t bigint := null; + v_rand_a bigint := null; + v_rand_b bigint := null; + + v_unix_t_hex varchar := null; + v_rand_a_hex varchar := null; + v_rand_b_hex varchar := null; + + c_milli double precision := 10^3; -- 1 000 + c_micro double precision := 10^6; -- 1 000 000 + c_scale double precision := 4.096; -- 4.0 * (1024 / 1000) + + c_version bigint := x'0000000000007000'::bigint; -- RFC-9562 version: b'0111...' + c_variant bigint := x'8000000000000000'::bigint; -- RFC-9562 variant: b'10xx...' + + begin + + v_time := extract(epoch from p_timestamp); + + v_unix_t := trunc(v_time * c_milli); + v_rand_a := trunc((v_time * c_micro - v_unix_t * c_milli) * c_scale); + v_rand_b := trunc(random() * 2^30)::bigint << 32 | trunc(random() * 2^32)::bigint; + + v_unix_t_hex := lpad(to_hex(v_unix_t), 12, '0'); + v_rand_a_hex := lpad(to_hex((v_rand_a | c_version)::bigint), 4, '0'); + v_rand_b_hex := lpad(to_hex((v_rand_b | c_variant)::bigint), 16, '0'); + + return (v_unix_t_hex || v_rand_a_hex || v_rand_b_hex)::uuid; + + end $$ language plpgsql; + `); + + this.addSql(`alter table "requirement_relation" drop constraint if exists "requirement_relation_left_id_unique";`); + this.addSql(`alter table "requirement_relation" drop constraint if exists "requirement_relation_right_id_unique";`); + this.addSql(`alter table "requirement_relation" drop constraint if exists "requirement_relation_left_1_id_unique";`); + + // Copy records from "organization" table to "requirement" table + this.addSql(` + insert into "requirement" (id, description, name, slug, req_type, solution_id) + select id, description, name, slug, 'organization' as req_type, '00000000-0000-0000-0000-000000000000' as solution_id + from "organization"; + `); + + // Copy records from "solution" table to "requirement" table + // Note the abuse of "organization_id" as "solution_id" + // this is because it will later be generalized to the "requirement_relation" table as "belongs" + this.addSql(` + insert into "requirement" (id, description, name, slug, req_type, solution_id) + select id, description, name, slug, 'solution' as req_type, organization_id as solution_id + from "solution"; + `); + + // Migrate non-null "follows_id" entries to "requirement_relation" table + this.addSql(` + insert into "requirement_relation" (id, left_id, right_id, rel_type) + select uuid7(), id, follows_id, 'follows' + from "requirement" + where follows_id is not null; + `); + + // Migrate "solution_id" entries to "requirement_relation" table, ignoring empty UUIDs and NULLs + this.addSql(` + insert into "requirement_relation" (id, left_id, right_id, rel_type) + select uuid7(), id, solution_id, 'belongs' + from "requirement" + where solution_id is not null and solution_id != '00000000-0000-0000-0000-000000000000'; + `); + + this.addSql(`drop table if exists "organization" cascade;`); + this.addSql(`drop table if exists "solution" cascade;`); + + this.addSql(`drop function uuid7();`); + + this.addSql(`alter table "requirement" drop column "solution_id", drop column "follows_id";`); + + this.addSql(`alter table "app_user_organization_role" add constraint "app_user_organization_role_organization_id_foreign" foreign key ("organization_id") references "requirement" ("id") on update cascade;`); + } + + override async down(): Promise { + // Recreate "organization" and "solution" tables + this.addSql(` + create table "organization" ( + id uuid primary key, + name varchar(255) not null, + description text, + slug varchar(255) unique + ); + `); + + this.addSql(` + create table "solution" ( + id uuid primary key, + name varchar(255) not null, + description text, + slug varchar(255) unique, + organization_id uuid references "organization" ("id") on delete set null + ); + `); + + // Restore "solution_id" and "follows_id" columns to the "requirement" table + this.addSql(`alter table "requirement" add column "solution_id" uuid;`); + this.addSql(`alter table "requirement" add column "follows_id" uuid;`); + + // Migrate "belongs" relationships back to "solution_id" column + this.addSql(` + update "requirement" r + set solution_id = rr.right_id + from "requirement_relation" rr + where rr.left_id = r.id and rr.rel_type = 'belongs'; + `); + + // Migrate "follows" relationships back to "follows_id" column + this.addSql(` + update "requirement" r + set follows_id = rr.right_id + from "requirement_relation" rr + where rr.left_id = r.id and rr.rel_type = 'follows'; + `); + + // Insert back data into "organization" table + this.addSql(` + insert into "organization" (id, name, description, slug) + select id, name, description, slug + from "requirement" + where req_type = 'organization'; + `); + + // Insert back data into "solution" table + this.addSql(` + insert into "solution" (id, name, description, slug, organization_id) + select id, name, description, slug, solution_id + from "requirement" + where req_type = 'solution'; + `); + + // Drop the "requirement_relation" entries related to follows and belongs + this.addSql(` + delete from "requirement_relation" + where rel_type in ('follows', 'belongs'); + `); + + // Drop constraints and columns added to the "requirement" table + this.addSql(`alter table "requirement" drop constraint if exists "requirement_slug_unique";`); + this.addSql(`alter table "requirement" drop column if exists "slug";`); + this.addSql(`alter table "requirement" rename column "description" to "statement";`); + + // Restore original length of the "name" column + this.addSql(`alter table "requirement" alter column "name" type varchar(255) using ("name"::varchar(255));`); + + // Drop the added req_type check constraint + this.addSql(`alter table "requirement" drop constraint if exists "requirement_req_type_check";`); + + // Drop the re-created foreign key constraint on "app_user_organization_role" + this.addSql(` + alter table "app_user_organization_role" drop constraint if exists "app_user_organization_role_organization_id_foreign"; + `); + + // Restore the original foreign key constraints + this.addSql(` + alter table "app_user_organization_role" + add constraint "app_user_organization_role_organization_id_foreign" + foreign key ("organization_id") references "organization" ("id") on update cascade; + `); + + this.addSql(` + alter table "solution" + add constraint "solution_organization_id_foreign" + foreign key ("organization_id") references "organization" ("id") on delete set null; + `); + + this.addSql(` + alter table "requirement" + add constraint "requirement_solution_id_foreign" + foreign key ("solution_id") references "solution" ("id") on delete set null; + `); + + this.addSql(` + alter table "requirement" + add constraint "requirement_follows_id_foreign" + foreign key ("follows_id") references "requirement" ("id") on delete set null; + `); + + // Recreate unique constraints for "requirement_relation" if they existed before + this.addSql(` + alter table "requirement_relation" + add constraint "requirement_relation_left_id_unique" unique ("left_id"); + `); + + this.addSql(` + alter table "requirement_relation" + add constraint "requirement_relation_right_id_unique" unique ("right_id"); + `); + + this.addSql(` + alter table "requirement_relation" + add constraint "requirement_relation_left_1_id_unique" unique ("left_id"); + `); + } +} diff --git a/migrations/Migration20241022160037.ts b/migrations/Migration20241022160037.ts new file mode 100644 index 00000000..dcc921ea --- /dev/null +++ b/migrations/Migration20241022160037.ts @@ -0,0 +1,104 @@ +import { Migration } from '@mikro-orm/migrations'; + +export class Migration20241022160037 extends Migration { + + override async up(): Promise { + this.addSql(`alter table "requirement" drop constraint "requirement_parent_component_id_foreign";`); + this.addSql(`alter table "requirement" drop constraint "requirement_parent_component_1_id_foreign";`); + + this.addSql(`alter table "requirement_relation" drop constraint "requirement_relation_left_1_id_foreign";`); + this.addSql(`alter table "requirement_relation" drop constraint "requirement_relation_left_id_foreign";`); + this.addSql(`alter table "requirement_relation" drop constraint "requirement_relation_right_id_foreign";`); + + // Postgres v16 does not support uuid v7 + // source: https://gist.github.com/fabiolimace/515a0440e3e40efeb234e12644a6a346#file-uuidv7-sql + this.addSql(` + create or replace function uuid7() returns uuid as $$ + declare + begin + return uuid7(clock_timestamp()); + end $$ language plpgsql; + + create or replace function uuid7(p_timestamp timestamp with time zone) returns uuid as $$ + declare + + v_time double precision := null; + + v_unix_t bigint := null; + v_rand_a bigint := null; + v_rand_b bigint := null; + + v_unix_t_hex varchar := null; + v_rand_a_hex varchar := null; + v_rand_b_hex varchar := null; + + c_milli double precision := 10^3; -- 1 000 + c_micro double precision := 10^6; -- 1 000 000 + c_scale double precision := 4.096; -- 4.0 * (1024 / 1000) + + c_version bigint := x'0000000000007000'::bigint; -- RFC-9562 version: b'0111...' + c_variant bigint := x'8000000000000000'::bigint; -- RFC-9562 variant: b'10xx...' + + begin + + v_time := extract(epoch from p_timestamp); + + v_unix_t := trunc(v_time * c_milli); + v_rand_a := trunc((v_time * c_micro - v_unix_t * c_milli) * c_scale); + v_rand_b := trunc(random() * 2^30)::bigint << 32 | trunc(random() * 2^32)::bigint; + + v_unix_t_hex := lpad(to_hex(v_unix_t), 12, '0'); + v_rand_a_hex := lpad(to_hex((v_rand_a | c_version)::bigint), 4, '0'); + v_rand_b_hex := lpad(to_hex((v_rand_b | c_variant)::bigint), 16, '0'); + + return (v_unix_t_hex || v_rand_a_hex || v_rand_b_hex)::uuid; + + end $$ language plpgsql; + `); + + // migrate non-null parent_component_id entries to "requirement_relation" table as rel_type = 'belongs' + this.addSql(` + insert into "requirement_relation" ("id", "left_id", "right_id", "rel_type") + select uuid7(), "id", "parent_component_id", 'belongs' + from "requirement" + where "parent_component_id" is not null; + `); + + this.addSql(`drop function uuid7();`); + + this.addSql(`alter table "requirement" drop column "parent_component_id", drop column "parent_component_1_id";`); + + this.addSql(`alter table "requirement_relation" drop column "left_1_id";`); + + this.addSql(`alter table "requirement_relation" alter column "left_id" drop default;`); + this.addSql(`alter table "requirement_relation" alter column "left_id" type uuid using ("left_id"::text::uuid);`); + this.addSql(`alter table "requirement_relation" alter column "left_id" drop not null;`); + this.addSql(`alter table "requirement_relation" alter column "right_id" drop default;`); + this.addSql(`alter table "requirement_relation" alter column "right_id" type uuid using ("right_id"::text::uuid);`); + this.addSql(`alter table "requirement_relation" alter column "right_id" drop not null;`); + this.addSql(`alter table "requirement_relation" add constraint "requirement_relation_left_id_foreign" foreign key ("left_id") references "requirement" ("id") on delete cascade;`); + this.addSql(`alter table "requirement_relation" add constraint "requirement_relation_right_id_foreign" foreign key ("right_id") references "requirement" ("id") on delete cascade;`); + } + + override async down(): Promise { + this.addSql(`alter table "requirement_relation" drop constraint "requirement_relation_left_id_foreign";`); + this.addSql(`alter table "requirement_relation" drop constraint "requirement_relation_right_id_foreign";`); + + this.addSql(`alter table "requirement" add column "parent_component_id" uuid null, add column "parent_component_1_id" uuid null;`); + + this.addSql(`alter table "requirement" add constraint "requirement_parent_component_id_foreign" foreign key ("parent_component_id") references "requirement" ("id") on update cascade on delete set null;`); + this.addSql(`alter table "requirement" add constraint "requirement_parent_component_1_id_foreign" foreign key ("parent_component_1_id") references "requirement" ("id") on update cascade on delete set null;`); + + this.addSql(`alter table "requirement_relation" add column "left_1_id" uuid null;`); + this.addSql(`alter table "requirement_relation" alter column "left_id" drop default;`); + this.addSql(`alter table "requirement_relation" alter column "left_id" type uuid using ("left_id"::text::uuid);`); + this.addSql(`alter table "requirement_relation" alter column "left_id" set not null;`); + this.addSql(`alter table "requirement_relation" alter column "right_id" drop default;`); + this.addSql(`alter table "requirement_relation" alter column "right_id" type uuid using ("right_id"::text::uuid);`); + this.addSql(`alter table "requirement_relation" alter column "right_id" set not null;`); + this.addSql(`alter table "requirement_relation" add constraint "requirement_relation_left_1_id_foreign" foreign key ("left_1_id") references "requirement" ("id") on update cascade on delete set null;`); + this.addSql(`alter table "requirement_relation" add constraint "requirement_relation_left_id_foreign" foreign key ("left_id") references "requirement" ("id") on update cascade;`); + this.addSql(`alter table "requirement_relation" add constraint "requirement_relation_right_id_foreign" foreign key ("right_id") references "requirement" ("id") on update cascade;`); + } + +} diff --git a/pages/index.client.vue b/pages/index.client.vue index eccbd196..5b9fc983 100644 --- a/pages/index.client.vue +++ b/pages/index.client.vue @@ -2,7 +2,7 @@ definePageMeta({ name: 'Home' }) const router = useRouter(), - { status, data: organizations, refresh, error: getOrgError } = await useFetch('/api/organizations'), + { status, data: organizations, refresh, error: getOrgError } = await useFetch('/api/organization'), confirm = useConfirm(), { $eventBus } = useNuxtApp() @@ -17,7 +17,7 @@ const handleDelete = async (organization: { id: string, name: string }) => { rejectLabel: 'Cancel', acceptLabel: 'Delete', accept: async () => { - await $fetch(`/api/organizations/${organization.id}`, { + await $fetch(`/api/organization/${organization.id}`, { method: 'delete' }).catch((e) => $eventBus.$emit('page-error', e)) refresh() diff --git a/pages/new-organization.vue b/pages/new-organization.vue index ee4aba56..2968cf8e 100644 --- a/pages/new-organization.vue +++ b/pages/new-organization.vue @@ -10,7 +10,7 @@ const router = useRouter(), const createOrganization = async () => { try { - const organizationId = (await $fetch('/api/organizations', { + const organizationId = (await $fetch('/api/organization', { method: 'post', body: { name: name.value, @@ -19,7 +19,7 @@ const createOrganization = async () => { }).catch((e) => $eventBus.$emit('page-error', e))); if (organizationId) { - const newOrganization = (await $fetch(`/api/organizations/${organizationId}`)); + const newOrganization = (await $fetch(`/api/organization/${organizationId}`)); router.push({ name: 'Organization', params: { organizationslug: newOrganization?.slug } }); } else { $eventBus.$emit('page-error', 'Failed to create organization. No organization ID returned.'); diff --git a/pages/o/[organization-slug]/[solution-slug]/edit-entry.client.vue b/pages/o/[organization-slug]/[solution-slug]/edit-entry.client.vue index 9dbf57ab..00356b21 100644 --- a/pages/o/[organization-slug]/[solution-slug]/edit-entry.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/edit-entry.client.vue @@ -6,7 +6,7 @@ const route = useRoute('Edit Solution'), { $eventBus } = useNuxtApp(), { organizationslug, solutionslug } = route.params, router = useRouter(), - { data: solutions, error: getSolutionError } = await useFetch(`/api/solutions/`, { + { data: solutions, error: getSolutionError } = await useFetch(`/api/solution/`, { query: { organizationSlug: organizationslug, slug: solutionslug @@ -19,7 +19,7 @@ if (getSolutionError.value) $eventBus.$emit('page-error', getSolutionError.value); const updateSolution = async () => { - await $fetch(`/api/solutions/${solution.value.id}`, { + await $fetch(`/api/solution/${solution.value.id}`, { method: 'PUT', body: { name: solution.value.name, diff --git a/pages/o/[organization-slug]/[solution-slug]/environment/assumptions.client.vue b/pages/o/[organization-slug]/[solution-slug]/environment/assumptions.client.vue index 1e059cd7..5158410f 100644 --- a/pages/o/[organization-slug]/[solution-slug]/environment/assumptions.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/environment/assumptions.client.vue @@ -7,7 +7,7 @@ definePageMeta({ name: 'Assumptions' }) const { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Assumptions').params, - { data: solutions, error: getSolutionError } = await useFetch(`/api/solutions`, { + { data: solutions, error: getSolutionError } = await useFetch(`/api/solution`, { query: { organizationSlug: organizationslug, slug: solutionslug @@ -18,7 +18,7 @@ const { $eventBus } = useNuxtApp(), if (getSolutionError.value) $eventBus.$emit('page-error', getSolutionError.value); -const { data: assumptions, refresh, status, error: getAssumptionsError } = await useFetch(`/api/assumptions`, { +const { data: assumptions, refresh, status, error: getAssumptionsError } = await useFetch(`/api/assumption`, { query: { solutionId }, transform: (data) => data.map((item) => { item.lastModified = new Date(item.lastModified) @@ -30,7 +30,7 @@ if (getAssumptionsError.value) $eventBus.$emit('page-error', getAssumptionsError.value); const onCreate = async (data: Assumption) => { - await $fetch(`/api/assumptions`, { + await $fetch(`/api/assumption`, { method: 'post', body: { name: data.name, @@ -43,7 +43,7 @@ const onCreate = async (data: Assumption) => { } const onDelete = async (id: string) => { - await $fetch(`/api/assumptions/${id}`, { + await $fetch(`/api/assumption/${id}`, { method: 'delete', body: { solutionId } }) @@ -53,7 +53,7 @@ const onDelete = async (id: string) => { } const onUpdate = async (data: Assumption) => { - await $fetch(`/api/assumptions/${data.id}`, { + await $fetch(`/api/assumption/${data.id}`, { method: 'put', body: { name: data.name, diff --git a/pages/o/[organization-slug]/[solution-slug]/environment/components.client.vue b/pages/o/[organization-slug]/[solution-slug]/environment/components.client.vue index af0b9661..6a659848 100644 --- a/pages/o/[organization-slug]/[solution-slug]/environment/components.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/environment/components.client.vue @@ -6,14 +6,14 @@ definePageMeta({ name: 'Environment Components' }) const { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Environment Components').params, - { data: solutions, error: getSolutionError } = await useFetch('/api/solutions', { + { data: solutions, error: getSolutionError } = await useFetch('/api/solution', { query: { slug: solutionslug, organizationSlug: organizationslug } }), solutionId = solutions.value?.[0].id, - { data: environmentComponents, status, refresh, error: getEnvironmentComponentsError } = await useFetch(`/api/environment-components`, { + { data: environmentComponents, status, refresh, error: getEnvironmentComponentsError } = await useFetch(`/api/environment-component`, { query: { solutionId }, transform: (data) => data.map((item) => { item.lastModified = new Date(item.lastModified) @@ -28,7 +28,7 @@ if (getEnvironmentComponentsError.value) $eventBus.$emit('page-error', getEnvironmentComponentsError.value) const onCreate = async (data: EnvironmentComponent) => { - await $fetch(`/api/environment-components`, { + await $fetch(`/api/environment-component`, { method: 'POST', body: { name: data.name, @@ -41,7 +41,7 @@ const onCreate = async (data: EnvironmentComponent) => { } const onDelete = async (id: string) => { - await $fetch(`/api/environment-components/${id}`, { + await $fetch(`/api/environment-component/${id}`, { method: 'DELETE', body: { solutionId } }).catch((e) => $eventBus.$emit('page-error', e)) @@ -49,7 +49,7 @@ const onDelete = async (id: string) => { } const onUpdate = async (data: EnvironmentComponent) => { - await $fetch(`/api/environment-components/${data.id}`, { + await $fetch(`/api/environment-component/${data.id}`, { method: 'PUT', body: { name: data.name, diff --git a/pages/o/[organization-slug]/[solution-slug]/environment/constraints.client.vue b/pages/o/[organization-slug]/[solution-slug]/environment/constraints.client.vue index d2661f06..656eec0c 100644 --- a/pages/o/[organization-slug]/[solution-slug]/environment/constraints.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/environment/constraints.client.vue @@ -8,7 +8,7 @@ definePageMeta({ name: 'Constraints' }) const { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Constraints').params, - { data: solutions, error: getSolutionError } = await useFetch('/api/solutions', { + { data: solutions, error: getSolutionError } = await useFetch('/api/solution', { query: { slug: solutionslug, organizationSlug: organizationslug @@ -16,7 +16,7 @@ const { $eventBus } = useNuxtApp(), }), solution = (solutions.value ?? [])[0], solutionId = solution.id, - { data: constraints, status, refresh, error: getConstraintsError } = await useFetch(`/api/constraints`, { + { data: constraints, status, refresh, error: getConstraintsError } = await useFetch(`/api/constraint`, { query: { solutionId }, transform: (data) => data.map((item) => { item.lastModified = new Date(item.lastModified) @@ -31,7 +31,7 @@ if (getConstraintsError.value) $eventBus.$emit('page-error', getConstraintsError.value) const onCreate = async (data: Constraint) => { - await $fetch(`/api/constraints`, { + await $fetch(`/api/constraint`, { method: 'POST', body: { name: data.name, @@ -45,7 +45,7 @@ const onCreate = async (data: Constraint) => { } const onDelete = async (id: string) => { - await $fetch(`/api/constraints/${id}`, { + await $fetch(`/api/constraint/${id}`, { method: 'DELETE', body: { solutionId } }).catch((e) => $eventBus.$emit('page-error', e)) @@ -53,7 +53,7 @@ const onDelete = async (id: string) => { } const onUpdate = async (data: Constraint) => { - await $fetch(`/api/constraints/${data.id}`, { + await $fetch(`/api/constraint/${data.id}`, { method: 'PUT', body: { name: data.name, @@ -71,7 +71,7 @@ const onUpdate = async (data: Constraint) => { Environmental constraints are the limitations and obligations that the environment imposes on the project and system.

- (`/api/effects`, { +const { data: effects, refresh, status, error: getEffectsError } = await useFetch(`/api/effect`, { query: { solutionId }, transform: (data) => data.map((item) => { item.lastModified = new Date(item.lastModified) @@ -29,7 +29,7 @@ if (getEffectsError.value) $eventBus.$emit('page-error', getEffectsError.value) const onCreate = async (data: Effect) => { - await $fetch(`/api/effects`, { + await $fetch(`/api/effect`, { method: 'POST', body: { name: data.name, @@ -41,7 +41,7 @@ const onCreate = async (data: Effect) => { } const onUpdate = async (data: Effect) => { - await $fetch(`/api/effects/${data.id}`, { + await $fetch(`/api/effect/${data.id}`, { method: 'PUT', body: { name: data.name, @@ -54,7 +54,7 @@ const onUpdate = async (data: Effect) => { } const onDelete = async (id: string) => { - await $fetch(`/api/effects/${id}`, { + await $fetch(`/api/effect/${id}`, { method: 'DELETE', body: { solutionId } }).catch((e) => $eventBus.$emit('page-error', e)) diff --git a/pages/o/[organization-slug]/[solution-slug]/environment/glossary.client.vue b/pages/o/[organization-slug]/[solution-slug]/environment/glossary.client.vue index d0fb567a..15ee76ac 100644 --- a/pages/o/[organization-slug]/[solution-slug]/environment/glossary.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/environment/glossary.client.vue @@ -6,7 +6,7 @@ definePageMeta({ name: 'Glossary' }) const { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Glossary').params, - { data: solutions, error: getSolutionError } = await useFetch('/api/solutions', { + { data: solutions, error: getSolutionError } = await useFetch('/api/solution', { query: { slug: solutionslug, organizationSlug: organizationslug @@ -17,7 +17,7 @@ const { $eventBus } = useNuxtApp(), if (getSolutionError.value) $eventBus.$emit('page-error', getSolutionError.value); -const { data: glossaryTerms, refresh, status, error: getGlossaryTermsError } = await useFetch(`/api/glossary-terms`, { +const { data: glossaryTerms, refresh, status, error: getGlossaryTermsError } = await useFetch(`/api/glossary-term`, { query: { solutionId }, transform: (data) => data.map((item) => { item.lastModified = new Date(item.lastModified) @@ -29,7 +29,7 @@ if (getGlossaryTermsError.value) $eventBus.$emit('page-error', getGlossaryTermsError.value) const onCreate = async (data: GlossaryTerm) => { - await $fetch(`/api/glossary-terms`, { + await $fetch(`/api/glossary-term`, { method: 'POST', body: { name: data.name, @@ -42,7 +42,7 @@ const onCreate = async (data: GlossaryTerm) => { } const onUpdate = async (data: GlossaryTerm) => { - await $fetch(`/api/glossary-terms/${data.id}`, { + await $fetch(`/api/glossary-term/${data.id}`, { method: 'PUT', body: { id: data.id, @@ -56,7 +56,7 @@ const onUpdate = async (data: GlossaryTerm) => { } const onDelete = async (id: string) => { - await $fetch(`/api/glossary-terms/${id}`, { + await $fetch(`/api/glossary-term/${id}`, { method: 'DELETE', body: { solutionId } }).catch((e) => $eventBus.$emit('page-error', e)) diff --git a/pages/o/[organization-slug]/[solution-slug]/environment/invariants.client.vue b/pages/o/[organization-slug]/[solution-slug]/environment/invariants.client.vue index 0fac8651..15787423 100644 --- a/pages/o/[organization-slug]/[solution-slug]/environment/invariants.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/environment/invariants.client.vue @@ -6,7 +6,7 @@ definePageMeta({ name: 'Invariants' }) const { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Invariants').params, - { data: solutions, error: getSolutionError } = await useFetch(`/api/solutions`, { + { data: solutions, error: getSolutionError } = await useFetch(`/api/solution`, { query: { slug: solutionslug, organizationSlug: organizationslug @@ -17,7 +17,7 @@ const { $eventBus } = useNuxtApp(), if (getSolutionError.value) $eventBus.$emit('page-error', getSolutionError.value) -const { data: invariants, refresh, status, error: getInvariantsError } = await useFetch(`/api/invariants`, { +const { data: invariants, refresh, status, error: getInvariantsError } = await useFetch(`/api/invariant`, { query: { solutionId }, transform: (data) => data.map((item) => { item.lastModified = new Date(item.lastModified) @@ -29,7 +29,7 @@ if (getInvariantsError.value) $eventBus.$emit('page-error', getInvariantsError.value) const onCreate = async (data: Invariant) => { - await useFetch(`/api/invariants`, { + await useFetch(`/api/invariant`, { method: 'POST', body: { name: data.name, @@ -42,7 +42,7 @@ const onCreate = async (data: Invariant) => { } const onUpdate = async (data: Invariant) => { - await useFetch(`/api/invariants/${data.id}`, { + await useFetch(`/api/invariant/${data.id}`, { method: 'PUT', body: { id: data.id, name: data.name, @@ -55,7 +55,7 @@ const onUpdate = async (data: Invariant) => { } const onDelete = async (id: string) => { - await useFetch(`/api/invariants/${id}`, { + await useFetch(`/api/invariant/${id}`, { method: 'DELETE', body: { solutionId } }).catch((e) => $eventBus.$emit('page-error', e)) diff --git a/pages/o/[organization-slug]/[solution-slug]/goals/functionality.client.vue b/pages/o/[organization-slug]/[solution-slug]/goals/functionality.client.vue index e80c04a0..40156a87 100644 --- a/pages/o/[organization-slug]/[solution-slug]/goals/functionality.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/goals/functionality.client.vue @@ -7,7 +7,7 @@ definePageMeta({ name: 'Goals Functionality' }) const { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Functionality').params, - { data: solutions, error: getSolutionError } = await useFetch(`/api/solutions`, { + { data: solutions, error: getSolutionError } = await useFetch(`/api/solution`, { query: { slug: solutionslug, organizationSlug: organizationslug @@ -18,7 +18,7 @@ const { $eventBus } = useNuxtApp(), if (getSolutionError.value) $eventBus.$emit('page-error', getSolutionError.value) -const { data: functionalBehaviors, refresh, status, error: getFunctionalBehaviorsError } = await useFetch(`/api/functional-behaviors`, { +const { data: functionalBehaviors, refresh, status, error: getFunctionalBehaviorsError } = await useFetch(`/api/functional-behavior`, { query: { solutionId }, transform: (data) => data.map((item) => { item.lastModified = new Date(item.lastModified) @@ -30,7 +30,7 @@ if (getFunctionalBehaviorsError.value) $eventBus.$emit('page-error', getFunctionalBehaviorsError.value); const onCreate = async (data: FunctionalBehavior) => { - await $fetch(`/api/functional-behaviors`, { + await $fetch(`/api/functional-behavior`, { method: 'POST', body: { ...data, @@ -43,7 +43,7 @@ const onCreate = async (data: FunctionalBehavior) => { } const onUpdate = async (data: FunctionalBehavior) => { - await $fetch(`/api/functional-behaviors/${data.id}`, { + await $fetch(`/api/functional-behavior/${data.id}`, { method: 'PUT', body: { ...data, @@ -56,7 +56,7 @@ const onUpdate = async (data: FunctionalBehavior) => { } const onDelete = async (id: string) => { - await $fetch(`/api/functional-behaviors/${id}`, { + await $fetch(`/api/functional-behavior/${id}`, { method: 'DELETE', body: { solutionId } }).catch((e) => $eventBus.$emit('page-error', e)) diff --git a/pages/o/[organization-slug]/[solution-slug]/goals/limitations.client.vue b/pages/o/[organization-slug]/[solution-slug]/goals/limitations.client.vue index 84018da2..6f6af9c5 100644 --- a/pages/o/[organization-slug]/[solution-slug]/goals/limitations.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/goals/limitations.client.vue @@ -6,7 +6,7 @@ definePageMeta({ name: 'Limitations' }) const { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Limitations').params, - { data: solutions, error: getSolutionError } = await useFetch(`/api/solutions`, { + { data: solutions, error: getSolutionError } = await useFetch(`/api/solution`, { query: { slug: solutionslug, organizationSlug: organizationslug @@ -17,7 +17,7 @@ const { $eventBus } = useNuxtApp(), if (getSolutionError.value) $eventBus.$emit('page-error', getSolutionError.value) -const { data: limits, status, refresh, error: getLimitsError } = await useFetch(`/api/limits`, { +const { data: limits, status, refresh, error: getLimitsError } = await useFetch(`/api/limit`, { query: { solutionId }, transform: (data) => data.map((item) => { item.lastModified = new Date(item.lastModified) @@ -29,7 +29,7 @@ if (getLimitsError.value) $eventBus.$emit('page-error', getLimitsError.value) const onCreate = async (data: Limit) => { - await $fetch(`/api/limits`, { + await $fetch(`/api/limit`, { method: 'POST', body: { solutionId, @@ -42,7 +42,7 @@ const onCreate = async (data: Limit) => { } const onUpdate = async (data: Limit) => { - await $fetch(`/api/limits/${data.id}`, { + await $fetch(`/api/limit/${data.id}`, { method: 'PUT', body: { solutionId, id: data.id, @@ -55,7 +55,7 @@ const onUpdate = async (data: Limit) => { } const onDelete = async (id: string) => { - await $fetch(`/api/limits/${id}`, { + await $fetch(`/api/limit/${id}`, { method: 'DELETE', body: { solutionId } }).catch((e) => $eventBus.$emit('page-error', e)) diff --git a/pages/o/[organization-slug]/[solution-slug]/goals/obstacles.client.vue b/pages/o/[organization-slug]/[solution-slug]/goals/obstacles.client.vue index f0ec98ec..d9e500ae 100644 --- a/pages/o/[organization-slug]/[solution-slug]/goals/obstacles.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/goals/obstacles.client.vue @@ -6,7 +6,7 @@ definePageMeta({ name: 'Obstacles' }) const { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Obstacles').params, - { data: solutions, error: getSolutionError } = await useFetch(`/api/solutions`, { + { data: solutions, error: getSolutionError } = await useFetch(`/api/solution`, { query: { slug: solutionslug, organizationSlug: organizationslug @@ -17,7 +17,7 @@ const { $eventBus } = useNuxtApp(), if (getSolutionError.value) $eventBus.$emit('page-error', getSolutionError.value); -const { data: obstacles, refresh, status, error: getObstaclesError } = await useFetch(`/api/obstacles`, { +const { data: obstacles, refresh, status, error: getObstaclesError } = await useFetch(`/api/obstacle`, { query: { solutionId }, transform: (data) => data.map((item) => { item.lastModified = new Date(item.lastModified) @@ -26,7 +26,7 @@ const { data: obstacles, refresh, status, error: getObstaclesError } = await use }) const onCreate = async (data: Obstacle) => { - await $fetch(`/api/obstacles`, { + await $fetch(`/api/obstacle`, { method: 'POST', body: { solutionId, @@ -39,7 +39,7 @@ const onCreate = async (data: Obstacle) => { } const onUpdate = async (data: Obstacle) => { - await $fetch(`/api/obstacles/${data.id}`, { + await $fetch(`/api/obstacle/${data.id}`, { method: 'PUT', body: { solutionId, @@ -52,7 +52,7 @@ const onUpdate = async (data: Obstacle) => { } const onDelete = async (id: string) => { - await $fetch(`/api/obstacles/${id}`, { + await $fetch(`/api/obstacle/${id}`, { method: 'DELETE', body: { solutionId } }).catch((e) => $eventBus.$emit('page-error', e)) diff --git a/pages/o/[organization-slug]/[solution-slug]/goals/outcomes.client.vue b/pages/o/[organization-slug]/[solution-slug]/goals/outcomes.client.vue index 01ff0a50..8027ab0b 100644 --- a/pages/o/[organization-slug]/[solution-slug]/goals/outcomes.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/goals/outcomes.client.vue @@ -6,7 +6,7 @@ definePageMeta({ name: 'Outcomes' }) const { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Outcomes').params, - { data: solutions, error: getSolutionError } = await useFetch(`/api/solutions`, { + { data: solutions, error: getSolutionError } = await useFetch(`/api/solution`, { query: { slug: solutionslug, organizationSlug: organizationslug @@ -17,7 +17,7 @@ const { $eventBus } = useNuxtApp(), if (getSolutionError.value) $eventBus.$emit('page-error', getSolutionError.value); -const { data: outcomes, refresh, status, error: getOutcomesError } = await useFetch(`/api/outcomes`, { +const { data: outcomes, refresh, status, error: getOutcomesError } = await useFetch(`/api/outcome`, { query: { solutionId }, transform: (data) => data.map((item) => { item.lastModified = new Date(item.lastModified) @@ -29,7 +29,7 @@ if (getOutcomesError.value) $eventBus.$emit('page-error', getOutcomesError.value); const onCreate = async (data: Outcome) => { - await $fetch(`/api/outcomes`, { + await $fetch(`/api/outcome`, { method: 'POST', body: { solutionId, @@ -42,7 +42,7 @@ const onCreate = async (data: Outcome) => { } const onUpdate = async (data: Outcome) => { - await $fetch(`/api/outcomes/${data.id}`, { + await $fetch(`/api/outcome/${data.id}`, { method: 'PUT', body: { solutionId, @@ -55,7 +55,7 @@ const onUpdate = async (data: Outcome) => { } const onDelete = async (id: string) => { - await $fetch(`/api/outcomes/${id}`, { + await $fetch(`/api/outcome/${id}`, { method: 'DELETE', body: { solutionId } }).catch((e) => $eventBus.$emit('page-error', e)) diff --git a/pages/o/[organization-slug]/[solution-slug]/goals/rationale.client.vue b/pages/o/[organization-slug]/[solution-slug]/goals/rationale.client.vue index 0f3908c3..a5c0b294 100644 --- a/pages/o/[organization-slug]/[solution-slug]/goals/rationale.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/goals/rationale.client.vue @@ -6,7 +6,7 @@ definePageMeta({ name: 'Rationale' }) const { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Rationale').params, - { data: solutions, error: getSolutionError } = await useFetch(`/api/solutions`, { + { data: solutions, error: getSolutionError } = await useFetch(`/api/solution`, { query: { slug: solutionslug, organizationSlug: organizationslug @@ -23,10 +23,10 @@ const [ { data: situationJustifications, error: getSituationError }, { data: objectiveJustifications, error: getObjectiveError } ] = await Promise.all([ - useFetch(`/api/justifications`, { query: { name: 'Vision', solutionId } }), - useFetch(`/api/justifications`, { query: { name: 'Mission', solutionId } }), - useFetch(`/api/justifications`, { query: { name: 'Situation', solutionId } }), - useFetch(`/api/justifications`, { query: { name: 'Objective', solutionId } }) + useFetch(`/api/justification`, { query: { name: 'Vision', solutionId } }), + useFetch(`/api/justification`, { query: { name: 'Mission', solutionId } }), + useFetch(`/api/justification`, { query: { name: 'Situation', solutionId } }), + useFetch(`/api/justification`, { query: { name: 'Objective', solutionId } }) ]); if (getVisionError.value) @@ -53,7 +53,7 @@ const fieldTriple: [Ref, Justification, string][] = [ fieldTriple.forEach(([goalDescription, justification, _name]) => { watch(goalDescription, debounce(() => { justification.description = goalDescription.value; - $fetch(`/api/justifications/${justification.id}`, { + $fetch(`/api/justification/${justification.id}`, { method: 'PUT', body: { solutionId, diff --git a/pages/o/[organization-slug]/[solution-slug]/goals/scenarios.client.vue b/pages/o/[organization-slug]/[solution-slug]/goals/scenarios.client.vue index 499f6a47..ac65943d 100644 --- a/pages/o/[organization-slug]/[solution-slug]/goals/scenarios.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/goals/scenarios.client.vue @@ -10,7 +10,7 @@ definePageMeta({ name: 'Goal Scenarios' }) const { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Scenarios').params, - { data: solutions, error: getSolutionError } = await useFetch(`/api/solutions`, { + { data: solutions, error: getSolutionError } = await useFetch(`/api/solution`, { query: { slug: solutionslug, organizationSlug: organizationslug @@ -27,16 +27,16 @@ const [ { data: functionalBehaviors, error: getFunctionalBehaviorsError }, { data: outcomes, error: getOutcomesError }, ] = await Promise.all([ - useFetch(`/api/user-stories`, { + useFetch(`/api/user-story`, { query: { solutionId }, transform: (data) => data.map((item) => { item.lastModified = new Date(item.lastModified) return item }) }), - useFetch(`/api/stakeholders`, { query: { solutionId } }), - useFetch(`/api/functional-behaviors`, { query: { solutionId } }), - useFetch(`/api/outcomes`, { query: { solutionId } }) + useFetch(`/api/stakeholder`, { query: { solutionId } }), + useFetch(`/api/functional-behavior`, { query: { solutionId } }), + useFetch(`/api/outcome`, { query: { solutionId } }) ]) if (getUserStoriesError.value) @@ -49,7 +49,7 @@ if (getOutcomesError.value) $eventBus.$emit('page-error', getOutcomesError.value); const onUserStoryCreate = async (userStory: UserStory) => { - await $fetch(`/api/user-stories`, { + await $fetch(`/api/user-story`, { method: 'POST', body: { ...userStory, @@ -63,7 +63,7 @@ const onUserStoryCreate = async (userStory: UserStory) => { } const onUserStoryUpdate = async (userStory: UserStory) => { - await $fetch(`/api/user-stories/${userStory.id}`, { + await $fetch(`/api/user-story/${userStory.id}`, { method: 'PUT', body: { ...userStory, @@ -77,7 +77,7 @@ const onUserStoryUpdate = async (userStory: UserStory) => { } const onUserStoryDelete = async (id: string) => { - await $fetch(`/api/user-stories/${id}`, { + await $fetch(`/api/user-story/${id}`, { method: 'DELETE', body: { solutionId } }).catch((e) => $eventBus.$emit('page-error', e)); diff --git a/pages/o/[organization-slug]/[solution-slug]/goals/stakeholders.client.vue b/pages/o/[organization-slug]/[solution-slug]/goals/stakeholders.client.vue index 296f056a..51eace6a 100644 --- a/pages/o/[organization-slug]/[solution-slug]/goals/stakeholders.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/goals/stakeholders.client.vue @@ -10,7 +10,7 @@ definePageMeta({ name: 'Stakeholders' }) const config = useAppConfig(), { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Stakeholders').params, - { data: solutions, error: getSolutionError } = await useFetch(`/api/solutions`, { + { data: solutions, error: getSolutionError } = await useFetch(`/api/solution`, { query: { slug: solutionslug, organizationSlug: organizationslug @@ -21,7 +21,7 @@ const config = useAppConfig(), if (getSolutionError.value) $eventBus.$emit('page-error', getSolutionError.value); -const { data: stakeholders, refresh: refreshStakeholders, status, error: getStakeholdersError } = await useFetch(`/api/stakeholders`, { +const { data: stakeholders, refresh: refreshStakeholders, status, error: getStakeholdersError } = await useFetch(`/api/stakeholder`, { query: { solutionId }, transform: (data) => data.map((item) => { item.lastModified = new Date(item.lastModified) @@ -75,10 +75,15 @@ const renderChart = async () => { watch(stakeholders, renderChart); const onCreate = async (data: Stakeholder) => { - await $fetch(`/api/stakeholders`, { + await $fetch(`/api/stakeholder`, { method: 'POST', body: { - ...data, + name: data.name, + description: data.description, + category: data.category, + segmentation: data.segmentation, + availability: Number(data.availability), + influence: Number(data.influence), solutionId } }).catch((e) => $eventBus.$emit('page-error', e)) @@ -87,10 +92,15 @@ const onCreate = async (data: Stakeholder) => { } const onUpdate = async (data: Stakeholder) => { - await $fetch(`/api/stakeholders/${data.id}`, { + await $fetch(`/api/stakeholder/${data.id}`, { method: 'PUT', body: { - ...data, + name: data.name, + description: data.description, + category: data.category, + segmentation: data.segmentation, + availability: Number(data.availability), + influence: Number(data.influence), solutionId } }).catch((e) => $eventBus.$emit('page-error', e)) @@ -99,7 +109,7 @@ const onUpdate = async (data: Stakeholder) => { } const onDelete = async (id: string) => { - await $fetch(`/api/stakeholders/${id}`, { + await $fetch(`/api/stakeholder/${id}`, { method: 'DELETE', body: { solutionId } }).catch((e) => $eventBus.$emit('page-error', e)) diff --git a/pages/o/[organization-slug]/[solution-slug]/index.client.vue b/pages/o/[organization-slug]/[solution-slug]/index.client.vue index 2f16e42d..08fd399e 100644 --- a/pages/o/[organization-slug]/[solution-slug]/index.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/index.client.vue @@ -5,7 +5,7 @@ definePageMeta({ name: 'Solution' }) const { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Solution').params, router = useRouter(), - { data: solutions, error: solutionError } = await useFetch('/api/solutions', { + { data: solutions, error: solutionError } = await useFetch('/api/solution', { query: { organizationSlug: organizationslug, slug: solutionslug @@ -36,7 +36,7 @@ const handleSolutionDelete = async () => { rejectLabel: 'Cancel', acceptLabel: 'Delete', accept: async () => { - await $fetch(`/api/solutions/${solution.id}`, { + await $fetch(`/api/solution/${solution.id}`, { method: 'delete' }).catch((e) => $eventBus.$emit('page-error', e)) router.push({ name: 'Organization', params: { organizationslug } }) @@ -54,7 +54,7 @@ const parsingRequirements = ref(false), const parseRawRequirement = async () => { parsingRequirements.value = true parsingError.value = '' - const response = await $fetch('/api/parse-requirements', { + const response = await $fetch('/api/parse-requirement', { method: 'post', body: { solutionId: solution.id, diff --git a/pages/o/[organization-slug]/[solution-slug]/project/roles-personnel.client.vue b/pages/o/[organization-slug]/[solution-slug]/project/roles-personnel.client.vue index e2a84a31..7a834e30 100644 --- a/pages/o/[organization-slug]/[solution-slug]/project/roles-personnel.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/project/roles-personnel.client.vue @@ -6,7 +6,7 @@ definePageMeta({ name: 'Roles & Personnel' }) const { $eventBus } = useNuxtApp(), { solutionslug, organizationslug } = useRoute('Roles & Personnel').params, - { data: solutions, error: getSolutionError } = await useFetch(`/api/solutions`, { + { data: solutions, error: getSolutionError } = await useFetch(`/api/solution`, { query: { slug: solutionslug, organizationSlug: organizationslug @@ -17,7 +17,7 @@ const { $eventBus } = useNuxtApp(), if (getSolutionError.value) $eventBus.$emit('page-error', getSolutionError.value) -const { data: personnel, refresh, status, error: getPersonnelError } = await useFetch(`/api/persons`, { +const { data: personnel, refresh, status, error: getPersonnelError } = await useFetch(`/api/person`, { query: { solutionId }, transform: (data) => data.map((item) => { item.lastModified = new Date(item.lastModified) @@ -29,7 +29,7 @@ if (getPersonnelError.value) $eventBus.$emit('page-error', getPersonnelError.value) const onCreate = async (data: Person) => { - await $fetch(`/api/persons`, { + await $fetch(`/api/person`, { method: 'POST', body: { name: data.name ?? 'Anonymous', @@ -43,7 +43,7 @@ const onCreate = async (data: Person) => { } const onUpdate = async (data: Person) => { - await $fetch(`/api/persons/${data.id}`, { + await $fetch(`/api/person/${data.id}`, { method: 'PUT', body: { name: data.name ?? 'Anonymous', @@ -57,7 +57,7 @@ const onUpdate = async (data: Person) => { } const onDelete = async (id: string) => { - await $fetch(`/api/persons/${id}`, { + await $fetch(`/api/person/${id}`, { method: 'DELETE', body: { solutionId } }).catch((e) => $eventBus.$emit('page-error', e)) diff --git a/pages/o/[organization-slug]/[solution-slug]/system/components.client.vue b/pages/o/[organization-slug]/[solution-slug]/system/components.client.vue index e56d5cd4..a2c7fb94 100644 --- a/pages/o/[organization-slug]/[solution-slug]/system/components.client.vue +++ b/pages/o/[organization-slug]/[solution-slug]/system/components.client.vue @@ -1,12 +1,13 @@ \ No newline at end of file diff --git a/pages/o/[organization-slug]/edit-entry.client.vue b/pages/o/[organization-slug]/edit-entry.client.vue index a1cddaab..77b64c88 100644 --- a/pages/o/[organization-slug]/edit-entry.client.vue +++ b/pages/o/[organization-slug]/edit-entry.client.vue @@ -5,7 +5,7 @@ definePageMeta({ name: 'Edit Organization' }) const router = useRouter(), { $eventBus } = useNuxtApp(), { organizationslug } = useRoute('Edit Organization').params, - { data: organizations, error: getOrgError } = await useFetch(`/api/organizations/`, { query: { slug: organizationslug } }), + { data: organizations, error: getOrgError } = await useFetch(`/api/organization/`, { query: { slug: organizationslug } }), organization = ref(organizations.value![0]), newSlug = ref(organization.value.slug); @@ -13,7 +13,7 @@ if (getOrgError.value) $eventBus.$emit('page-error', getOrgError.value); const updateOrganization = async () => { - await $fetch(`/api/organizations/${organization.value.id}`, { + await $fetch(`/api/organization/${organization.value.id}`, { method: 'PUT', body: { name: organization.value.name, diff --git a/pages/o/[organization-slug]/index.client.vue b/pages/o/[organization-slug]/index.client.vue index 84606bad..fb77e4fa 100644 --- a/pages/o/[organization-slug]/index.client.vue +++ b/pages/o/[organization-slug]/index.client.vue @@ -5,11 +5,11 @@ definePageMeta({ name: 'Organization' }) const { $eventBus } = useNuxtApp(), { organizationslug } = useRoute('Organization').params, router = useRouter(), - { data: organizations, error: getOrgError } = await useFetch('/api/organizations', { + { data: organizations, error: getOrgError } = await useFetch('/api/organization', { query: { slug: organizationslug } }), organization = organizations.value![0], - { refresh: refreshSolutions, status, data: solutions, error: getSolutionError } = await useFetch('/api/solutions', { + { refresh: refreshSolutions, status, data: solutions, error: getSolutionError } = await useFetch('/api/solution', { query: { organizationSlug: organizationslug } }), confirm = useConfirm() @@ -35,7 +35,7 @@ const handleOrganizationDelete = async () => { rejectLabel: 'Cancel', acceptLabel: 'Delete', accept: async () => { - await $fetch(`/api/organizations/${organization.id}`, { + await $fetch(`/api/organization/${organization.id}`, { method: 'delete' }).catch((e) => $eventBus.$emit('page-error', e)) router.push({ name: 'Home' }) @@ -60,7 +60,7 @@ const handleSolutionDelete = async (solution: SolutionModel) => { rejectLabel: 'Cancel', acceptLabel: 'Delete', accept: async () => { - await $fetch(`/api/solutions/${solution.id}`, { + await $fetch(`/api/solution/${solution.id}`, { method: 'delete' }).catch((e) => $eventBus.$emit('page-error', e)) await refreshSolutions() diff --git a/pages/o/[organization-slug]/new-solution.client.vue b/pages/o/[organization-slug]/new-solution.client.vue index e9c9762f..32dccf2d 100644 --- a/pages/o/[organization-slug]/new-solution.client.vue +++ b/pages/o/[organization-slug]/new-solution.client.vue @@ -9,7 +9,7 @@ const { $eventBus } = useNuxtApp(), slug = ref(''), description = ref('') -const { data: organizations, error: getOrgError } = await useFetch('/api/organizations', { +const { data: organizations, error: getOrgError } = await useFetch('/api/organization', { query: { slug: organizationslug } }), organization = organizations.value![0], @@ -20,7 +20,7 @@ if (getOrgError.value) const createSolution = async () => { try { - const solutionId = (await $fetch('/api/solutions', { + const solutionId = (await $fetch('/api/solution', { method: 'post', body: { name: name.value, @@ -30,7 +30,7 @@ const createSolution = async () => { }).catch((e) => $eventBus.$emit('page-error', e))); if (solutionId) { - const newSolution = (await $fetch(`/api/solutions/${solutionId}`)); + const newSolution = (await $fetch(`/api/solution/${solutionId}`)); router.push({ name: 'Solution', params: { organizationslug, solutionslug: newSolution.slug } }); } else { $eventBus.$emit('page-error', 'Failed to create solution. No solution ID returned.'); diff --git a/pages/o/[organization-slug]/users.vue b/pages/o/[organization-slug]/users.vue index df7ef33c..8a901d9f 100644 --- a/pages/o/[organization-slug]/users.vue +++ b/pages/o/[organization-slug]/users.vue @@ -8,7 +8,7 @@ definePageMeta({ name: 'Organization Users' }) const { $eventBus } = useNuxtApp() const { organizationslug } = useRoute('Organization Users').params, - { data: organizations, error: getOrgError } = await useFetch(`/api/organizations/`, { + { data: organizations, error: getOrgError } = await useFetch(`/api/organization/`, { query: { slug: organizationslug } }), organization = ref(organizations.value?.[0]!) diff --git a/server/api/appusers/[id].delete.ts b/server/api/appusers/[id].delete.ts index d243e673..32a5f6d6 100644 --- a/server/api/appusers/[id].delete.ts +++ b/server/api/appusers/[id].delete.ts @@ -43,6 +43,5 @@ export default defineEventHandler(async (event) => { }) // Removing the relationship to the organization and NOT the appuser itself - em.remove(appUserRole) - await em.flush() + await em.remove(appUserRole).flush() }) \ No newline at end of file diff --git a/server/api/assumptions/[id].delete.ts b/server/api/assumption/[id].delete.ts similarity index 100% rename from server/api/assumptions/[id].delete.ts rename to server/api/assumption/[id].delete.ts diff --git a/server/api/assumptions/[id].get.ts b/server/api/assumption/[id].get.ts similarity index 93% rename from server/api/assumptions/[id].get.ts rename to server/api/assumption/[id].get.ts index f58e8aac..83c51664 100644 --- a/server/api/assumptions/[id].get.ts +++ b/server/api/assumption/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { Assumption } from "~/server/domain/requirements" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/assumptions/[id].put.ts b/server/api/assumption/[id].put.ts similarity index 96% rename from server/api/assumptions/[id].put.ts rename to server/api/assumption/[id].put.ts index aec286e2..f87b11da 100644 --- a/server/api/assumptions/[id].put.ts +++ b/server/api/assumption/[id].put.ts @@ -31,6 +31,6 @@ export default defineEventHandler(async (event) => { lastModified: new Date() }) - await em.flush() + await em.persistAndFlush(assumption) }) diff --git a/server/api/assumptions/index.get.ts b/server/api/assumption/index.get.ts similarity index 100% rename from server/api/assumptions/index.get.ts rename to server/api/assumption/index.get.ts diff --git a/server/api/assumptions/index.post.ts b/server/api/assumption/index.post.ts similarity index 100% rename from server/api/assumptions/index.post.ts rename to server/api/assumption/index.post.ts diff --git a/server/api/audit-log/deleted.get.ts b/server/api/audit-log/deleted.get.ts index 21948cf3..788c9fef 100644 --- a/server/api/audit-log/deleted.get.ts +++ b/server/api/audit-log/deleted.get.ts @@ -1,6 +1,7 @@ import { ChangeSetType } from "@mikro-orm/core" import { z } from "zod" import { fork } from "~/server/data/orm.js" +import { AuditLog } from "~/server/domain/application" import { Organization } from "~/server/domain/requirements/index.js" const querySchema = z.object({ @@ -51,7 +52,7 @@ export default defineEventHandler(async (event) => { WHERE d.type = 'delete' AND a.entity_name = ?; `, [entityName]), - auditLogs = res.map((row) => ({ + auditLogs = res.map((row) => new AuditLog({ id: row.id, createdAt: new Date(row.created_at), type: row.type, diff --git a/server/api/auth/[...].ts b/server/api/auth/[...].ts index eeb0519f..c0e79dfb 100644 --- a/server/api/auth/[...].ts +++ b/server/api/auth/[...].ts @@ -50,7 +50,7 @@ export default NuxtAuthHandler({ appUser.lastLoginDate = new Date() } - await em.flush() + await em.persistAndFlush(appUser) Object.assign(token, { id: p.oid, diff --git a/server/api/constraints/[id].delete.ts b/server/api/constraint/[id].delete.ts similarity index 100% rename from server/api/constraints/[id].delete.ts rename to server/api/constraint/[id].delete.ts diff --git a/server/api/constraints/[id].get.ts b/server/api/constraint/[id].get.ts similarity index 93% rename from server/api/constraints/[id].get.ts rename to server/api/constraint/[id].get.ts index b5f7b7fd..885433d7 100644 --- a/server/api/constraints/[id].get.ts +++ b/server/api/constraint/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { Constraint } from "~/server/domain/requirements/Constraint.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/constraints/[id].put.ts b/server/api/constraint/[id].put.ts similarity index 96% rename from server/api/constraints/[id].put.ts rename to server/api/constraint/[id].put.ts index b7f765f7..0f3b8561 100644 --- a/server/api/constraints/[id].put.ts +++ b/server/api/constraint/[id].put.ts @@ -33,5 +33,5 @@ export default defineEventHandler(async (event) => { lastModified: new Date() }) - await em.flush() + await em.persistAndFlush(constraint) }) \ No newline at end of file diff --git a/server/api/constraints/index.get.ts b/server/api/constraint/index.get.ts similarity index 100% rename from server/api/constraints/index.get.ts rename to server/api/constraint/index.get.ts diff --git a/server/api/constraints/index.post.ts b/server/api/constraint/index.post.ts similarity index 100% rename from server/api/constraints/index.post.ts rename to server/api/constraint/index.post.ts diff --git a/server/api/effects/[id].delete.ts b/server/api/effect/[id].delete.ts similarity index 100% rename from server/api/effects/[id].delete.ts rename to server/api/effect/[id].delete.ts diff --git a/server/api/effects/[id].get.ts b/server/api/effect/[id].get.ts similarity index 92% rename from server/api/effects/[id].get.ts rename to server/api/effect/[id].get.ts index 06f40845..11b591ab 100644 --- a/server/api/effects/[id].get.ts +++ b/server/api/effect/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { Effect } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/effects/[id].put.ts b/server/api/effect/[id].put.ts similarity index 96% rename from server/api/effects/[id].put.ts rename to server/api/effect/[id].put.ts index c8dff168..c08f4337 100644 --- a/server/api/effects/[id].put.ts +++ b/server/api/effect/[id].put.ts @@ -31,5 +31,5 @@ export default defineEventHandler(async (event) => { lastModified: new Date(), }) - await em.flush() + await em.persistAndFlush(effect) }) \ No newline at end of file diff --git a/server/api/effects/index.get.ts b/server/api/effect/index.get.ts similarity index 100% rename from server/api/effects/index.get.ts rename to server/api/effect/index.get.ts diff --git a/server/api/effects/index.post.ts b/server/api/effect/index.post.ts similarity index 100% rename from server/api/effects/index.post.ts rename to server/api/effect/index.post.ts diff --git a/server/api/environment-components/[id].delete.ts b/server/api/environment-component/[id].delete.ts similarity index 100% rename from server/api/environment-components/[id].delete.ts rename to server/api/environment-component/[id].delete.ts diff --git a/server/api/environment-components/[id].get.ts b/server/api/environment-component/[id].get.ts similarity index 84% rename from server/api/environment-components/[id].get.ts rename to server/api/environment-component/[id].get.ts index 00aa0c72..53a5fb3f 100644 --- a/server/api/environment-components/[id].get.ts +++ b/server/api/environment-component/[id].get.ts @@ -1,8 +1,6 @@ -import { m } from "@vite-pwa/assets-generator/dist/shared/assets-generator.5e51fd40.mjs" import { z } from "zod" import { fork } from "~/server/data/orm.js" import { EnvironmentComponent } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/environment-components/[id].put.ts b/server/api/environment-component/[id].put.ts similarity index 64% rename from server/api/environment-components/[id].put.ts rename to server/api/environment-component/[id].put.ts index b46b7017..8d0e3242 100644 --- a/server/api/environment-components/[id].put.ts +++ b/server/api/environment-component/[id].put.ts @@ -1,5 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" +import { Belongs } from "~/server/domain/relations" import { EnvironmentComponent } from "~/server/domain/requirements/index.js" const paramSchema = z.object({ @@ -23,16 +24,31 @@ export default defineEventHandler(async (event) => { { sessionUser, solution } = await assertSolutionContributor(event, solutionId), em = fork(), environmentComponent = await assertReqBelongsToSolution(em, EnvironmentComponent, id, solution), - parentComponent = parentComponentId ? await assertReqBelongsToSolution(em, EnvironmentComponent, parentComponentId, solution) : environmentComponent.parentComponent + parentComponent = parentComponentId ? await assertReqBelongsToSolution(em, EnvironmentComponent, parentComponentId, solution) : undefined environmentComponent.assign({ name: name ?? environmentComponent.name, description: description ?? environmentComponent.description, isSilence: isSilence ?? environmentComponent.isSilence, modifiedBy: sessionUser, - parentComponent, lastModified: new Date() }) - await em.flush() + const existingParentComponent = await em.findOne(Belongs, { + left: environmentComponent, + right: parentComponent + }) + + if (!existingParentComponent && parentComponent) { + em.create(Belongs, { left: environmentComponent, right: parentComponent }) + } else if (existingParentComponent && !parentComponent) { + em.remove(existingParentComponent) + } else if (existingParentComponent && parentComponent) { + em.remove(existingParentComponent) + em.create(Belongs, { left: environmentComponent, right: parentComponent }) + } else { + // Do nothing + } + + await em.persistAndFlush(environmentComponent) }) \ No newline at end of file diff --git a/server/api/environment-components/index.get.ts b/server/api/environment-component/index.get.ts similarity index 100% rename from server/api/environment-components/index.get.ts rename to server/api/environment-component/index.get.ts diff --git a/server/api/environment-components/index.post.ts b/server/api/environment-component/index.post.ts similarity index 89% rename from server/api/environment-components/index.post.ts rename to server/api/environment-component/index.post.ts index d618601b..2c8a8dc0 100644 --- a/server/api/environment-components/index.post.ts +++ b/server/api/environment-component/index.post.ts @@ -24,12 +24,14 @@ export default defineEventHandler(async (event) => { description, lastModified: new Date(), modifiedBy: sessionUser, - isSilence, - parentComponent: parentComponentId ? em.getReference(EnvironmentComponent, parentComponentId) : undefined + isSilence }) em.create(Belongs, { left: newEnvironmentComponent, right: solution }) + if (parentComponentId) + em.create(Belongs, { left: newEnvironmentComponent, right: parentComponentId }) + await em.flush() return newEnvironmentComponent.id diff --git a/server/api/functional-behaviors/[id].delete.ts b/server/api/functional-behavior/[id].delete.ts similarity index 100% rename from server/api/functional-behaviors/[id].delete.ts rename to server/api/functional-behavior/[id].delete.ts diff --git a/server/api/functional-behaviors/[id].get.ts b/server/api/functional-behavior/[id].get.ts similarity index 93% rename from server/api/functional-behaviors/[id].get.ts rename to server/api/functional-behavior/[id].get.ts index aee71850..3a110add 100644 --- a/server/api/functional-behaviors/[id].get.ts +++ b/server/api/functional-behavior/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { FunctionalBehavior } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/functional-behaviors/[id].put.ts b/server/api/functional-behavior/[id].put.ts similarity index 96% rename from server/api/functional-behaviors/[id].put.ts rename to server/api/functional-behavior/[id].put.ts index e2b28fb1..7884b19a 100644 --- a/server/api/functional-behaviors/[id].put.ts +++ b/server/api/functional-behavior/[id].put.ts @@ -33,5 +33,5 @@ export default defineEventHandler(async (event) => { ...(isSilence !== undefined && { isSilence }) }) - await em.flush() + await em.persistAndFlush(functionalBehavior) }) \ No newline at end of file diff --git a/server/api/functional-behaviors/index.get.ts b/server/api/functional-behavior/index.get.ts similarity index 100% rename from server/api/functional-behaviors/index.get.ts rename to server/api/functional-behavior/index.get.ts diff --git a/server/api/functional-behaviors/index.post.ts b/server/api/functional-behavior/index.post.ts similarity index 100% rename from server/api/functional-behaviors/index.post.ts rename to server/api/functional-behavior/index.post.ts diff --git a/server/api/glossary-terms/[id].delete.ts b/server/api/glossary-term/[id].delete.ts similarity index 100% rename from server/api/glossary-terms/[id].delete.ts rename to server/api/glossary-term/[id].delete.ts diff --git a/server/api/glossary-terms/[id].get.ts b/server/api/glossary-term/[id].get.ts similarity index 93% rename from server/api/glossary-terms/[id].get.ts rename to server/api/glossary-term/[id].get.ts index d093ab82..1944984b 100644 --- a/server/api/glossary-terms/[id].get.ts +++ b/server/api/glossary-term/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { GlossaryTerm } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/glossary-terms/[id].put.ts b/server/api/glossary-term/[id].put.ts similarity index 96% rename from server/api/glossary-terms/[id].put.ts rename to server/api/glossary-term/[id].put.ts index adfa7a70..96e5df86 100644 --- a/server/api/glossary-terms/[id].put.ts +++ b/server/api/glossary-term/[id].put.ts @@ -33,5 +33,5 @@ export default defineEventHandler(async (event) => { lastModified: new Date() }) - await em.flush() + await em.persistAndFlush(glossaryTerm) }) \ No newline at end of file diff --git a/server/api/glossary-terms/index.get.ts b/server/api/glossary-term/index.get.ts similarity index 100% rename from server/api/glossary-terms/index.get.ts rename to server/api/glossary-term/index.get.ts diff --git a/server/api/glossary-terms/index.post.ts b/server/api/glossary-term/index.post.ts similarity index 76% rename from server/api/glossary-terms/index.post.ts rename to server/api/glossary-term/index.post.ts index 36d9c8de..10530f2c 100644 --- a/server/api/glossary-terms/index.post.ts +++ b/server/api/glossary-term/index.post.ts @@ -7,6 +7,7 @@ const bodySchema = z.object({ solutionId: z.string().uuid(), name: z.string().default("{Untitled Glossary Term}"), description: z.string().default(""), + parentComponentId: z.string().uuid().optional(), isSilence: z.boolean().default(false) }) @@ -14,14 +15,13 @@ const bodySchema = z.object({ * Creates a new glossary term and returns its id */ export default defineEventHandler(async (event) => { - const { name, description, solutionId, isSilence } = await validateEventBody(event, bodySchema), + const { name, description, solutionId, isSilence, parentComponentId } = await validateEventBody(event, bodySchema), { solution, sessionUser } = await assertSolutionContributor(event, solutionId), em = fork() const glossaryTerm = em.create(GlossaryTerm, { name, description, - parentComponent: undefined, lastModified: new Date(), modifiedBy: sessionUser, isSilence @@ -29,6 +29,9 @@ export default defineEventHandler(async (event) => { em.create(Belongs, { left: glossaryTerm, right: solution }) + if (parentComponentId) + em.create(Belongs, { left: glossaryTerm, right: parentComponentId }) + await em.flush() return glossaryTerm.id diff --git a/server/api/invariants/[id].delete.ts b/server/api/invariant/[id].delete.ts similarity index 100% rename from server/api/invariants/[id].delete.ts rename to server/api/invariant/[id].delete.ts diff --git a/server/api/invariants/[id].get.ts b/server/api/invariant/[id].get.ts similarity index 93% rename from server/api/invariants/[id].get.ts rename to server/api/invariant/[id].get.ts index fbe1d78e..5dc0e54a 100644 --- a/server/api/invariants/[id].get.ts +++ b/server/api/invariant/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { Invariant } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/invariants/[id].put.ts b/server/api/invariant/[id].put.ts similarity index 96% rename from server/api/invariants/[id].put.ts rename to server/api/invariant/[id].put.ts index 0b964a86..daa80631 100644 --- a/server/api/invariants/[id].put.ts +++ b/server/api/invariant/[id].put.ts @@ -31,5 +31,5 @@ export default defineEventHandler(async (event) => { lastModified: new Date() }) - await em.flush() + await em.persistAndFlush(invariant) }) \ No newline at end of file diff --git a/server/api/invariants/index.get.ts b/server/api/invariant/index.get.ts similarity index 100% rename from server/api/invariants/index.get.ts rename to server/api/invariant/index.get.ts diff --git a/server/api/invariants/index.post.ts b/server/api/invariant/index.post.ts similarity index 100% rename from server/api/invariants/index.post.ts rename to server/api/invariant/index.post.ts diff --git a/server/api/justifications/[id].delete.ts b/server/api/justification/[id].delete.ts similarity index 100% rename from server/api/justifications/[id].delete.ts rename to server/api/justification/[id].delete.ts diff --git a/server/api/justifications/[id].get.ts b/server/api/justification/[id].get.ts similarity index 93% rename from server/api/justifications/[id].get.ts rename to server/api/justification/[id].get.ts index 6d5e9e90..85baca64 100644 --- a/server/api/justifications/[id].get.ts +++ b/server/api/justification/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { Justification } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/justifications/[id].put.ts b/server/api/justification/[id].put.ts similarity index 96% rename from server/api/justifications/[id].put.ts rename to server/api/justification/[id].put.ts index e251692d..cdeb3caa 100644 --- a/server/api/justifications/[id].put.ts +++ b/server/api/justification/[id].put.ts @@ -32,5 +32,5 @@ export default defineEventHandler(async (event) => { lastModified: new Date() }) - await em.flush() + await em.persistAndFlush(justification) }) \ No newline at end of file diff --git a/server/api/justifications/index.get.ts b/server/api/justification/index.get.ts similarity index 100% rename from server/api/justifications/index.get.ts rename to server/api/justification/index.get.ts diff --git a/server/api/justifications/index.post.ts b/server/api/justification/index.post.ts similarity index 100% rename from server/api/justifications/index.post.ts rename to server/api/justification/index.post.ts diff --git a/server/api/limits/[id].delete.ts b/server/api/limit/[id].delete.ts similarity index 100% rename from server/api/limits/[id].delete.ts rename to server/api/limit/[id].delete.ts diff --git a/server/api/limits/[id].get.ts b/server/api/limit/[id].get.ts similarity index 92% rename from server/api/limits/[id].get.ts rename to server/api/limit/[id].get.ts index cbddbc63..1afc454c 100644 --- a/server/api/limits/[id].get.ts +++ b/server/api/limit/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { Limit } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/limits/[id].put.ts b/server/api/limit/[id].put.ts similarity index 96% rename from server/api/limits/[id].put.ts rename to server/api/limit/[id].put.ts index 640ad00d..0d7dbff3 100644 --- a/server/api/limits/[id].put.ts +++ b/server/api/limit/[id].put.ts @@ -31,5 +31,5 @@ export default defineEventHandler(async (event) => { lastModified: new Date() }) - await em.flush() + await em.persistAndFlush(limit) }) \ No newline at end of file diff --git a/server/api/limits/index.get.ts b/server/api/limit/index.get.ts similarity index 100% rename from server/api/limits/index.get.ts rename to server/api/limit/index.get.ts diff --git a/server/api/limits/index.post.ts b/server/api/limit/index.post.ts similarity index 100% rename from server/api/limits/index.post.ts rename to server/api/limit/index.post.ts diff --git a/server/api/non-functional-behaviors/[id].delete.ts b/server/api/non-functional-behavior/[id].delete.ts similarity index 100% rename from server/api/non-functional-behaviors/[id].delete.ts rename to server/api/non-functional-behavior/[id].delete.ts diff --git a/server/api/non-functional-behaviors/[id].get.ts b/server/api/non-functional-behavior/[id].get.ts similarity index 93% rename from server/api/non-functional-behaviors/[id].get.ts rename to server/api/non-functional-behavior/[id].get.ts index 79cd866f..db17b958 100644 --- a/server/api/non-functional-behaviors/[id].get.ts +++ b/server/api/non-functional-behavior/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { NonFunctionalBehavior } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/non-functional-behaviors/[id].put.ts b/server/api/non-functional-behavior/[id].put.ts similarity index 100% rename from server/api/non-functional-behaviors/[id].put.ts rename to server/api/non-functional-behavior/[id].put.ts diff --git a/server/api/non-functional-behaviors/index.get.ts b/server/api/non-functional-behavior/index.get.ts similarity index 100% rename from server/api/non-functional-behaviors/index.get.ts rename to server/api/non-functional-behavior/index.get.ts diff --git a/server/api/non-functional-behaviors/index.post.ts b/server/api/non-functional-behavior/index.post.ts similarity index 100% rename from server/api/non-functional-behaviors/index.post.ts rename to server/api/non-functional-behavior/index.post.ts diff --git a/server/api/obstacles/[id].delete.ts b/server/api/obstacle/[id].delete.ts similarity index 100% rename from server/api/obstacles/[id].delete.ts rename to server/api/obstacle/[id].delete.ts diff --git a/server/api/obstacles/[id].get.ts b/server/api/obstacle/[id].get.ts similarity index 92% rename from server/api/obstacles/[id].get.ts rename to server/api/obstacle/[id].get.ts index d13ae8d3..03b60f53 100644 --- a/server/api/obstacles/[id].get.ts +++ b/server/api/obstacle/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { Obstacle } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/obstacles/[id].put.ts b/server/api/obstacle/[id].put.ts similarity index 96% rename from server/api/obstacles/[id].put.ts rename to server/api/obstacle/[id].put.ts index 0e7ae544..44170ac4 100644 --- a/server/api/obstacles/[id].put.ts +++ b/server/api/obstacle/[id].put.ts @@ -31,5 +31,5 @@ export default defineEventHandler(async (event) => { lastModified: new Date() }) - await em.flush() + await em.persistAndFlush(obstacle) }) \ No newline at end of file diff --git a/server/api/obstacles/index.get.ts b/server/api/obstacle/index.get.ts similarity index 100% rename from server/api/obstacles/index.get.ts rename to server/api/obstacle/index.get.ts diff --git a/server/api/obstacles/index.post.ts b/server/api/obstacle/index.post.ts similarity index 100% rename from server/api/obstacles/index.post.ts rename to server/api/obstacle/index.post.ts diff --git a/server/api/organizations/[id].delete.ts b/server/api/organization/[id].delete.ts similarity index 100% rename from server/api/organizations/[id].delete.ts rename to server/api/organization/[id].delete.ts diff --git a/server/api/organizations/[id].get.ts b/server/api/organization/[id].get.ts similarity index 100% rename from server/api/organizations/[id].get.ts rename to server/api/organization/[id].get.ts diff --git a/server/api/organizations/[id].put.ts b/server/api/organization/[id].put.ts similarity index 94% rename from server/api/organizations/[id].put.ts rename to server/api/organization/[id].put.ts index d3d01cb6..d03bf8f1 100644 --- a/server/api/organizations/[id].put.ts +++ b/server/api/organization/[id].put.ts @@ -26,5 +26,5 @@ export default defineEventHandler(async (event) => { slug: name ? slugify(name) : organization.slug }) - await em.flush() + await em.persistAndFlush(organization) }) \ No newline at end of file diff --git a/server/api/organizations/index.get.ts b/server/api/organization/index.get.ts similarity index 100% rename from server/api/organizations/index.get.ts rename to server/api/organization/index.get.ts diff --git a/server/api/organizations/index.post.ts b/server/api/organization/index.post.ts similarity index 100% rename from server/api/organizations/index.post.ts rename to server/api/organization/index.post.ts diff --git a/server/api/outcomes/[id].delete.ts b/server/api/outcome/[id].delete.ts similarity index 100% rename from server/api/outcomes/[id].delete.ts rename to server/api/outcome/[id].delete.ts diff --git a/server/api/outcomes/[id].get.ts b/server/api/outcome/[id].get.ts similarity index 92% rename from server/api/outcomes/[id].get.ts rename to server/api/outcome/[id].get.ts index 169f1d9e..aad419c6 100644 --- a/server/api/outcomes/[id].get.ts +++ b/server/api/outcome/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { Outcome } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/outcomes/[id].put.ts b/server/api/outcome/[id].put.ts similarity index 96% rename from server/api/outcomes/[id].put.ts rename to server/api/outcome/[id].put.ts index fdf50a29..45ad66a5 100644 --- a/server/api/outcomes/[id].put.ts +++ b/server/api/outcome/[id].put.ts @@ -31,5 +31,5 @@ export default defineEventHandler(async (event) => { lastModified: new Date() }) - await em.flush() + await em.persistAndFlush(outcome) }) \ No newline at end of file diff --git a/server/api/outcomes/index.get.ts b/server/api/outcome/index.get.ts similarity index 100% rename from server/api/outcomes/index.get.ts rename to server/api/outcome/index.get.ts diff --git a/server/api/outcomes/index.post.ts b/server/api/outcome/index.post.ts similarity index 100% rename from server/api/outcomes/index.post.ts rename to server/api/outcome/index.post.ts diff --git a/server/api/parse-requirement/follows.get.ts b/server/api/parse-requirement/follows.get.ts new file mode 100644 index 00000000..7f8cac53 --- /dev/null +++ b/server/api/parse-requirement/follows.get.ts @@ -0,0 +1,30 @@ +import { z } from "zod" +import { fork } from "~/server/data/orm.js" +import { ParsedRequirement, Requirement } from "~/server/domain/requirements/index.js" +import { Follows } from "~/server/domain/relations/index.js" + +const querySchema = z.object({ + solutionId: z.string().uuid(), + id: z.string().uuid() +}) + +/** + * Get all unapproved requirements that follow from the specified parsed requirement + */ +export default defineEventHandler(async (event) => { + const { solutionId, id } = await validateEventQuery(event, querySchema), + { solution } = await assertSolutionReader(event, solutionId), + em = fork(), + parsedRequirement = await assertReqBelongsToSolution(em, ParsedRequirement, id, solution), + follows = await em.find(Follows, { + right: parsedRequirement, + left: { isSilence: true } + }, { populate: ['left'] }) + + const groupedResult = groupBy( + follows.map(f => f.left) as Requirement[], + ({ req_type }) => req_type + ) + + return groupedResult +}) \ No newline at end of file diff --git a/server/api/parse-requirements/index.get.ts b/server/api/parse-requirement/index.get.ts similarity index 92% rename from server/api/parse-requirements/index.get.ts rename to server/api/parse-requirement/index.get.ts index 6ef697a4..ff8a8a9b 100644 --- a/server/api/parse-requirements/index.get.ts +++ b/server/api/parse-requirement/index.get.ts @@ -1,7 +1,6 @@ import { z } from "zod"; import { fork } from "~/server/data/orm.js" import { ParsedRequirement, ReqType } from "~/server/domain/requirements/index.js"; -import { Belongs } from "~/server/domain/relations"; const querySchema = z.object({ solutionId: z.string().uuid() diff --git a/server/api/parse-requirements/index.post.ts b/server/api/parse-requirement/index.post.ts similarity index 92% rename from server/api/parse-requirements/index.post.ts rename to server/api/parse-requirement/index.post.ts index a66e8619..38e2a6e3 100644 --- a/server/api/parse-requirements/index.post.ts +++ b/server/api/parse-requirement/index.post.ts @@ -73,20 +73,7 @@ export default defineEventHandler(async (event) => { [K in ArrayToUnion['type']]?: ExtractGroupItem[] } - // The server is currently Node 20 which does not support Object.groupBy. - // See: https://github.com/final-hill/cathedral/issues/371 - if (!Object.groupBy) { - Object.groupBy = function (items: Iterable, keySelector: (item: T, index: number) => K): Partial> { - return [...items].reduce((acc, item, index) => { - const key = keySelector(item, index), - group = (acc as any)[key as any] ?? ((acc as any)[key as any] = []) - group.push(item) - return acc - }, {} as Partial>) - } - } - - const groupedResult = Object.groupBy(result, ({ type }) => type) as GroupedResult + const groupedResult = groupBy(result, ({ type }) => type) as GroupedResult const parsedRequirement = em.create(ParsedRequirement, { name: '{LLM Parsed Requirement}', @@ -138,8 +125,7 @@ export default defineEventHandler(async (event) => { lastModified: new Date(), modifiedBy: sessionUser, name: item.name, - description: item.description, - parentComponent: undefined + description: item.description }) em.create(Belongs, { left: environmentComponent, right: solution }); em.create(Follows, { left: environmentComponent, right: parsedRequirement }); @@ -162,8 +148,7 @@ export default defineEventHandler(async (event) => { lastModified: new Date(), modifiedBy: sessionUser, name: item.name, - description: item.description, - parentComponent: undefined + description: item.description }) em.create(Belongs, { left: glossaryTerm, right: solution }); em.create(Follows, { left: glossaryTerm, right: parsedRequirement }); @@ -257,7 +242,6 @@ export default defineEventHandler(async (event) => { influence: item.influence, description: '', category: item.category as StakeholderCategory, - parentComponent: undefined, segmentation: item.segmentation as StakeholderSegmentation }) em.create(Belongs, { left: stakeholder, right: solution }); @@ -269,8 +253,7 @@ export default defineEventHandler(async (event) => { lastModified: new Date(), modifiedBy: sessionUser, name: item.name, - description: item.description, - parentComponent: undefined + description: item.description }) em.create(Belongs, { left: systemComponent, right: solution }); em.create(Follows, { left: systemComponent, right: parsedRequirement }); @@ -312,7 +295,6 @@ export default defineEventHandler(async (event) => { influence: 50, description: '', category: StakeholderCategory.KEY_STAKEHOLDER, - parentComponent: undefined, segmentation: StakeholderSegmentation.VENDOR, }) }) @@ -355,7 +337,6 @@ export default defineEventHandler(async (event) => { influence: 50, description: '', category: StakeholderCategory.KEY_STAKEHOLDER, - parentComponent: undefined, segmentation: StakeholderSegmentation.VENDOR }) }) diff --git a/server/api/persons/[id].delete.ts b/server/api/person/[id].delete.ts similarity index 100% rename from server/api/persons/[id].delete.ts rename to server/api/person/[id].delete.ts diff --git a/server/api/persons/[id].get.ts b/server/api/person/[id].get.ts similarity index 92% rename from server/api/persons/[id].get.ts rename to server/api/person/[id].get.ts index 25232f69..96a45cbd 100644 --- a/server/api/persons/[id].get.ts +++ b/server/api/person/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { Person } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/persons/[id].put.ts b/server/api/person/[id].put.ts similarity index 96% rename from server/api/persons/[id].put.ts rename to server/api/person/[id].put.ts index 112c2643..0a1e8698 100644 --- a/server/api/persons/[id].put.ts +++ b/server/api/person/[id].put.ts @@ -33,5 +33,5 @@ export default defineEventHandler(async (event) => { lastModified: new Date() }) - await em.flush() + await em.persistAndFlush(person) }) \ No newline at end of file diff --git a/server/api/persons/index.get.ts b/server/api/person/index.get.ts similarity index 100% rename from server/api/persons/index.get.ts rename to server/api/person/index.get.ts diff --git a/server/api/persons/index.post.ts b/server/api/person/index.post.ts similarity index 100% rename from server/api/persons/index.post.ts rename to server/api/person/index.post.ts diff --git a/server/api/solutions/[id].delete.ts b/server/api/solution/[id].delete.ts similarity index 100% rename from server/api/solutions/[id].delete.ts rename to server/api/solution/[id].delete.ts diff --git a/server/api/solutions/[id].get.ts b/server/api/solution/[id].get.ts similarity index 100% rename from server/api/solutions/[id].get.ts rename to server/api/solution/[id].get.ts diff --git a/server/api/solutions/[id].put.ts b/server/api/solution/[id].put.ts similarity index 94% rename from server/api/solutions/[id].put.ts rename to server/api/solution/[id].put.ts index 0c9677f3..4c73048c 100644 --- a/server/api/solutions/[id].put.ts +++ b/server/api/solution/[id].put.ts @@ -24,5 +24,5 @@ export default defineEventHandler(async (event) => { description: description ?? solution.description }) - await em.flush() + await em.persistAndFlush(solution) }) \ No newline at end of file diff --git a/server/api/solutions/index.get.ts b/server/api/solution/index.get.ts similarity index 100% rename from server/api/solutions/index.get.ts rename to server/api/solution/index.get.ts diff --git a/server/api/solutions/index.post.ts b/server/api/solution/index.post.ts similarity index 94% rename from server/api/solutions/index.post.ts rename to server/api/solution/index.post.ts index 84bae2cc..b48c2175 100644 --- a/server/api/solutions/index.post.ts +++ b/server/api/solution/index.post.ts @@ -1,4 +1,3 @@ -import { e } from "@vite-pwa/assets-generator/dist/shared/assets-generator.5e51fd40.mjs" import { z } from "zod" import { fork } from "~/server/data/orm.js" import { Solution } from "~/server/domain/requirements/index.js" diff --git a/server/api/stakeholders/[id].delete.ts b/server/api/stakeholder/[id].delete.ts similarity index 100% rename from server/api/stakeholders/[id].delete.ts rename to server/api/stakeholder/[id].delete.ts diff --git a/server/api/stakeholders/[id].get.ts b/server/api/stakeholder/[id].get.ts similarity index 93% rename from server/api/stakeholders/[id].get.ts rename to server/api/stakeholder/[id].get.ts index 96b65420..0690be8f 100644 --- a/server/api/stakeholders/[id].get.ts +++ b/server/api/stakeholder/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { Stakeholder } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/stakeholders/[id].put.ts b/server/api/stakeholder/[id].put.ts similarity index 67% rename from server/api/stakeholders/[id].put.ts rename to server/api/stakeholder/[id].put.ts index 853cc734..67363128 100644 --- a/server/api/stakeholders/[id].put.ts +++ b/server/api/stakeholder/[id].put.ts @@ -1,5 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" +import { Belongs } from "~/server/domain/relations" import { Stakeholder, StakeholderSegmentation, StakeholderCategory } from "~/server/domain/requirements/index.js" const paramSchema = z.object({ @@ -27,11 +28,13 @@ export default defineEventHandler(async (event) => { await validateEventBody(event, bodySchema), { sessionUser, solution } = await assertSolutionContributor(event, solutionId), em = fork(), - stakeholder = await assertReqBelongsToSolution(em, Stakeholder, id, solution) + stakeholder = await assertReqBelongsToSolution(em, Stakeholder, id, solution), + parentComponent = parentComponentId ? await assertReqBelongsToSolution(em, Stakeholder, parentComponentId, solution) : undefined - - if (parentComponentId) - stakeholder.parentComponent = await assertReqBelongsToSolution(em, Stakeholder, parentComponentId, solution) + const existingParentComponent = await em.findOne(Belongs, { + left: stakeholder, + right: parentComponent + }) stakeholder.assign({ name: name ?? stakeholder.name, @@ -45,5 +48,16 @@ export default defineEventHandler(async (event) => { lastModified: new Date() }) - await em.flush() + if (!existingParentComponent && parentComponent) { + em.create(Belongs, { left: stakeholder, right: parentComponent }) + } else if (existingParentComponent && !parentComponent) { + em.remove(existingParentComponent) + } else if (existingParentComponent && parentComponent) { + em.remove(existingParentComponent) + em.create(Belongs, { left: stakeholder, right: parentComponent }) + } else { + // Do nothing + } + + await em.persistAndFlush(stakeholder) }) \ No newline at end of file diff --git a/server/api/stakeholders/index.get.ts b/server/api/stakeholder/index.get.ts similarity index 100% rename from server/api/stakeholders/index.get.ts rename to server/api/stakeholder/index.get.ts diff --git a/server/api/stakeholders/index.post.ts b/server/api/stakeholder/index.post.ts similarity index 88% rename from server/api/stakeholders/index.post.ts rename to server/api/stakeholder/index.post.ts index 668d6291..fec1dfd1 100644 --- a/server/api/stakeholders/index.post.ts +++ b/server/api/stakeholder/index.post.ts @@ -19,7 +19,7 @@ const bodySchema = z.object({ * Creates a new stakeholder and returns its id */ export default defineEventHandler(async (event) => { - const { availability, category, influence, name, segmentation, description, parentComponentId, solutionId, isSilence } + const { availability, category, influence, name, segmentation, description, solutionId, isSilence, parentComponentId } = await validateEventBody(event, bodySchema), { solution, sessionUser } = await assertSolutionContributor(event, solutionId), em = fork() @@ -33,12 +33,14 @@ export default defineEventHandler(async (event) => { category, lastModified: new Date(), modifiedBy: sessionUser, - isSilence, - parentComponent: parentComponentId ? em.getReference(Stakeholder, parentComponentId) : undefined + isSilence }) em.create(Belongs, { left: newStakeholder, right: solution }) + if (parentComponentId) + em.create(Belongs, { left: newStakeholder, right: parentComponentId }) + await em.flush() return newStakeholder.id diff --git a/server/api/system-components/[id].delete.ts b/server/api/system-component/[id].delete.ts similarity index 100% rename from server/api/system-components/[id].delete.ts rename to server/api/system-component/[id].delete.ts diff --git a/server/api/system-components/[id].get.ts b/server/api/system-component/[id].get.ts similarity index 93% rename from server/api/system-components/[id].get.ts rename to server/api/system-component/[id].get.ts index 16736474..c8d6f829 100644 --- a/server/api/system-components/[id].get.ts +++ b/server/api/system-component/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { SystemComponent } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/system-components/[id].put.ts b/server/api/system-component/[id].put.ts similarity index 50% rename from server/api/system-components/[id].put.ts rename to server/api/system-component/[id].put.ts index 0f7e6a7e..988d5a87 100644 --- a/server/api/system-components/[id].put.ts +++ b/server/api/system-component/[id].put.ts @@ -1,5 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" +import { Belongs } from "~/server/domain/relations" import { SystemComponent } from "~/server/domain/requirements/index.js" const paramSchema = z.object({ @@ -10,7 +11,7 @@ const bodySchema = z.object({ solutionId: z.string().uuid(), name: z.string().optional(), description: z.string().optional(), - parentComponentId: z.string().uuid().optional(), + parentComponent: z.string().uuid().optional(), isSilence: z.boolean().optional() }) @@ -19,13 +20,16 @@ const bodySchema = z.object({ */ export default defineEventHandler(async (event) => { const { id } = await validateEventParams(event, paramSchema), - { name, description, parentComponentId, solutionId, isSilence } = await validateEventBody(event, bodySchema), + { name, description, parentComponent, solutionId, isSilence } = await validateEventBody(event, bodySchema), { sessionUser, solution } = await assertSolutionContributor(event, solutionId), em = fork(), - systemComponent = await assertReqBelongsToSolution(em, SystemComponent, id, solution) + systemComponent = await assertReqBelongsToSolution(em, SystemComponent, id, solution), + pComponent = parentComponent ? await assertReqBelongsToSolution(em, SystemComponent, parentComponent, solution) : undefined - if (parentComponentId) - systemComponent.parentComponent = await assertReqBelongsToSolution(em, SystemComponent, parentComponentId, solution) + const existingParentComponent = await em.findOne(Belongs, { + left: systemComponent, + right: pComponent + }) systemComponent.assign({ name: name ?? systemComponent.name, @@ -35,5 +39,16 @@ export default defineEventHandler(async (event) => { lastModified: new Date() }) - await em.flush() + if (!existingParentComponent && pComponent) { + em.create(Belongs, { left: systemComponent, right: pComponent }) + } else if (existingParentComponent && !pComponent) { + em.remove(existingParentComponent) + } else if (existingParentComponent && pComponent) { + em.remove(existingParentComponent) + em.create(Belongs, { left: systemComponent, right: pComponent }) + } else { + // Do nothing + } + + await em.persistAndFlush(systemComponent) }) \ No newline at end of file diff --git a/server/api/system-component/index.get.ts b/server/api/system-component/index.get.ts new file mode 100644 index 00000000..802c0dfb --- /dev/null +++ b/server/api/system-component/index.get.ts @@ -0,0 +1,42 @@ +import { z } from "zod" +import { fork } from "~/server/data/orm.js" +import { Belongs } from "~/server/domain/relations" +import { ReqType, SystemComponent } from "~/server/domain/requirements/index.js" +import { type ReqRelModel } from "~/server/domain/types" + +const querySchema = z.object({ + solutionId: z.string().uuid(), + name: z.string().optional(), + description: z.string().optional(), + parentComponent: z.string().uuid().optional(), + isSilence: z.boolean().optional().default(false) +}) + +/** + * Returns all system-components that match the query parameters + */ +export default defineEventHandler(async (event) => { + const query = await validateEventQuery(event, querySchema), + em = fork() + + await assertSolutionReader(event, query.solutionId) + + const sysComponents = await findAllSolutionRequirements(ReqType.SYSTEM_COMPONENT, em, query), + parentComponents = await em.find(Belongs, { + left: sysComponents, + right: { + req_type: ReqType.SYSTEM_COMPONENT, + ...(query.parentComponent ? { id: query.parentComponent } : {}) + } + }, { populate: ['right'] }), + joinedComponents = sysComponents.map(sysComp => { + const parent = parentComponents.find(pc => pc.left.id === sysComp.id) + return { + ...sysComp, + solutionId: query.solutionId, + parentComponent: parent?.right + } + }) as unknown as ReqRelModel[] + + return joinedComponents +}) \ No newline at end of file diff --git a/server/api/system-components/index.post.ts b/server/api/system-component/index.post.ts similarity index 75% rename from server/api/system-components/index.post.ts rename to server/api/system-component/index.post.ts index a0ea17ee..61a26ad4 100644 --- a/server/api/system-components/index.post.ts +++ b/server/api/system-component/index.post.ts @@ -7,7 +7,7 @@ const bodySchema = z.object({ solutionId: z.string().uuid(), name: z.string().default("{Untitled System Component}"), description: z.string().default(""), - parentComponentId: z.string().uuid().optional(), + parentComponent: z.string().uuid().optional(), isSilence: z.boolean().default(false) }) @@ -15,7 +15,7 @@ const bodySchema = z.object({ * Creates a new system-component and returns its id */ export default defineEventHandler(async (event) => { - const { name, description, parentComponentId, solutionId, isSilence } = await validateEventBody(event, bodySchema), + const { name, description, parentComponent, solutionId, isSilence } = await validateEventBody(event, bodySchema), { solution, sessionUser } = await assertSolutionContributor(event, solutionId), em = fork() @@ -24,12 +24,14 @@ export default defineEventHandler(async (event) => { description, lastModified: new Date(), modifiedBy: sessionUser, - isSilence, - parentComponent: parentComponentId ? em.getReference(SystemComponent, parentComponentId) : undefined + isSilence }) em.create(Belongs, { left: newSystemComponent, right: solution }) + if (parentComponent) + em.create(Belongs, { left: newSystemComponent, right: parentComponent }) + await em.flush() return newSystemComponent.id diff --git a/server/api/system-components/index.get.ts b/server/api/system-components/index.get.ts deleted file mode 100644 index 337d99d9..00000000 --- a/server/api/system-components/index.get.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { z } from "zod" -import { fork } from "~/server/data/orm.js" -import { ReqType, SystemComponent } from "~/server/domain/requirements/index.js" - -const querySchema = z.object({ - solutionId: z.string().uuid(), - name: z.string().optional(), - description: z.string().optional(), - parentComponentId: z.string().uuid().optional(), - isSilence: z.boolean().optional().default(false) -}) - -/** - * Returns all system-components that match the query parameters - */ -export default defineEventHandler(async (event) => { - const query = await validateEventQuery(event, querySchema), - em = fork() - - await assertSolutionReader(event, query.solutionId) - - return await findAllSolutionRequirements(ReqType.SYSTEM_COMPONENT, em, query) -}) \ No newline at end of file diff --git a/server/api/use-cases/[id].delete.ts b/server/api/use-case/[id].delete.ts similarity index 100% rename from server/api/use-cases/[id].delete.ts rename to server/api/use-case/[id].delete.ts diff --git a/server/api/use-cases/[id].get.ts b/server/api/use-case/[id].get.ts similarity index 92% rename from server/api/use-cases/[id].get.ts rename to server/api/use-case/[id].get.ts index a5b696cb..ac149a55 100644 --- a/server/api/use-cases/[id].get.ts +++ b/server/api/use-case/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { UseCase } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/use-cases/[id].put.ts b/server/api/use-case/[id].put.ts similarity index 82% rename from server/api/use-cases/[id].put.ts rename to server/api/use-case/[id].put.ts index 807e8af3..7c654618 100644 --- a/server/api/use-cases/[id].put.ts +++ b/server/api/use-case/[id].put.ts @@ -10,15 +10,15 @@ const bodySchema = z.object({ solutionId: z.string().uuid(), name: z.string().optional(), description: z.string().optional(), - primaryActorId: z.string().uuid().optional(), + primaryActor: z.string().uuid().optional(), priority: z.nativeEnum(MoscowPriority).optional(), scope: z.string().optional(), level: z.string().optional(), goalInContext: z.string().optional(), - preconditionId: z.string().uuid().optional(), + precondition: z.string().uuid().optional(), triggerId: z.string().uuid().optional(), mainSuccessScenario: z.string().optional(), - successGuaranteeId: z.string().uuid().optional(), + successGuarantee: z.string().uuid().optional(), extensions: z.string().optional(), isSilence: z.boolean().optional() }) @@ -33,12 +33,12 @@ export default defineEventHandler(async (event) => { em = fork(), useCase = await assertReqBelongsToSolution(em, UseCase, id, solution) - if (body.primaryActorId) - useCase.primaryActor = await assertReqBelongsToSolution(em, Stakeholder, body.primaryActorId, solution) - if (body.preconditionId) - useCase.precondition = await assertReqBelongsToSolution(em, Assumption, body.preconditionId, solution) - if (body.successGuaranteeId) - useCase.successGuarantee = await assertReqBelongsToSolution(em, Effect, body.successGuaranteeId, solution) + if (body.primaryActor) + useCase.primaryActor = await assertReqBelongsToSolution(em, Stakeholder, body.primaryActor, solution) + if (body.precondition) + useCase.precondition = await assertReqBelongsToSolution(em, Assumption, body.precondition, solution) + if (body.successGuarantee) + useCase.successGuarantee = await assertReqBelongsToSolution(em, Effect, body.successGuarantee, solution) useCase.assign({ name: body.name ?? useCase.name, @@ -55,5 +55,5 @@ export default defineEventHandler(async (event) => { lastModified: new Date() }) - await em.flush() + await em.persistAndFlush(useCase) }) \ No newline at end of file diff --git a/server/api/use-cases/index.get.ts b/server/api/use-case/index.get.ts similarity index 82% rename from server/api/use-cases/index.get.ts rename to server/api/use-case/index.get.ts index 3aafadcc..adde2173 100644 --- a/server/api/use-cases/index.get.ts +++ b/server/api/use-case/index.get.ts @@ -7,15 +7,15 @@ const querySchema = z.object({ solutionId: z.string().uuid(), name: z.string().optional(), description: z.string().optional(), - primaryActorId: z.string().uuid().optional(), + primaryActor: z.string().uuid().optional(), priority: z.nativeEnum(MoscowPriority).optional(), scope: z.string().optional(), level: z.string().optional(), goalInContext: z.string().optional(), - preconditionId: z.string().uuid().optional(), + precondition: z.string().uuid().optional(), triggerId: z.literal(emptyUuid).optional(), mainSuccessScenario: z.string().optional(), - successGuaranteeId: z.string().uuid().optional(), + successGuarantee: z.string().uuid().optional(), extensions: z.string().optional(), isSilence: z.boolean().optional().default(false) }) @@ -29,5 +29,5 @@ export default defineEventHandler(async (event) => { await assertSolutionReader(event, query.solutionId) - return await findAllSolutionRequirements(ReqType.USE_CASE, em, query) + return await findAllSolutionRequirements(ReqType.USE_CASE, em, query, ['primaryActor', 'precondition', 'successGuarantee']) }) \ No newline at end of file diff --git a/server/api/use-cases/index.post.ts b/server/api/use-case/index.post.ts similarity index 77% rename from server/api/use-cases/index.post.ts rename to server/api/use-case/index.post.ts index 6f8b3ee2..a1056d63 100644 --- a/server/api/use-cases/index.post.ts +++ b/server/api/use-case/index.post.ts @@ -8,15 +8,15 @@ const bodySchema = z.object({ solutionId: z.string().uuid(), name: z.string(), description: z.string(), - primaryActorId: z.string().uuid(), + primaryActor: z.string().uuid(), priority: z.nativeEnum(MoscowPriority), scope: z.string(), level: z.string(), goalInContext: z.string(), - preconditionId: z.string().uuid(), + precondition: z.string().uuid(), triggerId: z.literal(emptyUuid), mainSuccessScenario: z.string(), - successGuaranteeId: z.string().uuid(), + successGuarantee: z.string().uuid(), extensions: z.string(), isSilence: z.boolean().default(false) }) @@ -32,15 +32,15 @@ export default defineEventHandler(async (event) => { const newUseCase = new UseCase({ name: body.name, description: body.description, - primaryActor: body.primaryActorId ? em.getReference(Stakeholder, body.primaryActorId) : undefined, + primaryActor: body.primaryActor ? em.getReference(Stakeholder, body.primaryActor) : undefined, priority: body.priority, scope: body.scope, level: body.level, goalInContext: body.goalInContext, - precondition: body.preconditionId ? em.getReference(Assumption, body.preconditionId) : undefined, + precondition: body.precondition ? em.getReference(Assumption, body.precondition) : undefined, triggerId: body.triggerId, mainSuccessScenario: body.mainSuccessScenario, - successGuarantee: body.successGuaranteeId ? em.getReference(Effect, body.successGuaranteeId) : undefined, + successGuarantee: body.successGuarantee ? em.getReference(Effect, body.successGuarantee) : undefined, extensions: body.extensions, lastModified: new Date(), modifiedBy: sessionUser, diff --git a/server/api/user-stories/[id].delete.ts b/server/api/user-story/[id].delete.ts similarity index 100% rename from server/api/user-stories/[id].delete.ts rename to server/api/user-story/[id].delete.ts diff --git a/server/api/user-stories/[id].get.ts b/server/api/user-story/[id].get.ts similarity index 92% rename from server/api/user-stories/[id].get.ts rename to server/api/user-story/[id].get.ts index e2651cd7..300cb533 100644 --- a/server/api/user-stories/[id].get.ts +++ b/server/api/user-story/[id].get.ts @@ -1,7 +1,6 @@ import { z } from "zod" import { fork } from "~/server/data/orm.js" import { UserStory } from "~/server/domain/requirements/index.js" -import { Belongs } from "~/server/domain/relations" const paramSchema = z.object({ id: z.string().uuid() diff --git a/server/api/user-stories/[id].put.ts b/server/api/user-story/[id].put.ts similarity index 77% rename from server/api/user-stories/[id].put.ts rename to server/api/user-story/[id].put.ts index 7d6f6be2..092fa95a 100644 --- a/server/api/user-stories/[id].put.ts +++ b/server/api/user-story/[id].put.ts @@ -10,10 +10,10 @@ const bodySchema = z.object({ solutionId: z.string().uuid(), name: z.string().optional(), description: z.string().optional(), - primaryActorId: z.string().uuid().optional(), + primaryActor: z.string().uuid().optional(), priority: z.nativeEnum(MoscowPriority).optional(), - outcomeId: z.string().uuid().optional(), - functionalBehaviorId: z.string().uuid().optional(), + outcome: z.string().uuid().optional(), + functionalBehavior: z.string().uuid().optional(), isSilence: z.boolean().optional() }) @@ -27,12 +27,12 @@ export default defineEventHandler(async (event) => { em = fork(), userStory = await assertReqBelongsToSolution(em, UserStory, id, solution) - if (body.primaryActorId) - userStory.primaryActor = await assertReqBelongsToSolution(em, Stakeholder, body.primaryActorId, solution) - if (body.outcomeId) - userStory.outcome = await assertReqBelongsToSolution(em, Outcome, body.outcomeId, solution) - if (body.functionalBehaviorId) - userStory.functionalBehavior = await assertReqBelongsToSolution(em, FunctionalBehavior, body.functionalBehaviorId, solution) + if (body.primaryActor) + userStory.primaryActor = await assertReqBelongsToSolution(em, Stakeholder, body.primaryActor, solution) + if (body.outcome) + userStory.outcome = await assertReqBelongsToSolution(em, Outcome, body.outcome, solution) + if (body.functionalBehavior) + userStory.functionalBehavior = await assertReqBelongsToSolution(em, FunctionalBehavior, body.functionalBehavior, solution) userStory.assign({ name: body.name ?? userStory.name, @@ -43,5 +43,5 @@ export default defineEventHandler(async (event) => { lastModified: new Date() }) - await em.flush() + await em.persistAndFlush(userStory) }) \ No newline at end of file diff --git a/server/api/user-stories/index.get.ts b/server/api/user-story/index.get.ts similarity index 92% rename from server/api/user-stories/index.get.ts rename to server/api/user-story/index.get.ts index ed36fa5f..e62481cd 100644 --- a/server/api/user-stories/index.get.ts +++ b/server/api/user-story/index.get.ts @@ -22,5 +22,5 @@ export default defineEventHandler(async (event) => { await assertSolutionReader(event, query.solutionId) - return await findAllSolutionRequirements(ReqType.USER_STORY, em, query) + return await findAllSolutionRequirements(ReqType.USER_STORY, em, query, ['primaryActor', 'outcome', 'functionalBehavior']) }) \ No newline at end of file diff --git a/server/api/user-stories/index.post.ts b/server/api/user-story/index.post.ts similarity index 71% rename from server/api/user-stories/index.post.ts rename to server/api/user-story/index.post.ts index 361b8970..73ad4dd8 100644 --- a/server/api/user-stories/index.post.ts +++ b/server/api/user-story/index.post.ts @@ -7,10 +7,10 @@ const bodySchema = z.object({ solutionId: z.string().uuid(), name: z.string().default("{Untitled User Story}"), description: z.string().default(""), - primaryActorId: z.string().uuid().optional(), + primaryActor: z.string().uuid().optional(), priority: z.nativeEnum(MoscowPriority).default(MoscowPriority.MUST), - outcomeId: z.string().uuid().optional(), - functionalBehaviorId: z.string().uuid().optional(), + outcome: z.string().uuid().optional(), + functionalBehavior: z.string().uuid().optional(), isSilence: z.boolean().default(false) }) @@ -23,11 +23,11 @@ export default defineEventHandler(async (event) => { em = fork() const newUserStory = new UserStory({ - functionalBehavior: body.functionalBehaviorId ? em.getReference(FunctionalBehavior, body.functionalBehaviorId) : undefined, - outcome: body.outcomeId ? em.getReference(Outcome, body.outcomeId) : undefined, + functionalBehavior: body.functionalBehavior ? em.getReference(FunctionalBehavior, body.functionalBehavior) : undefined, + outcome: body.outcome ? em.getReference(Outcome, body.outcome) : undefined, name: body.name, description: body.description, - primaryActor: body.primaryActorId ? em.getReference(Stakeholder, body.primaryActorId) : undefined, + primaryActor: body.primaryActor ? em.getReference(Stakeholder, body.primaryActor) : undefined, priority: body.priority, lastModified: new Date(), modifiedBy: sessionUser, diff --git a/server/domain/application/AuditLog.ts b/server/domain/application/AuditLog.ts index 8d77d310..cb090a6d 100644 --- a/server/domain/application/AuditLog.ts +++ b/server/domain/application/AuditLog.ts @@ -7,19 +7,21 @@ import { type Properties } from '../types/index.js'; */ @Entity() export class AuditLog extends BaseEntity { - constructor(props: Properties>) { + constructor(props: Properties> & { id?: string, createdAt?: Date }) { super() this.type = props.type; this.entity = props.entity; this.entityId = props.entityId; this.entityName = props.entityName; + this.createdAt = props.createdAt || new Date(); + this.id = props.id || uuidv7(); } /** * The unique identifier of the AuditLog */ @Property({ type: 'uuid', primary: true }) - id: string = uuidv7(); + id: string /** * The unique identifier of the entity that was changed @@ -49,5 +51,5 @@ export class AuditLog extends BaseEntity { * The date and time when the AuditLog was created */ @Property({ type: 'datetime' }) - createdAt: Date = new Date(); + createdAt: Date } \ No newline at end of file diff --git a/server/domain/relations/Characterizes.ts b/server/domain/relations/Characterizes.ts index bfaa8748..bf0aad0f 100644 --- a/server/domain/relations/Characterizes.ts +++ b/server/domain/relations/Characterizes.ts @@ -1,4 +1,4 @@ -import { Entity, ManyToOne } from "@mikro-orm/core"; +import { Entity } from "@mikro-orm/core"; import { RequirementRelation } from "./RequirementRelation.js"; import { MetaRequirement } from "../requirements/MetaRequirement.js"; import { type Properties } from "../types/index.js"; diff --git a/server/domain/relations/RequirementRelation.ts b/server/domain/relations/RequirementRelation.ts index e6b2b66c..18de2c3a 100644 --- a/server/domain/relations/RequirementRelation.ts +++ b/server/domain/relations/RequirementRelation.ts @@ -1,5 +1,5 @@ import { v7 as uuidv7 } from 'uuid'; -import { BaseEntity, Entity, ManyToOne, Property } from "@mikro-orm/core"; +import { BaseEntity, Cascade, Entity, ManyToOne, Property } from "@mikro-orm/core"; import { Requirement } from '../requirements/Requirement.js' import { type Properties } from '../types/index.js'; @@ -21,9 +21,9 @@ export abstract class RequirementRelation extends BaseEntity { @Property({ type: 'uuid', primary: true }) id: string; - @ManyToOne({ entity: () => Requirement }) + @ManyToOne({ entity: () => Requirement, cascade: [Cascade.REMOVE] }) left: Requirement - @ManyToOne({ entity: () => Requirement }) + @ManyToOne({ entity: () => Requirement, cascade: [Cascade.REMOVE] }) right: Requirement } \ No newline at end of file diff --git a/server/domain/relations/index.ts b/server/domain/relations/index.ts index c082cea2..d8c1207a 100644 --- a/server/domain/relations/index.ts +++ b/server/domain/relations/index.ts @@ -1,4 +1,3 @@ - export * from './RequirementRelation.js' export * from './Belongs.js' export * from './Characterizes.js' diff --git a/server/domain/requirements/EnvironmentComponent.ts b/server/domain/requirements/EnvironmentComponent.ts index 25c905cc..f6ea5547 100644 --- a/server/domain/requirements/EnvironmentComponent.ts +++ b/server/domain/requirements/EnvironmentComponent.ts @@ -8,15 +8,8 @@ import { ReqType } from "./ReqType.js"; */ @Entity({ discriminatorValue: ReqType.ENVIRONMENT_COMPONENT }) export class EnvironmentComponent extends Component { - constructor({ parentComponent, ...rest }: Properties>) { - super(rest); - this.parentComponent = parentComponent; + constructor(props: Properties>) { + super(props); this.req_type = ReqType.ENVIRONMENT_COMPONENT; } - - /** - * The parent component of the current environment component if any - */ - @ManyToOne({ entity: () => EnvironmentComponent }) - parentComponent?: EnvironmentComponent; } \ No newline at end of file diff --git a/server/domain/requirements/GlossaryTerm.ts b/server/domain/requirements/GlossaryTerm.ts index 1be2b726..f0ec86a6 100644 --- a/server/domain/requirements/GlossaryTerm.ts +++ b/server/domain/requirements/GlossaryTerm.ts @@ -8,15 +8,8 @@ import { ReqType } from "./ReqType.js"; */ @Entity({ discriminatorValue: ReqType.GLOSSARY_TERM }) export class GlossaryTerm extends Component { - constructor({ parentComponent, ...rest }: Properties>) { - super(rest); - this.parentComponent = parentComponent; + constructor(props: Properties>) { + super(props); this.req_type = ReqType.GLOSSARY_TERM; } - - /** - * The parent term of the glossary term, if any. - */ - @ManyToOne({ entity: () => GlossaryTerm }) - parentComponent?: GlossaryTerm; } \ No newline at end of file diff --git a/server/domain/requirements/ParsedRequirement.ts b/server/domain/requirements/ParsedRequirement.ts index e48e1635..4bc28d1d 100644 --- a/server/domain/requirements/ParsedRequirement.ts +++ b/server/domain/requirements/ParsedRequirement.ts @@ -1,14 +1,8 @@ import { Entity } from '@mikro-orm/core'; -import type { Properties } from '../types/index.js'; +import { type Properties } from '../types/index.js'; import { MetaRequirement } from './MetaRequirement.js'; import { ReqType } from './ReqType.js'; -export type ParsedReqColType = 'assumptions' | 'constraints' | - 'effects' | 'environmentComponents' | 'functionalBehaviors' | - 'glossaryTerms' | 'invariants' | 'justifications' | 'limits' | - 'nonFunctionalBehaviors' | 'obstacles' | 'outcomes' | 'persons' | - 'stakeholders' | 'systemComponents' | 'useCases' | 'userStories' - /** * A requirement that has been parsed from natural language text */ diff --git a/server/domain/requirements/ReqType.ts b/server/domain/requirements/ReqType.ts index aeb596ef..9b5276ec 100644 --- a/server/domain/requirements/ReqType.ts +++ b/server/domain/requirements/ReqType.ts @@ -1,3 +1,7 @@ +// Note: it is assumed that api endpoints are snakeCaseToSlug(requirement.req_type) +// see the workbox view for a couple example usages +// TODO: update the architecture to encode this assumption somehow. +// Nuxt may limit the ability to do this, but it is worth investigating. export enum ReqType { ACTOR = 'actor', ASSUMPTION = 'assumption', diff --git a/server/domain/requirements/Stakeholder.ts b/server/domain/requirements/Stakeholder.ts index 3bd8f92b..ee7f8974 100644 --- a/server/domain/requirements/Stakeholder.ts +++ b/server/domain/requirements/Stakeholder.ts @@ -17,15 +17,8 @@ export class Stakeholder extends Component { this.availability = props.availability; this.segmentation = props.segmentation; this.category = props.category; - this.parentComponent = props.parentComponent; } - /** - * The parent component of the stakeholder, if any. - */ - @ManyToOne({ entity: () => Stakeholder }) - parentComponent?: Stakeholder; - /** * The segmentation of the stakeholder. */ diff --git a/server/domain/requirements/SystemComponent.ts b/server/domain/requirements/SystemComponent.ts index 371f9980..c0b68a82 100644 --- a/server/domain/requirements/SystemComponent.ts +++ b/server/domain/requirements/SystemComponent.ts @@ -11,12 +11,5 @@ export class SystemComponent extends Component { constructor(props: Properties>) { super(props); this.req_type = ReqType.SYSTEM_COMPONENT; - this.parentComponent = props.parentComponent; } - - /** - * Parent component of the current system component - */ - @ManyToOne({ entity: () => SystemComponent }) - parentComponent?: SystemComponent; } \ No newline at end of file diff --git a/server/domain/types/index.ts b/server/domain/types/index.ts index 656ec634..4a4a9425 100644 --- a/server/domain/types/index.ts +++ b/server/domain/types/index.ts @@ -1,6 +1,16 @@ +import { Requirement } from "../requirements/Requirement.js"; + /** * A type that represents all the members of a type T that are not functions */ export type Properties = Pick any ? never : K -}[keyof T]>; \ No newline at end of file +}[keyof T]>; + +/** + * Represents a requirement model with relations + */ +export type ReqRelModel = R & { + parentComponent?: string, + solutionId: string +} \ No newline at end of file diff --git a/server/utils/findAllSolutionRequirements.ts b/server/utils/findAllSolutionRequirements.ts index 7ef72a9c..b681784a 100644 --- a/server/utils/findAllSolutionRequirements.ts +++ b/server/utils/findAllSolutionRequirements.ts @@ -2,24 +2,41 @@ import { PostgreSqlDriver, SqlEntityManager } from "@mikro-orm/postgresql" import { Requirement } from "../domain/requirements/Requirement.js" import { Belongs } from "../domain/relations/index.js" import { ReqType } from "../domain/requirements/ReqType.js" +import { type ReqRelModel } from "../domain/types/index.js" +/** + * + * @param req_type - The type of requirement to find + * @param em - The entity manager + * @param query - The query parameters + * @param [populate] - The reference fields to populate + * @returns + */ export default async function findAllSolutionRequirements( req_type: ReqType, em: SqlEntityManager, - query: Record & { solutionId: string } -): Promise { - const results = await em.find(Belongs, { - left: { - req_type, - ...Object.entries(query) - .filter(([key, value]) => value !== undefined && key !== "solutionId") - .reduce((acc, [key, value]) => ({ - ...acc, - [key.endsWith("Id") ? key.replace(/Id$/, "") : key]: value - }), {}) - }, + query: Record & { solutionId: string, parentComponent?: string }, + populate: string[] = [] +): Promise[]> { + const q = Object.entries(query) + .filter(([key, value]) => + value !== undefined && !['solutionId'].includes(key) + ).reduce((acc, [key, value]) => ({ + ...acc, + [key.endsWith("Id") ? key.replace(/Id$/, "") : key]: value + }), {}) + + const solutionItems = await em.find(Belongs, { + left: { req_type, ...q }, right: query.solutionId - }, { populate: ['left'] }) + }, { + populate: [ + 'left', + ...populate.map(prop => `left.${prop}`) as any[] + ] + }) - return results.map(result => result.left as unknown as R) + return solutionItems.map(result => + Object.assign(result.left, { solutionId: query.solutionId }) as unknown as ReqRelModel + ) } \ No newline at end of file diff --git a/server/utils/groupBy.ts b/server/utils/groupBy.ts new file mode 100644 index 00000000..d44def06 --- /dev/null +++ b/server/utils/groupBy.ts @@ -0,0 +1,10 @@ +// The server is currently Node 20 which does not support Object.groupBy. +// See: https://github.com/final-hill/cathedral/issues/371 +export const groupBy = function (items: Iterable, keySelector: (item: T, index: number) => K): Partial> { + return [...items].reduce((acc, item, index) => { + const key = keySelector(item, index), + group = (acc as any)[key as any] ?? ((acc as any)[key as any] = []) + group.push(item) + return acc + }, {} as Partial>) +} \ No newline at end of file diff --git a/utils/snakeCaseToSlug.ts b/utils/snakeCaseToSlug.ts new file mode 100644 index 00000000..91d1c4ea --- /dev/null +++ b/utils/snakeCaseToSlug.ts @@ -0,0 +1,7 @@ +/** + * Convert snake_case to slug + * @example + * snakeCaseToSlug('snake_case_string'); // 'snake-case-string' + */ +export default (str: string) => + str.replace(/_/g, '-'); \ No newline at end of file diff --git a/utils/snakeCaseToTitle.ts b/utils/snakeCaseToTitle.ts new file mode 100644 index 00000000..b3d9ebd5 --- /dev/null +++ b/utils/snakeCaseToTitle.ts @@ -0,0 +1,8 @@ +/** + * Convert snake_case to Title Case + * @example + * snakeCaseToTitle('snake_case_string'); // 'Snake Case String' + */ +export default (str: string) => + str.replace(/_/g, ' ') + .replace(/\b\w/g, char => char.toUpperCase()); \ No newline at end of file