From 40158f710f0a0be9b1f1548d456d7ae007903f1a Mon Sep 17 00:00:00 2001 From: l Date: Tue, 30 Apr 2024 13:17:29 -0400 Subject: [PATCH 1/4] Add cypress tests and test objects --- cypress/e2e/03_features.cy.ts | 148 +++++++++++++++++++++++++++++ cypress/support/objects/objects.ts | 66 +++++++------ 2 files changed, 186 insertions(+), 28 deletions(-) create mode 100644 cypress/e2e/03_features.cy.ts diff --git a/cypress/e2e/03_features.cy.ts b/cypress/e2e/03_features.cy.ts new file mode 100644 index 000000000..6e3713067 --- /dev/null +++ b/cypress/e2e/03_features.cy.ts @@ -0,0 +1,148 @@ +import { User, HostName, Workspaces, Repositories, Features } from '../support/objects/objects'; + + + +describe('Create Features for Workspace', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 2; i++) { + cy.request({ + method: 'POST', + url: `${HostName}/features`, + headers: { 'x-jwt': `${value}` }, + body: Features[i] + }).its('body').should('have.property', 'name', Features[i].name.trim()) + .its('body').should('have.property', 'brief', Features[i].brief.trim()) + .its('body').should('have.property', 'requirements', Features[i].requirements.trim()) + .its('body').should('have.property', 'architecture', Features[i].architecture.trim()) + } + }) + }) +}) + +describe('Modify name for Feature', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 2; i++) { + cy.request({ + method: 'POST', + url: `${HostName}/features`, + headers: { 'x-jwt': `${value}` }, + body: { + uuid: Features[i].uuid, + name: Features[i].name + "_addtext" + } + }).its('body').should('have.property', 'name', Features[i].name.trim() + "_addtext") + .its('body').should('have.property', 'brief', Features[i].brief.trim()) + .its('body').should('have.property', 'requirements', Features[i].requirements.trim()) + .its('body').should('have.property', 'architecture', Features[i].architecture.trim()) + } + }) + }) +}) + +describe('Modify brief for Feature', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 2; i++) { + cy.request({ + method: 'POST', + url: `${HostName}/features`, + headers: { 'x-jwt': `${value}` }, + body: { + uuid: Features[i].uuid, + brief: Features[i].brief + "_addtext" + } + }).its('body').should('have.property', 'name', Features[i].name.trim()) + .its('body').should('have.property', 'brief', Features[i].brief.trim() + "_addtext") + .its('body').should('have.property', 'requirements', Features[i].requirements.trim()) + .its('body').should('have.property', 'architecture', Features[i].architecture.trim()) + } + }) + }) +}) + +describe('Modify requirements for Feature', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 2; i++) { + cy.request({ + method: 'POST', + url: `${HostName}/features`, + headers: { 'x-jwt': `${value}` }, + body: { + uuid: Features[i].uuid, + requirements: Features[i].requirements + "_addtext" + } + }).its('body').should('have.property', 'name', Features[i].name.trim()) + .its('body').should('have.property', 'brief', Features[i].brief.trim()) + .its('body').should('have.property', 'requirements', Features[i].requirements.trim() + "_addtext") + .its('body').should('have.property', 'architecture', Features[i].architecture.trim()) + } + }) + }) +}) + +describe('Modify architecture for Feature', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 2; i++) { + cy.request({ + method: 'POST', + url: `${HostName}/features`, + headers: { 'x-jwt': `${value}` }, + body: { + uuid: Features[i].uuid, + architecture: Features[i].architecture + "_addtext" + } + }).its('body').should('have.property', 'name', Features[i].name.trim()) + .its('body').should('have.property', 'brief', Features[i].brief.trim()) + .its('body').should('have.property', 'requirements', Features[i].requirements.trim()) + .its('body').should('have.property', 'architecture', Features[i].architecture.trim() + "_addtext") + } + }) + }) +}) + + +describe('Get Features for Workspace', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + cy.request({ + method: 'GET', + url: `${HostName}/features/forworkspace/` + Features[0].workspace_uuid, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(200) + for(let i = 0; i <= 2; i++) { + expect(resp.body[i]).to.have.property('name', Features[i].name.trim() + "_addtext") + expect(resp.body[i]).to.have.property('brief', Features[i].brief.trim() + "_addtext") + expect(resp.body[i]).to.have.property('requirements', Features[i].requirements.trim() + "_addtext") + expect(resp.body[i]).to.have.property('architecture', Features[i].architecture.trim() + "_addtext") + } + }) + }) + }) +}) + +describe('Get Feature by uuid', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 2; i++) { + cy.request({ + method: 'GET', + url: `${HostName}/features/` + Features[i].uuid, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(200) + expect(resp.body).to.have.property('name', Features[i].name.trim() + "_addtext") + expect(resp.body).to.have.property('brief', Features[i].brief.trim() + "_addtext") + expect(resp.body).to.have.property('requirements', Features[i].requirements.trim() + "_addtext") + expect(resp.body).to.have.property('architecture', Features[i].architecture.trim() + "_addtext") + }) + } + }) + }) +}) diff --git a/cypress/support/objects/objects.ts b/cypress/support/objects/objects.ts index b62af74d7..c1cfa4af0 100644 --- a/cypress/support/objects/objects.ts +++ b/cypress/support/objects/objects.ts @@ -57,34 +57,42 @@ export const Workspaces = [ export const Repositories = [ { - name: 'frontend', - url: 'https://github.com/stakwork/sphinx-tribes-frontend' + uuid: 'com1t3gn1e4a4qu3tnlg', + workspace_uuid: 'cohob00n1e4808utqel0', + name: ' frontend ', + url: ' https://github.com/stakwork/sphinx-tribes-frontend ' }, { - name: 'backend', - url: 'https://github.com/stakwork/sphinx-tribes' + uuid: 'com1t3gn1e4a4qu3tnlg', + workspace_uuid: 'cohob00n1e4808utqel0', + name: ' backend ', + url: ' https://github.com/stakwork/sphinx-tribes ' } ]; export const Features = [ { - name: 'Hive Process', + uuid: 'com1kson1e49th88dbg0', + workspace_uuid: 'cohob00n1e4808utqel0', + name: ' Hive Process ', priority: 1, - brief: 'To follow a set of best practices in product development.
' + + brief: ' To follow a set of best practices in product development.
' + 'Dividing complex features into small
steps makes it easier to ' + 'track and the timing more certain.
A guided process would help ' + 'a PM new to the hive process get the best results with the least mental ' + 'load.
This feature is for a not se technical Product Manager.
' + - 'The hive process lets you get features out to production faster and with less risk.', - requirements: 'Modify workspaces endpoint to accomodate new fields.
' + - 'Create end points for features, user stories and phases', - architecture: 'Describe the architecture of the feature with the following sections:' + + 'The hive process lets you get features out to production faster and with less risk. ', + requirements: ' Modify workspaces endpoint to accomodate new fields.
' + + 'Create end points for features, user stories and phases ', + architecture: ' Describe the architecture of the feature with the following sections:' + '

Wireframes

Visual Schematics

Object Definition

' + 'DB Schema Changes

UX

CI/CD

Changes

Endpoints

' + - 'Front

', + 'Front

', }, { - name: 'AI Assited text fields', + uuid: 'com1l5on1e49tucv350g', + workspace_uuid: 'cohob00n1e4808utqel0', + name: ' AI Assited text fields ', priority: 2, brief: 'An important struggle of a technical product manager is to find ' + 'the right words to describe a business goal. The definition of ' + @@ -93,18 +101,20 @@ export const Features = [ 'We are going to leverage AI to help the PM write better definitions.
' + 'The fields that would benefit form AI assistance are: mission, tactics, ' + 'feature brief and feature user stories', - requirements: 'Create a new page for a conversation format between the PM and the LLM
' + + requirements: ' Create a new page for a conversation format between the PM and the LLM
' + 'Rely as much as possible on stakwork workflows
' + - 'Have history of previous definitions', - architecture: 'Describe the architecture of the feature with the following sections:' + + 'Have history of previous definitions ', + architecture: ' Describe the architecture of the feature with the following sections:' + '

Wireframes

Visual Schematics

Object Definition

' + 'DB Schema Changes

UX

CI/CD

Changes

Endpoints

' + - 'Front

', + 'Front

', }, { - name: 'AI Assited relation between text fields', + uuid: 'com1l5on1e49tucv350g', + workspace_uuid: 'cohob00n1e4808utqel0', + name: ' AI Assited relation between text fields ', priority: 2, - brief: 'A product and feature\'s various definition fields: mission, tactics, ' + + brief: ' A product and feature\'s various definition fields: mission, tactics, ' + 'feature brief, user stories, requirements and architecture should have some ' + 'relation between each other.
' + 'One way to do that is to leverage an LLM ' + 'to discern the parts of the defintion that have a connection to other definitions.
' + @@ -114,21 +124,21 @@ export const Features = [ architecture: 'Describe the architecture of the feature with the following sections:' + '

Wireframes

Visual Schematics

Object Definition

' + 'DB Schema Changes

UX

CI/CD

Changes

Endpoints

' + - 'Front

', + 'Front

', }, ]; export const UserStories = [ - { id: 'f4c4c4b4-7a90-4a3a-b3e2-151d0feca9bf', description: ' As a {PM} I want to {make providers \"hive ready\"}, so I can {leverage the hive process ' }, - { id: '78f4b326-1841-449b-809a-a0947622db3e', description: ' As a {PM} I want to {CRUD Features}, so I can {use the system to manage my features} ' }, - { id: '5d353d23-3d27-4aa8-a9f7-04dcd5f4843c', description: ' As a {PM} I want to {follow best practices}, so I can {make more valuable features} ' }, - { id: '1a4e00f4-0e58-4e08-a1df-b623bc10f08d', description: ' As a {PM} I want to {save the architecture of the feature}, so I can {share it with people} ' }, - { id: 'eb6e4138-37e5-465d-934e-18e335abaa47', description: ' As a {PM} I want to {create phases}, so I can {divide the work in several deliverable stages} ' }, - { id: '35a5d8dd-240d-4ff0-a699-aa2fa2cfa32c', description: ' As a {PM} I want to {assign bounties to features}, so I can {group bounties together} ' }, + { uuid: 'com1lh0n1e49ug76noig', feature_uuid: 'com1kson1e49th88dbg0', description: ' As a {PM} I want to {make providers \"hive ready\"}, so I can {leverage the hive process ' }, + { uuid: 'com1lk8n1e49uqfe3l40', feature_uuid: 'com1kson1e49th88dbg0', description: ' As a {PM} I want to {CRUD Features}, so I can {use the system to manage my features} ' }, + { uuid: 'com1ln8n1e49v4159gug', feature_uuid: 'com1kson1e49th88dbg0', description: ' As a {PM} I want to {follow best practices}, so I can {make more valuable features} ' }, + { uuid: 'com1lqgn1e49vevhs9k0', feature_uuid: 'com1kson1e49th88dbg0', description: ' As a {PM} I want to {save the architecture of the feature}, so I can {share it with people} ' }, + { uuid: 'com1lt8n1e49voquoq90', feature_uuid: 'com1kson1e49th88dbg0', description: ' As a {PM} I want to {create phases}, so I can {divide the work in several deliverable stages} ' }, + { uuid: 'com1m08n1e4a02r6j0pg', feature_uuid: 'com1kson1e49th88dbg0', description: ' As a {PM} I want to {assign bounties to features}, so I can {group bounties together} ' }, ]; export const Phases = [ - { id: 'a96e3bff-e5c8-429e-bd65-911d619761aa', name: ' MVP ' }, - { id: '6de147ab-695c-45b1-81e7-2d1a5ba482ab', name: ' MVP ' }, - { id: '28541c4a-41de-447e-86d8-293583d1abc2', name: ' MVP ' }, + { uuid: 'com1msgn1e4a0ts5kls0', feature_uuid: 'com1kson1e49th88dbg0', name: ' MVP ' }, + { uuid: 'com1mvgn1e4a1879uiv0', feature_uuid: 'com1kson1e49th88dbg0', name: ' Phase 2 ' }, + { uuid: 'com1n2gn1e4a1i8p60p0', feature_uuid: 'com1kson1e49th88dbg0', name: ' Phase 3 ' }, ]; \ No newline at end of file From ddf56a8cc334266545cfa25db04643b4d7cbfd1e Mon Sep 17 00:00:00 2001 From: l Date: Tue, 30 Apr 2024 13:27:50 -0400 Subject: [PATCH 2/4] fix cypress tests --- cypress/e2e/03_features.cy.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cypress/e2e/03_features.cy.ts b/cypress/e2e/03_features.cy.ts index 6e3713067..3a9becb7c 100644 --- a/cypress/e2e/03_features.cy.ts +++ b/cypress/e2e/03_features.cy.ts @@ -53,7 +53,7 @@ describe('Modify brief for Feature', () => { uuid: Features[i].uuid, brief: Features[i].brief + "_addtext" } - }).its('body').should('have.property', 'name', Features[i].name.trim()) + }).its('body').should('have.property', 'name', Features[i].name.trim() + "_addtext") .its('body').should('have.property', 'brief', Features[i].brief.trim() + "_addtext") .its('body').should('have.property', 'requirements', Features[i].requirements.trim()) .its('body').should('have.property', 'architecture', Features[i].architecture.trim()) @@ -74,8 +74,8 @@ describe('Modify requirements for Feature', () => { uuid: Features[i].uuid, requirements: Features[i].requirements + "_addtext" } - }).its('body').should('have.property', 'name', Features[i].name.trim()) - .its('body').should('have.property', 'brief', Features[i].brief.trim()) + }).its('body').should('have.property', 'name', Features[i].name.trim() + "_addtext") + .its('body').should('have.property', 'brief', Features[i].brief.trim() + "_addtext") .its('body').should('have.property', 'requirements', Features[i].requirements.trim() + "_addtext") .its('body').should('have.property', 'architecture', Features[i].architecture.trim()) } @@ -95,9 +95,9 @@ describe('Modify architecture for Feature', () => { uuid: Features[i].uuid, architecture: Features[i].architecture + "_addtext" } - }).its('body').should('have.property', 'name', Features[i].name.trim()) - .its('body').should('have.property', 'brief', Features[i].brief.trim()) - .its('body').should('have.property', 'requirements', Features[i].requirements.trim()) + }).its('body').should('have.property', 'name', Features[i].name.trim() + "_addtext") + .its('body').should('have.property', 'brief', Features[i].brief.trim() + "_addtext") + .its('body').should('have.property', 'requirements', Features[i].requirements.trim() + "_addtext") .its('body').should('have.property', 'architecture', Features[i].architecture.trim() + "_addtext") } }) From 298a07bb4fbfa315da61fc7ab77ef388dbf895b2 Mon Sep 17 00:00:00 2001 From: Abdul Wahab Date: Thu, 2 May 2024 16:04:37 +0500 Subject: [PATCH 3/4] Added feature endpoints --- cypress/e2e/03_features.cy.ts | 66 +++++++------ cypress/support/objects/objects.ts | 8 +- db/config.go | 1 + db/features.go | 41 ++++++++ db/interface.go | 3 + db/structs.go | 14 +++ handlers/features.go | 93 ++++++++++++++++++ mocks/Database.go | 150 +++++++++++++++++++++++++++++ routes/features.go | 21 ++++ routes/index.go | 1 + 10 files changed, 366 insertions(+), 32 deletions(-) create mode 100644 db/features.go create mode 100644 handlers/features.go create mode 100644 routes/features.go diff --git a/cypress/e2e/03_features.cy.ts b/cypress/e2e/03_features.cy.ts index 3a9becb7c..dd2c66424 100644 --- a/cypress/e2e/03_features.cy.ts +++ b/cypress/e2e/03_features.cy.ts @@ -11,10 +11,12 @@ describe('Create Features for Workspace', () => { url: `${HostName}/features`, headers: { 'x-jwt': `${value}` }, body: Features[i] - }).its('body').should('have.property', 'name', Features[i].name.trim()) - .its('body').should('have.property', 'brief', Features[i].brief.trim()) - .its('body').should('have.property', 'requirements', Features[i].requirements.trim()) - .its('body').should('have.property', 'architecture', Features[i].architecture.trim()) + }).its('body').then(body => { + expect(body).to.have.property('name').and.equal(Features[i].name.trim()); + expect(body).to.have.property('brief').and.equal(Features[i].brief.trim()); + expect(body).to.have.property('requirements').and.equal(Features[i].requirements.trim()); + expect(body).to.have.property('architecture').and.equal(Features[i].architecture.trim()); + }); } }) }) @@ -32,10 +34,12 @@ describe('Modify name for Feature', () => { uuid: Features[i].uuid, name: Features[i].name + "_addtext" } - }).its('body').should('have.property', 'name', Features[i].name.trim() + "_addtext") - .its('body').should('have.property', 'brief', Features[i].brief.trim()) - .its('body').should('have.property', 'requirements', Features[i].requirements.trim()) - .its('body').should('have.property', 'architecture', Features[i].architecture.trim()) + }).its('body').then(body => { + expect(body).to.have.property('name').and.equal(Features[i].name.trim() + " _addtext"); + expect(body).to.have.property('brief').and.equal(Features[i].brief.trim()); + expect(body).to.have.property('requirements').and.equal(Features[i].requirements.trim()); + expect(body).to.have.property('architecture').and.equal(Features[i].architecture.trim()); + }); } }) }) @@ -53,10 +57,12 @@ describe('Modify brief for Feature', () => { uuid: Features[i].uuid, brief: Features[i].brief + "_addtext" } - }).its('body').should('have.property', 'name', Features[i].name.trim() + "_addtext") - .its('body').should('have.property', 'brief', Features[i].brief.trim() + "_addtext") - .its('body').should('have.property', 'requirements', Features[i].requirements.trim()) - .its('body').should('have.property', 'architecture', Features[i].architecture.trim()) + }).its('body').then(body => { + expect(body).to.have.property('name').and.equal(Features[i].name.trim() + " _addtext"); + expect(body).to.have.property('brief').and.equal(Features[i].brief.trim() + " _addtext"); + expect(body).to.have.property('requirements').and.equal(Features[i].requirements.trim()); + expect(body).to.have.property('architecture').and.equal(Features[i].architecture.trim()); + }); } }) }) @@ -74,10 +80,12 @@ describe('Modify requirements for Feature', () => { uuid: Features[i].uuid, requirements: Features[i].requirements + "_addtext" } - }).its('body').should('have.property', 'name', Features[i].name.trim() + "_addtext") - .its('body').should('have.property', 'brief', Features[i].brief.trim() + "_addtext") - .its('body').should('have.property', 'requirements', Features[i].requirements.trim() + "_addtext") - .its('body').should('have.property', 'architecture', Features[i].architecture.trim()) + }).its('body').then(body => { + expect(body).to.have.property('name').and.equal(Features[i].name.trim() + " _addtext"); + expect(body).to.have.property('brief').and.equal(Features[i].brief.trim() + " _addtext"); + expect(body).to.have.property('requirements').and.equal(Features[i].requirements.trim() + " _addtext"); + expect(body).to.have.property('architecture').and.equal(Features[i].architecture.trim()); + }); } }) }) @@ -95,10 +103,12 @@ describe('Modify architecture for Feature', () => { uuid: Features[i].uuid, architecture: Features[i].architecture + "_addtext" } - }).its('body').should('have.property', 'name', Features[i].name.trim() + "_addtext") - .its('body').should('have.property', 'brief', Features[i].brief.trim() + "_addtext") - .its('body').should('have.property', 'requirements', Features[i].requirements.trim() + "_addtext") - .its('body').should('have.property', 'architecture', Features[i].architecture.trim() + "_addtext") + }).its('body').then(body => { + expect(body).to.have.property('name').and.equal(Features[i].name.trim() + " _addtext"); + expect(body).to.have.property('brief').and.equal(Features[i].brief.trim() + " _addtext"); + expect(body).to.have.property('requirements').and.equal(Features[i].requirements.trim() + " _addtext"); + expect(body).to.have.property('architecture').and.equal(Features[i].architecture.trim() + " _addtext"); + }); } }) }) @@ -116,10 +126,10 @@ describe('Get Features for Workspace', () => { }).then((resp) => { expect(resp.status).to.eq(200) for(let i = 0; i <= 2; i++) { - expect(resp.body[i]).to.have.property('name', Features[i].name.trim() + "_addtext") - expect(resp.body[i]).to.have.property('brief', Features[i].brief.trim() + "_addtext") - expect(resp.body[i]).to.have.property('requirements', Features[i].requirements.trim() + "_addtext") - expect(resp.body[i]).to.have.property('architecture', Features[i].architecture.trim() + "_addtext") + expect(resp.body[i]).to.have.property('name', Features[i].name.trim() + " _addtext") + expect(resp.body[i]).to.have.property('brief', Features[i].brief.trim() + " _addtext") + expect(resp.body[i]).to.have.property('requirements', Features[i].requirements.trim() + " _addtext") + expect(resp.body[i]).to.have.property('architecture', Features[i].architecture.trim() + " _addtext") } }) }) @@ -137,10 +147,10 @@ describe('Get Feature by uuid', () => { body: {} }).then((resp) => { expect(resp.status).to.eq(200) - expect(resp.body).to.have.property('name', Features[i].name.trim() + "_addtext") - expect(resp.body).to.have.property('brief', Features[i].brief.trim() + "_addtext") - expect(resp.body).to.have.property('requirements', Features[i].requirements.trim() + "_addtext") - expect(resp.body).to.have.property('architecture', Features[i].architecture.trim() + "_addtext") + expect(resp.body).to.have.property('name', Features[i].name.trim() + " _addtext") + expect(resp.body).to.have.property('brief', Features[i].brief.trim() + " _addtext") + expect(resp.body).to.have.property('requirements', Features[i].requirements.trim() + " _addtext") + expect(resp.body).to.have.property('architecture', Features[i].architecture.trim() + " _addtext") }) } }) diff --git a/cypress/support/objects/objects.ts b/cypress/support/objects/objects.ts index c1cfa4af0..bcbe8215d 100644 --- a/cypress/support/objects/objects.ts +++ b/cypress/support/objects/objects.ts @@ -100,7 +100,7 @@ export const Features = [ ' the base from which every technical decition relays on.
' + 'We are going to leverage AI to help the PM write better definitions.
' + 'The fields that would benefit form AI assistance are: mission, tactics, ' + - 'feature brief and feature user stories', + 'feature brief and feature user stories ', requirements: ' Create a new page for a conversation format between the PM and the LLM
' + 'Rely as much as possible on stakwork workflows
' + 'Have history of previous definitions ', @@ -110,7 +110,7 @@ export const Features = [ 'Front

', }, { - uuid: 'com1l5on1e49tucv350g', + uuid: 'com1l5on1e49tucv350h', workspace_uuid: 'cohob00n1e4808utqel0', name: ' AI Assited relation between text fields ', priority: 2, @@ -118,9 +118,9 @@ export const Features = [ 'feature brief, user stories, requirements and architecture should have some ' + 'relation between each other.
' + 'One way to do that is to leverage an LLM ' + 'to discern the parts of the defintion that have a connection to other definitions.
' + - 'The UI will need to show the user how each definition is related to other defintions.', + 'The UI will need to show the user how each definition is related to other defintions. ', requirements: 'Create a new process after a Feature text has changed. It should use the LLM to ' + - 'determine de relationship between parts of the text.', + 'determine de relationship between parts of the text. ', architecture: 'Describe the architecture of the feature with the following sections:' + '

Wireframes

Visual Schematics

Object Definition

' + 'DB Schema Changes

UX

CI/CD

Changes

Endpoints

' + diff --git a/db/config.go b/db/config.go index 4b50a4502..f07e89d70 100644 --- a/db/config.go +++ b/db/config.go @@ -67,6 +67,7 @@ func InitDB() { db.AutoMigrate(&ConnectionCodes{}) db.AutoMigrate(&BountyRoles{}) db.AutoMigrate(&UserInvoiceData{}) + db.AutoMigrate(&WorkspaceFeatures{}) DB.MigrateTablesWithOrgUuid() DB.MigrateOrganizationToWorkspace() diff --git a/db/features.go b/db/features.go new file mode 100644 index 000000000..c978124be --- /dev/null +++ b/db/features.go @@ -0,0 +1,41 @@ +package db + +import ( + "strings" + "time" +) + +func (db database) GetFeaturesByWorkspaceUuid(uuid string) []WorkspaceFeatures { + ms := []WorkspaceFeatures{} + + db.db.Model(&WorkspaceFeatures{}).Where("workspace_uuid = ?", uuid).Order("Created").Find(&ms) + + return ms +} + +func (db database) GetFeatureByUuid(uuid string) WorkspaceFeatures { + ms := WorkspaceFeatures{} + + db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", uuid).Find(&ms) + + return ms +} + +func (db database) CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, error) { + m.Name = strings.TrimSpace(m.Name) + m.Brief = strings.TrimSpace(m.Brief) + m.Requirements = strings.TrimSpace(m.Requirements) + m.Architecture = strings.TrimSpace(m.Architecture) + + now := time.Now() + m.Updated = &now + + if db.db.Model(&m).Where("uuid = ?", m.Uuid).Updates(&m).RowsAffected == 0 { + m.Created = &now + db.db.Create(&m) + } + + db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", m.Uuid).Find(&m) + + return m, nil +} diff --git a/db/interface.go b/db/interface.go index e3cc95163..757c7f542 100644 --- a/db/interface.go +++ b/db/interface.go @@ -139,4 +139,7 @@ type Database interface { PersonUniqueNameFromName(name string) (string, error) ProcessAlerts(p Person) UserHasAccess(pubKeyFromAuth string, uuid string, role string) bool + CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, error) + GetFeaturesByWorkspaceUuid(uuid string) []WorkspaceFeatures + GetFeatureByUuid(uuid string) WorkspaceFeatures } diff --git a/db/structs.go b/db/structs.go index e6a25a7de..caaa56a4c 100644 --- a/db/structs.go +++ b/db/structs.go @@ -547,6 +547,20 @@ type WorkspaceUsersData struct { Person } +type WorkspaceFeatures struct { + ID uint `json:"id"` + Uuid string `json:"uuid"` + WorkspaceUuid string `json:"workspace_uuid"` + Name string `json:"name"` + Brief string `json:"brief"` + Requirements string `json:"requirements"` + Architecture string `json:"architecture"` + Created *time.Time `json:"created"` + Updated *time.Time `json:"updated"` + CreatedBy string `json:"created_by"` + UpdatedBy string `json:"updated_by"` +} + type BountyRoles struct { Name string `json:"name"` } diff --git a/handlers/features.go b/handlers/features.go new file mode 100644 index 000000000..c8b03f215 --- /dev/null +++ b/handlers/features.go @@ -0,0 +1,93 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/go-chi/chi" + "github.com/stakwork/sphinx-tribes/auth" + "github.com/stakwork/sphinx-tribes/db" +) + +type featureHandler struct { + db db.Database +} + +func NewFeatureHandler(database db.Database) *featureHandler { + return &featureHandler{ + db: database, + } +} + +func (oh *featureHandler) CreateOrEditFeatures(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) + if pubKeyFromAuth == "" { + fmt.Println("no pubkey from auth") + w.WriteHeader(http.StatusUnauthorized) + return + } + + features := db.WorkspaceFeatures{} + body, _ := io.ReadAll(r.Body) + r.Body.Close() + err := json.Unmarshal(body, &features) + + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusNotAcceptable) + return + } + + // Validate struct data + err = db.Validate.Struct(features) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + msg := fmt.Sprintf("Error: did not pass validation test : %s", err) + json.NewEncoder(w).Encode(msg) + return + } + + p, err := oh.db.CreateOrEditFeature(features) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(p) +} + +func (oh *featureHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) + if pubKeyFromAuth == "" { + fmt.Println("no pubkey from auth") + w.WriteHeader(http.StatusUnauthorized) + return + } + + uuid := chi.URLParam(r, "uuid") + workspaceFeatures := oh.db.GetFeaturesByWorkspaceUuid(uuid) + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(workspaceFeatures) +} + +func (oh *featureHandler) GetFeatureByUuid(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) + if pubKeyFromAuth == "" { + fmt.Println("no pubkey from auth") + w.WriteHeader(http.StatusUnauthorized) + return + } + + uuid := chi.URLParam(r, "uuid") + workspaceFeature := oh.db.GetFeatureByUuid(uuid) + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(workspaceFeature) +} diff --git a/mocks/Database.go b/mocks/Database.go index 784a2492a..974d81e38 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -927,6 +927,62 @@ func (_c *Database_CreateOrEditBounty_Call) RunAndReturn(run func(db.NewBounty) return _c } +// CreateOrEditFeature provides a mock function with given fields: m +func (_m *Database) CreateOrEditFeature(m db.WorkspaceFeatures) (db.WorkspaceFeatures, error) { + ret := _m.Called(m) + + if len(ret) == 0 { + panic("no return value specified for CreateOrEditFeature") + } + + var r0 db.WorkspaceFeatures + var r1 error + if rf, ok := ret.Get(0).(func(db.WorkspaceFeatures) (db.WorkspaceFeatures, error)); ok { + return rf(m) + } + if rf, ok := ret.Get(0).(func(db.WorkspaceFeatures) db.WorkspaceFeatures); ok { + r0 = rf(m) + } else { + r0 = ret.Get(0).(db.WorkspaceFeatures) + } + + if rf, ok := ret.Get(1).(func(db.WorkspaceFeatures) error); ok { + r1 = rf(m) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Database_CreateOrEditFeature_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateOrEditFeature' +type Database_CreateOrEditFeature_Call struct { + *mock.Call +} + +// CreateOrEditFeature is a helper method to define mock.On call +// - m db.WorkspaceFeatures +func (_e *Database_Expecter) CreateOrEditFeature(m interface{}) *Database_CreateOrEditFeature_Call { + return &Database_CreateOrEditFeature_Call{Call: _e.mock.On("CreateOrEditFeature", m)} +} + +func (_c *Database_CreateOrEditFeature_Call) Run(run func(m db.WorkspaceFeatures)) *Database_CreateOrEditFeature_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(db.WorkspaceFeatures)) + }) + return _c +} + +func (_c *Database_CreateOrEditFeature_Call) Return(_a0 db.WorkspaceFeatures, _a1 error) *Database_CreateOrEditFeature_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Database_CreateOrEditFeature_Call) RunAndReturn(run func(db.WorkspaceFeatures) (db.WorkspaceFeatures, error)) *Database_CreateOrEditFeature_Call { + _c.Call.Return(run) + return _c +} + // CreateOrEditPerson provides a mock function with given fields: m func (_m *Database) CreateOrEditPerson(m db.Person) (db.Person, error) { ret := _m.Called(m) @@ -2520,6 +2576,100 @@ func (_c *Database_GetCreatedBounties_Call) RunAndReturn(run func(*http.Request) return _c } +// GetFeatureByUuid provides a mock function with given fields: uuid +func (_m *Database) GetFeatureByUuid(uuid string) db.WorkspaceFeatures { + ret := _m.Called(uuid) + + if len(ret) == 0 { + panic("no return value specified for GetFeatureByUuid") + } + + var r0 db.WorkspaceFeatures + if rf, ok := ret.Get(0).(func(string) db.WorkspaceFeatures); ok { + r0 = rf(uuid) + } else { + r0 = ret.Get(0).(db.WorkspaceFeatures) + } + + return r0 +} + +// Database_GetFeatureByUuid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFeatureByUuid' +type Database_GetFeatureByUuid_Call struct { + *mock.Call +} + +// GetFeatureByUuid is a helper method to define mock.On call +// - uuid string +func (_e *Database_Expecter) GetFeatureByUuid(uuid interface{}) *Database_GetFeatureByUuid_Call { + return &Database_GetFeatureByUuid_Call{Call: _e.mock.On("GetFeatureByUuid", uuid)} +} + +func (_c *Database_GetFeatureByUuid_Call) Run(run func(uuid string)) *Database_GetFeatureByUuid_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Database_GetFeatureByUuid_Call) Return(_a0 db.WorkspaceFeatures) *Database_GetFeatureByUuid_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_GetFeatureByUuid_Call) RunAndReturn(run func(string) db.WorkspaceFeatures) *Database_GetFeatureByUuid_Call { + _c.Call.Return(run) + return _c +} + +// GetFeaturesByWorkspaceUuid provides a mock function with given fields: uuid +func (_m *Database) GetFeaturesByWorkspaceUuid(uuid string) []db.WorkspaceFeatures { + ret := _m.Called(uuid) + + if len(ret) == 0 { + panic("no return value specified for GetFeaturesByWorkspaceUuid") + } + + var r0 []db.WorkspaceFeatures + if rf, ok := ret.Get(0).(func(string) []db.WorkspaceFeatures); ok { + r0 = rf(uuid) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.WorkspaceFeatures) + } + } + + return r0 +} + +// Database_GetFeaturesByWorkspaceUuid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFeaturesByWorkspaceUuid' +type Database_GetFeaturesByWorkspaceUuid_Call struct { + *mock.Call +} + +// GetFeaturesByWorkspaceUuid is a helper method to define mock.On call +// - uuid string +func (_e *Database_Expecter) GetFeaturesByWorkspaceUuid(uuid interface{}) *Database_GetFeaturesByWorkspaceUuid_Call { + return &Database_GetFeaturesByWorkspaceUuid_Call{Call: _e.mock.On("GetFeaturesByWorkspaceUuid", uuid)} +} + +func (_c *Database_GetFeaturesByWorkspaceUuid_Call) Run(run func(uuid string)) *Database_GetFeaturesByWorkspaceUuid_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Database_GetFeaturesByWorkspaceUuid_Call) Return(_a0 []db.WorkspaceFeatures) *Database_GetFeaturesByWorkspaceUuid_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_GetFeaturesByWorkspaceUuid_Call) RunAndReturn(run func(string) []db.WorkspaceFeatures) *Database_GetFeaturesByWorkspaceUuid_Call { + _c.Call.Return(run) + return _c +} + // GetFilterStatusCount provides a mock function with given fields: func (_m *Database) GetFilterStatusCount() db.FilterStattuCount { ret := _m.Called() diff --git a/routes/features.go b/routes/features.go new file mode 100644 index 000000000..0b2449399 --- /dev/null +++ b/routes/features.go @@ -0,0 +1,21 @@ +package routes + +import ( + "github.com/go-chi/chi" + "github.com/stakwork/sphinx-tribes/auth" + "github.com/stakwork/sphinx-tribes/db" + "github.com/stakwork/sphinx-tribes/handlers" +) + +func FeatureRoutes() chi.Router { + r := chi.NewRouter() + featureHandlers := handlers.NewFeatureHandler(db.DB) + r.Group(func(r chi.Router) { + r.Use(auth.PubKeyContext) + + r.Post("/", featureHandlers.CreateOrEditFeatures) + r.Get("/forworkspace/{uuid}", featureHandlers.GetFeaturesByWorkspaceUuid) + r.Get("/{uuid}", featureHandlers.GetFeatureByUuid) + }) + return r +} diff --git a/routes/index.go b/routes/index.go index 98dc0c162..22090938c 100644 --- a/routes/index.go +++ b/routes/index.go @@ -36,6 +36,7 @@ func NewRouter() *http.Server { r.Mount("/gobounties", BountyRoutes()) r.Mount("/workspaces", WorkspaceRoutes()) r.Mount("/metrics", MetricsRoutes()) + r.Mount("/features", FeatureRoutes()) r.Group(func(r chi.Router) { r.Get("/tribe_by_feed", tribeHandlers.GetFirstTribeByFeed) From 30081336f1c90b8884e950cc60aab7608401cc94 Mon Sep 17 00:00:00 2001 From: Abdul Wahab Date: Thu, 2 May 2024 21:46:34 +0500 Subject: [PATCH 4/4] Addressed changes --- handlers/features.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/handlers/features.go b/handlers/features.go index c8b03f215..9c4f9cd68 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -41,6 +41,8 @@ func (oh *featureHandler) CreateOrEditFeatures(w http.ResponseWriter, r *http.Re return } + features.CreatedBy = pubKeyFromAuth + // Validate struct data err = db.Validate.Struct(features) if err != nil {