From 709d6011ff882dc26140c4e8d1cb9fe8c2f2078b Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 26 Apr 2024 13:30:54 +0100 Subject: [PATCH 01/44] fixed migration org uuid --- .github/workflows/docker-build-on-master.yml | 9 +++------ .github/workflows/frontend-e2e.yml | 2 +- .github/workflows/prjob_tests.yml | 2 +- db/config.go | 14 ++++++++++++++ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/.github/workflows/docker-build-on-master.yml b/.github/workflows/docker-build-on-master.yml index df0cb0219..8bba45a8f 100644 --- a/.github/workflows/docker-build-on-master.yml +++ b/.github/workflows/docker-build-on-master.yml @@ -4,13 +4,13 @@ env: on: push: - branch: - - master + branches: + - master jobs: build: runs-on: ubuntu-20.04 - name: Build and push Tribes image + name: Build and push Tribes image env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true steps: @@ -44,6 +44,3 @@ jobs: --platform linux/amd64,linux/arm64,linux/arm/v7 \ --tag "${{ secrets.DOCKER_HUB_USER }}/sphinx-tribes:master" \ --output "type=registry" ./ - - - diff --git a/.github/workflows/frontend-e2e.yml b/.github/workflows/frontend-e2e.yml index d284d6a53..36675e00b 100644 --- a/.github/workflows/frontend-e2e.yml +++ b/.github/workflows/frontend-e2e.yml @@ -2,7 +2,7 @@ name: Cypress Frontend E2E tests on: pull_request: branches: - - master + - "*" jobs: cypress-run: diff --git a/.github/workflows/prjob_tests.yml b/.github/workflows/prjob_tests.yml index 810e6a84e..38a579912 100644 --- a/.github/workflows/prjob_tests.yml +++ b/.github/workflows/prjob_tests.yml @@ -2,7 +2,7 @@ name: Tests on: pull_request: branches: - - master + - "*" jobs: test-go: name: Go diff --git a/db/config.go b/db/config.go index 9789078b9..f635fa4ef 100644 --- a/db/config.go +++ b/db/config.go @@ -177,6 +177,8 @@ func (db database) MigrateTablesWithOrgUuid() { if !db.db.Migrator().HasTable("bounty") { if !db.db.Migrator().HasColumn(Bounty{}, "workspace_uuid") { db.db.AutoMigrate(&Bounty{}) + } else { + db.db.AutoMigrate(&NewBounty{}) } } if !db.db.Migrator().HasTable("budget_histories") { @@ -187,28 +189,40 @@ func (db database) MigrateTablesWithOrgUuid() { if !db.db.Migrator().HasTable("payment_histories") { if !db.db.Migrator().HasColumn(PaymentHistory{}, "workspace_uuid") { db.db.AutoMigrate(&PaymentHistory{}) + } else { + db.db.AutoMigrate(&NewPaymentHistory{}) } } if !db.db.Migrator().HasTable("invoice_list") { if !db.db.Migrator().HasColumn(InvoiceList{}, "workspace_uuid") { db.db.AutoMigrate(&InvoiceList{}) + } else { + db.db.AutoMigrate(&NewInvoiceList{}) } } if !db.db.Migrator().HasTable("bounty_budgets") { if !db.db.Migrator().HasColumn(BountyBudget{}, "workspace_uuid") { db.db.AutoMigrate(&BountyBudget{}) + } else { + db.db.AutoMigrate(&NewBountyBudget{}) } } if !db.db.Migrator().HasTable("workspace_user_roles") { if !db.db.Migrator().HasColumn(UserRoles{}, "workspace_uuid") { db.db.AutoMigrate(&UserRoles{}) } + } else { + db.db.AutoMigrate(&WorkspaceUserRoles{}) } if !db.db.Migrator().HasTable("workspaces") { db.db.AutoMigrate(&Organization{}) + } else { + db.db.AutoMigrate(&Workspace{}) } if !db.db.Migrator().HasTable("workspace_users") { db.db.AutoMigrate(&OrganizationUsers{}) + } else { + db.db.AutoMigrate(&WorkspaceUsers{}) } } From 78e347052b62d1a9a51d1d25fcdbf55aba5c516a Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 26 Apr 2024 16:50:14 +0100 Subject: [PATCH 02/44] added difference in budget --- db/db.go | 2 +- db/structs.go | 21 ++++++++++++--------- db/workspaces.go | 27 ++++++++++++++++++--------- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/db/db.go b/db/db.go index d2af69fb5..ed1fd7e18 100644 --- a/db/db.go +++ b/db/db.go @@ -1142,7 +1142,7 @@ func (db database) GetAllBounties(r *http.Request) []NewBounty { languageArray := strings.Split(languages, ",") languageLength := len(languageArray) - if workspaceUuid != "" && orgUuid != "" { + if workspaceUuid == "" && orgUuid != "" { workspaceUuid = orgUuid } diff --git a/db/structs.go b/db/structs.go index 7f1bf8ea6..6ab15f38d 100644 --- a/db/structs.go +++ b/db/structs.go @@ -581,15 +581,18 @@ type NewBountyBudget struct { } type StatusBudget struct { - OrgUuid string `json:"org_uuid"` - WorkspaceUuid string `json:"workspace_uuid"` - CurrentBudget uint `json:"current_budget"` - OpenBudget uint `json:"open_budget"` - OpenCount int64 `json:"open_count"` - AssignedBudget uint `json:"assigned_budget"` - AssignedCount int64 `json:"assigned_count"` - CompletedBudget uint `json:"completed_budget"` - CompletedCount int64 `json:"completed_count"` + OrgUuid string `json:"org_uuid"` + WorkspaceUuid string `json:"workspace_uuid"` + CurrentBudget uint `json:"current_budget"` + OpenBudget uint `json:"open_budget"` + OpenCount int64 `json:"open_count"` + OpenDifference int `json:"open_difference"` + AssignedBudget uint `json:"assigned_budget"` + AssignedCount int64 `json:"assigned_count"` + AssignedDifference int `json:"assigned_difference"` + CompletedBudget uint `json:"completed_budget"` + CompletedCount int64 `json:"completed_count"` + CompletedDifference int `json:"completed_difference"` } type BudgetInvoiceRequest struct { diff --git a/db/workspaces.go b/db/workspaces.go index d1e77b596..f737bdd31 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -162,28 +162,37 @@ func (db database) GetWorkspaceStatusBudget(workspace_uuid string) StatusBudget var openCount int64 db.db.Model(&Bounty{}).Where("assignee = '' ").Where("paid != true").Count(&openCount) + var openDifference int = int(orgBudget.TotalBudget - openBudget) + var assignedBudget uint db.db.Model(&Bounty{}).Where("assignee != '' ").Where("paid != true").Select("SUM(price)").Row().Scan(&assignedBudget) var assignedCount int64 db.db.Model(&Bounty{}).Where("assignee != '' ").Where("paid != true").Count(&assignedCount) + var assignedDifference int = int(orgBudget.TotalBudget - assignedBudget) + var completedBudget uint db.db.Model(&Bounty{}).Where("completed = true ").Where("paid != true").Select("SUM(price)").Row().Scan(&completedBudget) var completedCount int64 db.db.Model(&Bounty{}).Where("completed = true ").Where("paid != true").Count(&completedCount) + var completedDifference int = int(orgBudget.TotalBudget - completedBudget) + statusBudget := StatusBudget{ - OrgUuid: workspace_uuid, - WorkspaceUuid: workspace_uuid, - CurrentBudget: orgBudget.TotalBudget, - OpenBudget: openBudget, - OpenCount: openCount, - AssignedBudget: assignedBudget, - AssignedCount: assignedCount, - CompletedBudget: completedBudget, - CompletedCount: completedCount, + OrgUuid: workspace_uuid, + WorkspaceUuid: workspace_uuid, + CurrentBudget: orgBudget.TotalBudget, + OpenBudget: openBudget, + OpenCount: openCount, + OpenDifference: openDifference, + AssignedBudget: assignedBudget, + AssignedCount: assignedCount, + AssignedDifference: assignedDifference, + CompletedBudget: completedBudget, + CompletedCount: completedCount, + CompletedDifference: completedDifference, } return statusBudget From 55752776435080bfa366c397de358c657e5d1a30 Mon Sep 17 00:00:00 2001 From: elraphty Date: Tue, 30 Apr 2024 10:47:41 +0100 Subject: [PATCH 03/44] fixed csv export bug --- db/metrics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/metrics.go b/db/metrics.go index 1a1f0e870..ae6389f8d 100644 --- a/db/metrics.go +++ b/db/metrics.go @@ -190,7 +190,7 @@ func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) } else { orderQuery = " ORDER BY " + sortBy + "" + "DESC" } - if limit > 0 { + if limit > 1 { limitQuery = fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset) } From 0a3f966946456971326b39145f25219823bf1d32 Mon Sep 17 00:00:00 2001 From: l Date: Tue, 30 Apr 2024 08:53:49 -0400 Subject: [PATCH 04/44] Added cypress tests and objects --- cypress/e2e/02_repositories.cy.ts | 38 ++++++++++++++++++ cypress/support/objects/objects.ts | 64 +++++++++++++++++------------- 2 files changed, 75 insertions(+), 27 deletions(-) create mode 100644 cypress/e2e/02_repositories.cy.ts diff --git a/cypress/e2e/02_repositories.cy.ts b/cypress/e2e/02_repositories.cy.ts new file mode 100644 index 000000000..f3484ba1b --- /dev/null +++ b/cypress/e2e/02_repositories.cy.ts @@ -0,0 +1,38 @@ +import { User, HostName, Workspaces, Repositories } from '../support/objects/objects'; + + +describe('Create Repositories for Workspace', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 1; i++) { + cy.request({ + method: 'POST', + url: `${HostName}/workspaces/repositories`, + headers: { 'x-jwt': `${value}` }, + body: Repositories[i] + }).its('body').should('have.property', 'name', Repositories[i].name.trim()) + .its('body').should('have.property', 'url', Repositories[i].url.trim()); + } + }) + }) +}) + + +describe('Check Repositories Values', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + cy.request({ + method: 'GET', + url: `${HostName}/workspaces/repositories/` + Repositories[0].workspace_uuid, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(200) + expect(resp.body[0]).to.have.property('name', Repositories[0].name.trim()) + expect(resp.body[0]).to.have.property('url', Repositories[0].url.trim()) + expect(resp.body[1]).to.have.property('name', Repositories[1].name.trim()) + expect(resp.body[1]).to.have.property('url', Repositories[1].url.trim()) + }) + }) + }) +}) diff --git a/cypress/support/objects/objects.ts b/cypress/support/objects/objects.ts index b62af74d7..bfae6de06 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 = [ { + 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 687a5fb36359b7fea2bb67d6018b94ef5c9dc8ef Mon Sep 17 00:00:00 2001 From: elraphty Date: Tue, 30 Apr 2024 15:15:25 +0100 Subject: [PATCH 05/44] fixed workspace name not being displayed --- db/interface.go | 2 +- db/metrics.go | 4 +-- db/structs.go | 58 +++++++++++++++++++++------------------- handlers/metrics.go | 54 ++++++++++++++++++++----------------- handlers/metrics_test.go | 12 ++++----- mocks/Database.go | 12 ++++----- 6 files changed, 75 insertions(+), 67 deletions(-) diff --git a/db/interface.go b/db/interface.go index e3cc95163..9a8b88112 100644 --- a/db/interface.go +++ b/db/interface.go @@ -133,7 +133,7 @@ type Database interface { NewHuntersPaid(r PaymentDateRange) int64 TotalHuntersPaid(r PaymentDateRange) int64 GetPersonByPubkey(pubkey string) Person - GetBountiesByDateRange(r PaymentDateRange, re *http.Request) []Bounty + GetBountiesByDateRange(r PaymentDateRange, re *http.Request) []NewBounty GetBountiesByDateRangeCount(r PaymentDateRange, re *http.Request) int64 GetBountiesProviders(r PaymentDateRange, re *http.Request) []Person PersonUniqueNameFromName(name string) (string, error) diff --git a/db/metrics.go b/db/metrics.go index ae6389f8d..b2a91bc94 100644 --- a/db/metrics.go +++ b/db/metrics.go @@ -155,7 +155,7 @@ func CalculateAverageDays(paidCount int64, paidSum uint) uint { return 0 } -func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) []Bounty { +func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) []NewBounty { offset, limit, sortBy, direction, _ := utils.GetPaginationParams(re) keys := re.URL.Query() open := keys.Get("Open") @@ -203,7 +203,7 @@ func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) query := `SELECT * FROM public.bounty WHERE created >= '` + r.StartDate + `' AND created <= '` + r.EndDate + `'` + providerCondition allQuery := query + " " + statusQuery + " " + orderQuery + " " + limitQuery - b := []Bounty{} + b := []NewBounty{} db.db.Raw(allQuery).Find(&b) return b } diff --git a/db/structs.go b/db/structs.go index 6ab15f38d..446b76b92 100644 --- a/db/structs.go +++ b/db/structs.go @@ -429,38 +429,42 @@ type BountyOwners struct { } type BountyData struct { - Bounty + Bounty NewBounty BountyId uint `json:"bounty_id"` BountyCreated int64 `json:"bounty_created"` BountyUpdated *time.Time `json:"bounty_updated"` BountyDescription string `json:"bounty_description"` Person - AssigneeAlias string `json:"assignee_alias"` - AssigneeId uint `json:"assignee_id"` - AssigneeImg string `json:"assignee_img"` - AssigneeCreated *time.Time `json:"assignee_created"` - AssigneeUpdated *time.Time `json:"assignee_updated"` - AssigneeDescription string `json:"assignee_description"` - AssigneeRouteHint string `json:"assignee_route_hint"` - BountyOwnerId uint `json:"bounty_owner_id"` - OwnerUuid string `json:"owner_uuid"` - OwnerKey string `json:"owner_key"` - OwnerAlias string `json:"owner_alias"` - OwnerUniqueName string `json:"owner_unique_name"` - OwnerDescription string `json:"owner_description"` - OwnerTags pq.StringArray `gorm:"type:text[]" json:"owner_tags" null` - OwnerImg string `json:"owner_img"` - OwnerCreated *time.Time `json:"owner_created"` - OwnerUpdated *time.Time `json:"owner_updated"` - OwnerLastLogin int64 `json:"owner_last_login"` - OwnerRouteHint string `json:"owner_route_hint"` - OwnerContactKey string `json:"owner_contact_key"` - OwnerPriceToMeet int64 `json:"owner_price_to_meet"` - OwnerTwitterConfirmed bool `json:"owner_twitter_confirmed"` - OrganizationName string `json:"organization_name"` - OrganizationImg string `json:"organization_img"` - WorkspaceUuid string `json:"organization_uuid"` - WorkspaceDescription string `json:"description"` + AssigneeAlias string `json:"assignee_alias"` + AssigneeId uint `json:"assignee_id"` + AssigneeImg string `json:"assignee_img"` + AssigneeCreated *time.Time `json:"assignee_created"` + AssigneeUpdated *time.Time `json:"assignee_updated"` + AssigneeDescription string `json:"assignee_description"` + AssigneeRouteHint string `json:"assignee_route_hint"` + BountyOwnerId uint `json:"bounty_owner_id"` + OwnerUuid string `json:"owner_uuid"` + OwnerKey string `json:"owner_key"` + OwnerAlias string `json:"owner_alias"` + OwnerUniqueName string `json:"owner_unique_name"` + OwnerDescription string `json:"owner_description"` + OwnerTags pq.StringArray `gorm:"type:text[]" json:"owner_tags" null` + OwnerImg string `json:"owner_img"` + OwnerCreated *time.Time `json:"owner_created"` + OwnerUpdated *time.Time `json:"owner_updated"` + OwnerLastLogin int64 `json:"owner_last_login"` + OwnerRouteHint string `json:"owner_route_hint"` + OwnerContactKey string `json:"owner_contact_key"` + OwnerPriceToMeet int64 `json:"owner_price_to_meet"` + OwnerTwitterConfirmed bool `json:"owner_twitter_confirmed"` + OrganizationName string `json:"organization_name"` + OrganizationImg string `json:"organization_img"` + OrganizationUuid string `json:"organization_uuid"` + OrganizationDescription string `json:"description"` + WorkspaceName string `json:"workspace_name"` + WorkspaceImg string `json:"workspace_img"` + WorkspaceUuid string `json:"workspace_uuid"` + WorkspaceDescription string `json:"workspace_description"` } type BountyResponse struct { diff --git a/handlers/metrics.go b/handlers/metrics.go index 2a0140cb5..8d24aab2f 100644 --- a/handlers/metrics.go +++ b/handlers/metrics.go @@ -300,46 +300,50 @@ func MetricsCsv(w http.ResponseWriter, r *http.Request) { } } -func (mh *metricHandler) GetMetricsBountiesData(metricBounties []db.Bounty) []db.BountyData { +func (mh *metricHandler) GetMetricsBountiesData(metricBounties []db.NewBounty) []db.BountyData { var metricBountiesData []db.BountyData for _, bounty := range metricBounties { bountyOwner := mh.db.GetPersonByPubkey(bounty.OwnerID) bountyAssignee := mh.db.GetPersonByPubkey(bounty.Assignee) - organization := mh.db.GetWorkspaceByUuid(bounty.OrgUuid) + workspace := mh.db.GetWorkspaceByUuid(bounty.WorkspaceUuid) bountyData := db.BountyData{ - Bounty: bounty, - BountyId: bounty.ID, - Person: bountyOwner, - BountyCreated: bounty.Created, - BountyDescription: bounty.Description, - BountyUpdated: bounty.Updated, - AssigneeId: bountyAssignee.ID, - AssigneeImg: bountyAssignee.Img, - AssigneeAlias: bountyAssignee.OwnerAlias, - AssigneeDescription: bountyAssignee.Description, - AssigneeRouteHint: bountyAssignee.OwnerRouteHint, - BountyOwnerId: bountyOwner.ID, - OwnerUuid: bountyOwner.Uuid, - OwnerDescription: bountyOwner.Description, - OwnerUniqueName: bountyOwner.UniqueName, - OwnerImg: bountyOwner.Img, - OrganizationName: organization.Name, - OrganizationImg: organization.Img, - WorkspaceUuid: organization.Uuid, - WorkspaceDescription: organization.Description, + Bounty: bounty, + BountyId: bounty.ID, + Person: bountyOwner, + BountyCreated: bounty.Created, + BountyDescription: bounty.Description, + BountyUpdated: bounty.Updated, + AssigneeId: bountyAssignee.ID, + AssigneeImg: bountyAssignee.Img, + AssigneeAlias: bountyAssignee.OwnerAlias, + AssigneeDescription: bountyAssignee.Description, + AssigneeRouteHint: bountyAssignee.OwnerRouteHint, + BountyOwnerId: bountyOwner.ID, + OwnerUuid: bountyOwner.Uuid, + OwnerDescription: bountyOwner.Description, + OwnerUniqueName: bountyOwner.UniqueName, + OwnerImg: bountyOwner.Img, + OrganizationName: workspace.Name, + OrganizationImg: workspace.Img, + OrganizationUuid: workspace.Uuid, + OrganizationDescription: workspace.Description, + WorkspaceName: workspace.Name, + WorkspaceImg: workspace.Img, + WorkspaceUuid: workspace.Uuid, + WorkspaceDescription: workspace.Description, } metricBountiesData = append(metricBountiesData, bountyData) } return metricBountiesData } -func getMetricsBountyCsv(metricBounties []db.Bounty) []db.MetricsBountyCsv { +func getMetricsBountyCsv(metricBounties []db.NewBounty) []db.MetricsBountyCsv { var metricBountiesCsv []db.MetricsBountyCsv for _, bounty := range metricBounties { bountyOwner := db.DB.GetPersonByPubkey(bounty.OwnerID) bountyAssignee := db.DB.GetPersonByPubkey(bounty.Assignee) - organization := db.DB.GetWorkspaceByUuid(bounty.OrgUuid) + workspace := db.DB.GetWorkspaceByUuid(bounty.WorkspaceUuid) bountyLink := fmt.Sprintf("https://community.sphinx.chat/bounty/%d", bounty.ID) bountyStatus := "Open" @@ -353,7 +357,7 @@ func getMetricsBountyCsv(metricBounties []db.Bounty) []db.MetricsBountyCsv { tm := time.Unix(bounty.Created, 0) bountyCsv := db.MetricsBountyCsv{ DatePosted: &tm, - Organization: organization.Name, + Organization: workspace.Name, BountyAmount: bounty.Price, Provider: bountyOwner.OwnerAlias, Hunter: bountyAssignee.OwnerAlias, diff --git a/handlers/metrics_test.go b/handlers/metrics_test.go index 38695f8ac..62e0eb05b 100644 --- a/handlers/metrics_test.go +++ b/handlers/metrics_test.go @@ -148,7 +148,7 @@ func TestMetricsBounties(t *testing.T) { t.Fatal(err) } - bounties := []db.Bounty{ + bounties := []db.NewBounty{ { ID: 1, OwnerID: "owner-1", @@ -169,9 +169,9 @@ func TestMetricsBounties(t *testing.T) { assert.Equal(t, http.StatusOK, rr.Code) assert.Equal(t, res[0].BountyId, uint(1)) - assert.Equal(t, res[0].OwnerID, "owner-1") - assert.Equal(t, res[0].Price, uint(100)) - assert.Equal(t, res[0].Title, "bounty 1") + assert.Equal(t, res[0].Bounty.OwnerID, "owner-1") + assert.Equal(t, res[0].Bounty.Price, uint(100)) + assert.Equal(t, res[0].Bounty.Title, "bounty 1") assert.Equal(t, res[0].BountyDescription, "test bounty") assert.Equal(t, res[0].BountyCreated, int64(1112)) }) @@ -194,7 +194,7 @@ func TestMetricsBounties(t *testing.T) { req.URL.RawQuery = "provider=provider1,provider2" // Mock bounties data for multiple providers - bounties := []db.Bounty{ + bounties := []db.NewBounty{ { ID: 1, OwnerID: "provider1", @@ -230,7 +230,7 @@ func TestMetricsBounties(t *testing.T) { // Assert that the response contains bounties only from the selected providers for _, bounty := range res { - assert.Contains(t, []string{"provider1", "provider2"}, bounty.OwnerID) + assert.Contains(t, []string{"provider1", "provider2"}, bounty.Bounty.OwnerID) } }) } diff --git a/mocks/Database.go b/mocks/Database.go index 784a2492a..7b3961287 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -1775,19 +1775,19 @@ func (_c *Database_GetBotsByOwner_Call) RunAndReturn(run func(string) []db.Bot) } // GetBountiesByDateRange provides a mock function with given fields: r, re -func (_m *Database) GetBountiesByDateRange(r db.PaymentDateRange, re *http.Request) []db.Bounty { +func (_m *Database) GetBountiesByDateRange(r db.PaymentDateRange, re *http.Request) []db.NewBounty { ret := _m.Called(r, re) if len(ret) == 0 { panic("no return value specified for GetBountiesByDateRange") } - var r0 []db.Bounty - if rf, ok := ret.Get(0).(func(db.PaymentDateRange, *http.Request) []db.Bounty); ok { + var r0 []db.NewBounty + if rf, ok := ret.Get(0).(func(db.PaymentDateRange, *http.Request) []db.NewBounty); ok { r0 = rf(r, re) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).([]db.Bounty) + r0 = ret.Get(0).([]db.NewBounty) } } @@ -1813,12 +1813,12 @@ func (_c *Database_GetBountiesByDateRange_Call) Run(run func(r db.PaymentDateRan return _c } -func (_c *Database_GetBountiesByDateRange_Call) Return(_a0 []db.Bounty) *Database_GetBountiesByDateRange_Call { +func (_c *Database_GetBountiesByDateRange_Call) Return(_a0 []db.NewBounty) *Database_GetBountiesByDateRange_Call { _c.Call.Return(_a0) return _c } -func (_c *Database_GetBountiesByDateRange_Call) RunAndReturn(run func(db.PaymentDateRange, *http.Request) []db.Bounty) *Database_GetBountiesByDateRange_Call { +func (_c *Database_GetBountiesByDateRange_Call) RunAndReturn(run func(db.PaymentDateRange, *http.Request) []db.NewBounty) *Database_GetBountiesByDateRange_Call { _c.Call.Return(run) return _c } From 4ff090db5e21c12c5ef77a3a2ee905d34d731d34 Mon Sep 17 00:00:00 2001 From: elraphty Date: Tue, 30 Apr 2024 16:49:44 +0100 Subject: [PATCH 06/44] Changed Bounty to NewBounty --- db/structs.go | 2 +- handlers/metrics.go | 2 +- handlers/metrics_test.go | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/db/structs.go b/db/structs.go index 446b76b92..637e9c295 100644 --- a/db/structs.go +++ b/db/structs.go @@ -429,7 +429,7 @@ type BountyOwners struct { } type BountyData struct { - Bounty NewBounty + NewBounty BountyId uint `json:"bounty_id"` BountyCreated int64 `json:"bounty_created"` BountyUpdated *time.Time `json:"bounty_updated"` diff --git a/handlers/metrics.go b/handlers/metrics.go index 8d24aab2f..431ceda55 100644 --- a/handlers/metrics.go +++ b/handlers/metrics.go @@ -308,7 +308,7 @@ func (mh *metricHandler) GetMetricsBountiesData(metricBounties []db.NewBounty) [ workspace := mh.db.GetWorkspaceByUuid(bounty.WorkspaceUuid) bountyData := db.BountyData{ - Bounty: bounty, + NewBounty: bounty, BountyId: bounty.ID, Person: bountyOwner, BountyCreated: bounty.Created, diff --git a/handlers/metrics_test.go b/handlers/metrics_test.go index 62e0eb05b..8b8a2d1d7 100644 --- a/handlers/metrics_test.go +++ b/handlers/metrics_test.go @@ -169,9 +169,9 @@ func TestMetricsBounties(t *testing.T) { assert.Equal(t, http.StatusOK, rr.Code) assert.Equal(t, res[0].BountyId, uint(1)) - assert.Equal(t, res[0].Bounty.OwnerID, "owner-1") - assert.Equal(t, res[0].Bounty.Price, uint(100)) - assert.Equal(t, res[0].Bounty.Title, "bounty 1") + assert.Equal(t, res[0].OwnerID, "owner-1") + assert.Equal(t, res[0].Price, uint(100)) + assert.Equal(t, res[0].Title, "bounty 1") assert.Equal(t, res[0].BountyDescription, "test bounty") assert.Equal(t, res[0].BountyCreated, int64(1112)) }) @@ -230,7 +230,7 @@ func TestMetricsBounties(t *testing.T) { // Assert that the response contains bounties only from the selected providers for _, bounty := range res { - assert.Contains(t, []string{"provider1", "provider2"}, bounty.Bounty.OwnerID) + assert.Contains(t, []string{"provider1", "provider2"}, bounty.OwnerID) } }) } From 377c27f6ca0de5699b12c212a78315927c02b18c Mon Sep 17 00:00:00 2001 From: elraphty Date: Wed, 1 May 2024 16:11:22 +0100 Subject: [PATCH 07/44] added workspace to admin --- db/metrics.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/db/metrics.go b/db/metrics.go index b2a91bc94..666bf8d57 100644 --- a/db/metrics.go +++ b/db/metrics.go @@ -162,9 +162,11 @@ func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) assingned := keys.Get("Assigned") paid := keys.Get("Paid") providers := keys.Get("provider") + workspace := keys.Get("workspace_uuid") orderQuery := "" limitQuery := "" + workspaceQuery := "" var statusConditions []string @@ -193,6 +195,9 @@ func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) if limit > 1 { limitQuery = fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset) } + if workspace != "" { + workspaceQuery = fmt.Sprintf("AND workspace_uuid = %s", workspace) + } providerCondition := "" if len(providers) > 0 { @@ -201,7 +206,7 @@ func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) } query := `SELECT * FROM public.bounty WHERE created >= '` + r.StartDate + `' AND created <= '` + r.EndDate + `'` + providerCondition - allQuery := query + " " + statusQuery + " " + orderQuery + " " + limitQuery + allQuery := query + " " + workspaceQuery + " " + statusQuery + " " + orderQuery + " " + limitQuery b := []NewBounty{} db.db.Raw(allQuery).Find(&b) @@ -214,6 +219,7 @@ func (db database) GetBountiesByDateRangeCount(r PaymentDateRange, re *http.Requ assingned := keys.Get("Assigned") paid := keys.Get("Paid") providers := keys.Get("provider") + workspace := keys.Get("workspace_uuid") var statusConditions []string @@ -239,11 +245,15 @@ func (db database) GetBountiesByDateRangeCount(r PaymentDateRange, re *http.Requ providerSlice := strings.Split(providers, ",") providerCondition = " AND owner_id IN ('" + strings.Join(providerSlice, "','") + "')" } + var workspaceQuery string + if workspace != "" { + workspaceQuery = fmt.Sprintf("AND workspace_uuid = %s", workspace) + } var count int64 query := `SELECT COUNT(*) FROM public.bounty WHERE created >= '` + r.StartDate + `' AND created <= '` + r.EndDate + `'` + providerCondition - allQuery := query + " " + statusQuery + allQuery := query + " " + workspaceQuery + " " + statusQuery db.db.Raw(allQuery).Scan(&count) return count } From 4dac8d016f6f93f30e2a5039f6a5c77ce674a9c9 Mon Sep 17 00:00:00 2001 From: elraphty Date: Wed, 1 May 2024 18:56:22 +0100 Subject: [PATCH 08/44] added workspace for payment and bounty metrics --- db/interface.go | 20 ++-- db/metrics.go | 144 +++++++++++++++++++-------- handlers/metrics.go | 26 +++-- handlers/metrics_test.go | 24 +++-- mocks/Database.go | 210 ++++++++++++++++++++------------------- 5 files changed, 250 insertions(+), 174 deletions(-) diff --git a/db/interface.go b/db/interface.go index 9a8b88112..c1a714a0e 100644 --- a/db/interface.go +++ b/db/interface.go @@ -122,16 +122,16 @@ type Database interface { DeleteAllUsersFromWorkspace(uuid string) error GetFilterStatusCount() FilterStattuCount UserHasManageBountyRoles(pubKeyFromAuth string, uuid string) bool - BountiesPaidPercentage(r PaymentDateRange) uint - TotalSatsPosted(r PaymentDateRange) uint - TotalSatsPaid(r PaymentDateRange) uint - SatsPaidPercentage(r PaymentDateRange) uint - AveragePaidTime(r PaymentDateRange) uint - AverageCompletedTime(r PaymentDateRange) uint - TotalBountiesPosted(r PaymentDateRange) int64 - TotalPaidBounties(r PaymentDateRange) int64 - NewHuntersPaid(r PaymentDateRange) int64 - TotalHuntersPaid(r PaymentDateRange) int64 + BountiesPaidPercentage(r PaymentDateRange, workspace string) uint + TotalSatsPosted(r PaymentDateRange, workspace string) uint + TotalSatsPaid(r PaymentDateRange, workspace string) uint + SatsPaidPercentage(r PaymentDateRange, workspace string) uint + AveragePaidTime(r PaymentDateRange, workspace string) uint + AverageCompletedTime(r PaymentDateRange, workspace string) uint + TotalBountiesPosted(r PaymentDateRange, workspace string) int64 + TotalPaidBounties(r PaymentDateRange, workspace string) int64 + NewHuntersPaid(r PaymentDateRange, workspace string) int64 + TotalHuntersPaid(r PaymentDateRange, workspace string) int64 GetPersonByPubkey(pubkey string) Person GetBountiesByDateRange(r PaymentDateRange, re *http.Request) []NewBounty GetBountiesByDateRangeCount(r PaymentDateRange, re *http.Request) int64 diff --git a/db/metrics.go b/db/metrics.go index 666bf8d57..f0914fd91 100644 --- a/db/metrics.go +++ b/db/metrics.go @@ -23,27 +23,45 @@ func (db database) TotalWorkspacesByDateRange(r PaymentDateRange) int64 { return count } -func (db database) TotalPaymentsByDateRange(r PaymentDateRange) uint { +func (db database) TotalPaymentsByDateRange(r PaymentDateRange, workspace string) uint { var sum uint - db.db.Model(&PaymentHistory{}).Where("payment_type = ?", r.PaymentType).Where("created >= ?", r.StartDate).Where("created <= ?", r.EndDate).Select("SUM(amount)").Row().Scan(&sum) + query := db.db.Model(&NewPaymentHistory{}).Where("payment_type = ?", r.PaymentType).Where("created >= ?", r.StartDate).Where("created <= ?", r.EndDate) + + if workspace != "" { + query.Where("workspace_uuid", workspace) + } + + query.Select("SUM(amount)").Row().Scan(&sum) return sum } -func (db database) TotalSatsPosted(r PaymentDateRange) uint { +func (db database) TotalSatsPosted(r PaymentDateRange, workspace string) uint { var sum uint - db.db.Model(&Bounty{}).Where("created >= ?", r.StartDate).Where("created <= ?", r.EndDate).Select("SUM(price)").Row().Scan(&sum) + query := db.db.Model(&NewBounty{}).Where("created >= ?", r.StartDate).Where("created <= ?", r.EndDate) + + if workspace != "" { + query.Where("workspace_uuid", workspace) + } + + query.Select("SUM(price)").Row().Scan(&sum) return sum } -func (db database) TotalSatsPaid(r PaymentDateRange) uint { +func (db database) TotalSatsPaid(r PaymentDateRange, workspace string) uint { var sum uint - db.db.Model(&Bounty{}).Where("paid = ?", true).Where("created >= ?", r.StartDate).Where("created <= ?", r.EndDate).Select("SUM(price)").Row().Scan(&sum) + query := db.db.Model(&NewBounty{}).Where("paid = ?", true).Where("created >= ?", r.StartDate).Where("created <= ?", r.EndDate) + + if workspace != "" { + query.Where("workspace_uuid", workspace) + } + + query.Select("SUM(price)").Row().Scan(&sum) return sum } -func (db database) SatsPaidPercentage(r PaymentDateRange) uint { - satsPosted := DB.TotalSatsPosted(r) - satsPaid := DB.TotalSatsPaid(r) +func (db database) SatsPaidPercentage(r PaymentDateRange, workspace string) uint { + satsPosted := DB.TotalSatsPosted(r, workspace) + satsPaid := DB.TotalSatsPaid(r, workspace) if satsPaid != 0 && satsPosted != 0 { value := (satsPaid * 100) / satsPosted paidPercentage := math.Round(float64(value)) @@ -52,43 +70,69 @@ func (db database) SatsPaidPercentage(r PaymentDateRange) uint { return 0 } -func (db database) TotalPaidBounties(r PaymentDateRange) int64 { +func (db database) TotalPaidBounties(r PaymentDateRange, workspace string) int64 { var count int64 - db.db.Model(&Bounty{}).Where("paid = ?", true).Where("created >= ?", r.StartDate).Where("created <= ?", r.EndDate).Count(&count) + query := db.db.Model(&NewBounty{}).Where("paid = ?", true).Where("created >= ?", r.StartDate).Where("created <= ?", r.EndDate) + + if workspace != "" { + query.Where("workspace_uuid", workspace) + } + + query.Count(&count) return count } -func (db database) TotalHuntersPaid(r PaymentDateRange) int64 { +func (db database) TotalHuntersPaid(r PaymentDateRange, workspace string) int64 { var count int64 query := fmt.Sprintf(`SELECT COUNT(DISTINCT assignee) FROM bounty WHERE assignee !='' AND paid=true AND created >= %s AND created <= %s`, r.StartDate, r.EndDate) - db.db.Raw(query).Count(&count) + var workspaceQuery string + if workspace != "" { + workspaceQuery = fmt.Sprintf("AND workspace_uuid = %s", workspace) + } + + allQuery := query + " " + workspaceQuery + db.db.Raw(allQuery).Count(&count) return count } -func (db database) NewHuntersPaid(r PaymentDateRange) int64 { +func (db database) NewHuntersPaid(r PaymentDateRange, workspace string) int64 { var count int64 - db.db.Model(&Bounty{}). + + query := db.db.Model(&NewBounty{}). Select("DISTINCT assignee"). Where("paid = true"). - Where("created >= ?", r.StartDate).Where("created <= ?", r.EndDate). - Not("assignee IN (?)", db.db.Model(&Bounty{}). - Select("assignee"). - Where("paid = true"). - Where("created < ?", r.StartDate), - ).Count(&count) + Where("created >= ?", r.StartDate).Where("created <= ?", r.EndDate) + + if workspace != "" { + query.Where("workspace_uuid", workspace) + } + + query.Not("assignee IN (?)", db.db.Model(&NewBounty{}). + Select("assignee"). + Where("paid = true"). + Where("created < ?", r.StartDate), + ) + + query.Count(&count) return count } -func (db database) TotalBountiesPosted(r PaymentDateRange) int64 { +func (db database) TotalBountiesPosted(r PaymentDateRange, workspace string) int64 { var count int64 - db.db.Model(&Bounty{}).Where("created >= ?", r.StartDate).Where("created <= ?", r.EndDate).Count(&count) + query := db.db.Model(&Bounty{}).Where("created >= ?", r.StartDate).Where("created <= ?", r.EndDate) + + if workspace != "" { + query.Where("workspace_uuid", workspace) + } + + query.Count(&count) return count } -func (db database) BountiesPaidPercentage(r PaymentDateRange) uint { - bountiesPosted := DB.TotalBountiesPosted(r) - bountiesPaid := DB.TotalPaidBounties(r) +func (db database) BountiesPaidPercentage(r PaymentDateRange, workspace string) uint { + bountiesPosted := DB.TotalBountiesPosted(r, workspace) + bountiesPaid := DB.TotalPaidBounties(r, workspace) if bountiesPaid != 0 && bountiesPosted != 0 { value := bountiesPaid * 100 / bountiesPosted paidPercentage := math.Round(float64(value)) @@ -97,23 +141,31 @@ func (db database) BountiesPaidPercentage(r PaymentDateRange) uint { return 0 } -func (db database) PaidDifference(r PaymentDateRange) []DateDifference { +func (db database) PaidDifference(r PaymentDateRange, workspace string) []DateDifference { ms := []DateDifference{} - db.db.Raw(`SELECT EXTRACT(EPOCH FROM (paid_date - TO_TIMESTAMP(created))) as diff FROM public.bounty WHERE paid_date IS NOT NULL AND created >= '` + r.StartDate + `' AND created <= '` + r.EndDate + `' `).Find(&ms) + query := fmt.Sprintf("SELECT EXTRACT(EPOCH FROM (paid_date - TO_TIMESTAMP(created))) as diff FROM public.bounty WHERE paid_date IS NOT NULL AND created >= %s AND created <= %s", "`"+r.StartDate+"`", "`"+r.EndDate+"`") + + var workspaceQuery string + if workspace != "" { + workspaceQuery = fmt.Sprintf("AND workspace_uuid = %s", workspace) + } + + allQuery := query + " " + workspaceQuery + db.db.Raw(allQuery).Find(&ms) return ms } -func (db database) PaidDifferenceCount(r PaymentDateRange) int64 { +func (db database) PaidDifferenceCount(r PaymentDateRange, workspace string) int64 { var count int64 - list := db.PaidDifference(r) + list := db.PaidDifference(r, workspace) count = int64(len(list)) return count } -func (db database) AveragePaidTime(r PaymentDateRange) uint { - paidList := DB.PaidDifference(r) - paidCount := DB.PaidDifferenceCount(r) +func (db database) AveragePaidTime(r PaymentDateRange, workspace string) uint { + paidList := DB.PaidDifference(r, workspace) + paidCount := DB.PaidDifferenceCount(r, workspace) var paidSum uint for _, diff := range paidList { paidSum = uint(math.Round(diff.Diff)) @@ -121,23 +173,31 @@ func (db database) AveragePaidTime(r PaymentDateRange) uint { return CalculateAverageDays(paidCount, paidSum) } -func (db database) CompletedDifference(r PaymentDateRange) []DateDifference { +func (db database) CompletedDifference(r PaymentDateRange, workspace string) []DateDifference { ms := []DateDifference{} - db.db.Raw(`SELECT EXTRACT(EPOCH FROM (completion_date - TO_TIMESTAMP(created))) as diff FROM public.bounty WHERE completion_date IS NOT NULL AND created >= '` + r.StartDate + `' AND created <= '` + r.EndDate + `' `).Find(&ms) + query := fmt.Sprintf("SELECT EXTRACT(EPOCH FROM (completion_date - TO_TIMESTAMP(created))) as diff FROM public.bounty WHERE completion_date IS NOT NULL AND created >= %s AND created <= %s ", "`"+r.StartDate+"`", "`"+r.EndDate+"`") + + var workspaceQuery string + if workspace != "" { + workspaceQuery = fmt.Sprintf("AND workspace_uuid = %s", workspace) + } + + allQuery := query + " " + workspaceQuery + db.db.Raw(allQuery).Find(&ms) return ms } -func (db database) CompletedDifferenceCount(r PaymentDateRange) int64 { +func (db database) CompletedDifferenceCount(r PaymentDateRange, workspace string) int64 { var count int64 - list := db.CompletedDifference(r) + list := db.CompletedDifference(r, workspace) count = int64(len(list)) return count } -func (db database) AverageCompletedTime(r PaymentDateRange) uint { - paidList := DB.CompletedDifference(r) - paidCount := DB.CompletedDifferenceCount(r) +func (db database) AverageCompletedTime(r PaymentDateRange, workspace string) uint { + paidList := DB.CompletedDifference(r, workspace) + paidCount := DB.CompletedDifferenceCount(r, workspace) var paidSum uint for _, diff := range paidList { paidSum = uint(math.Round(diff.Diff)) @@ -162,7 +222,7 @@ func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) assingned := keys.Get("Assigned") paid := keys.Get("Paid") providers := keys.Get("provider") - workspace := keys.Get("workspace_uuid") + workspace := keys.Get("workspace") orderQuery := "" limitQuery := "" @@ -219,7 +279,7 @@ func (db database) GetBountiesByDateRangeCount(r PaymentDateRange, re *http.Requ assingned := keys.Get("Assigned") paid := keys.Get("Paid") providers := keys.Get("provider") - workspace := keys.Get("workspace_uuid") + workspace := keys.Get("workspace") var statusConditions []string diff --git a/handlers/metrics.go b/handlers/metrics.go index 431ceda55..df7e99513 100644 --- a/handlers/metrics.go +++ b/handlers/metrics.go @@ -33,6 +33,8 @@ func NewMetricHandler(db db.Database) *metricHandler { func PaymentMetrics(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) + keys := r.URL.Query() + workspace := keys.Get("workspace") if pubKeyFromAuth == "" { fmt.Println("no pubkey from auth") @@ -51,7 +53,7 @@ func PaymentMetrics(w http.ResponseWriter, r *http.Request) { return } - sumAmount := db.DB.TotalPaymentsByDateRange(request) + sumAmount := db.DB.TotalPaymentsByDateRange(request, workspace) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(sumAmount) @@ -114,6 +116,8 @@ func PeopleMetrics(w http.ResponseWriter, r *http.Request) { func (mh *metricHandler) BountyMetrics(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) + keys := r.URL.Query() + workspace := keys.Get("workspace") if pubKeyFromAuth == "" { fmt.Println("no pubkey from auth") @@ -146,16 +150,16 @@ func (mh *metricHandler) BountyMetrics(w http.ResponseWriter, r *http.Request) { } } - totalBountiesPosted := mh.db.TotalBountiesPosted(request) - totalBountiesPaid := mh.db.TotalPaidBounties(request) - bountiesPaidPercentage := mh.db.BountiesPaidPercentage(request) - totalSatsPosted := mh.db.TotalSatsPosted(request) - totalSatsPaid := mh.db.TotalSatsPaid(request) - satsPaidPercentage := mh.db.SatsPaidPercentage(request) - avgPaidDays := mh.db.AveragePaidTime(request) - avgCompletedDays := mh.db.AverageCompletedTime(request) - uniqueHuntersPaid := mh.db.TotalHuntersPaid(request) - newHuntersPaid := mh.db.NewHuntersPaid(request) + totalBountiesPosted := mh.db.TotalBountiesPosted(request, workspace) + totalBountiesPaid := mh.db.TotalPaidBounties(request, workspace) + bountiesPaidPercentage := mh.db.BountiesPaidPercentage(request, workspace) + totalSatsPosted := mh.db.TotalSatsPosted(request, workspace) + totalSatsPaid := mh.db.TotalSatsPaid(request, workspace) + satsPaidPercentage := mh.db.SatsPaidPercentage(request, workspace) + avgPaidDays := mh.db.AveragePaidTime(request, workspace) + avgCompletedDays := mh.db.AverageCompletedTime(request, workspace) + uniqueHuntersPaid := mh.db.TotalHuntersPaid(request, workspace) + newHuntersPaid := mh.db.NewHuntersPaid(request, workspace) bountyMetrics := db.BountyMetrics{ BountiesPosted: totalBountiesPosted, diff --git a/handlers/metrics_test.go b/handlers/metrics_test.go index 8b8a2d1d7..f9a2ee619 100644 --- a/handlers/metrics_test.go +++ b/handlers/metrics_test.go @@ -58,25 +58,27 @@ func TestBountyMetrics(t *testing.T) { db.RedisError = errors.New("redis not initialized") rr := httptest.NewRecorder() handler := http.HandlerFunc(mh.BountyMetrics) + workspace := "test-workspace" + dateRange := db.PaymentDateRange{ StartDate: "1111", EndDate: "2222", } body, _ := json.Marshal(dateRange) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, "/bounty_stats", bytes.NewReader(body)) + req, err := http.NewRequestWithContext(ctx, http.MethodPost, "/bounty_stats?workspace="+workspace, bytes.NewReader(body)) if err != nil { t.Fatal(err) } - mockDb.On("TotalBountiesPosted", dateRange).Return(int64(1)).Once() - mockDb.On("TotalPaidBounties", dateRange).Return(int64(1)).Once() - mockDb.On("BountiesPaidPercentage", dateRange).Return(uint(1)).Once() - mockDb.On("TotalSatsPosted", dateRange).Return(uint(1)).Once() - mockDb.On("TotalSatsPaid", dateRange).Return(uint(1)).Once() - mockDb.On("SatsPaidPercentage", dateRange).Return(uint(1)).Once() - mockDb.On("AveragePaidTime", dateRange).Return(uint(1)).Once() - mockDb.On("AverageCompletedTime", dateRange).Return(uint(1)).Once() - mockDb.On("TotalHuntersPaid", dateRange).Return(int64(1)).Once() - mockDb.On("NewHuntersPaid", dateRange).Return(int64(1)).Once() + mockDb.On("TotalBountiesPosted", dateRange, workspace).Return(int64(1)).Once() + mockDb.On("TotalPaidBounties", dateRange, workspace).Return(int64(1)).Once() + mockDb.On("BountiesPaidPercentage", dateRange, workspace).Return(uint(1)).Once() + mockDb.On("TotalSatsPosted", dateRange, workspace).Return(uint(1)).Once() + mockDb.On("TotalSatsPaid", dateRange, workspace).Return(uint(1)).Once() + mockDb.On("SatsPaidPercentage", dateRange, workspace).Return(uint(1)).Once() + mockDb.On("AveragePaidTime", dateRange, workspace).Return(uint(1)).Once() + mockDb.On("AverageCompletedTime", dateRange, workspace).Return(uint(1)).Once() + mockDb.On("TotalHuntersPaid", dateRange, workspace).Return(int64(1)).Once() + mockDb.On("NewHuntersPaid", dateRange, workspace).Return(int64(1)).Once() handler.ServeHTTP(rr, req) expectedMetricRes := db.BountyMetrics{ diff --git a/mocks/Database.go b/mocks/Database.go index 7b3961287..152ef1159 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -311,17 +311,17 @@ func (_c *Database_AddUserInvoiceData_Call) RunAndReturn(run func(db.UserInvoice return _c } -// AverageCompletedTime provides a mock function with given fields: r -func (_m *Database) AverageCompletedTime(r db.PaymentDateRange) uint { - ret := _m.Called(r) +// AverageCompletedTime provides a mock function with given fields: r, workspace +func (_m *Database) AverageCompletedTime(r db.PaymentDateRange, workspace string) uint { + ret := _m.Called(r, workspace) if len(ret) == 0 { panic("no return value specified for AverageCompletedTime") } var r0 uint - if rf, ok := ret.Get(0).(func(db.PaymentDateRange) uint); ok { - r0 = rf(r) + if rf, ok := ret.Get(0).(func(db.PaymentDateRange, string) uint); ok { + r0 = rf(r, workspace) } else { r0 = ret.Get(0).(uint) } @@ -336,13 +336,14 @@ type Database_AverageCompletedTime_Call struct { // AverageCompletedTime is a helper method to define mock.On call // - r db.PaymentDateRange -func (_e *Database_Expecter) AverageCompletedTime(r interface{}) *Database_AverageCompletedTime_Call { - return &Database_AverageCompletedTime_Call{Call: _e.mock.On("AverageCompletedTime", r)} +// - workspace string +func (_e *Database_Expecter) AverageCompletedTime(r interface{}, workspace interface{}) *Database_AverageCompletedTime_Call { + return &Database_AverageCompletedTime_Call{Call: _e.mock.On("AverageCompletedTime", r, workspace)} } -func (_c *Database_AverageCompletedTime_Call) Run(run func(r db.PaymentDateRange)) *Database_AverageCompletedTime_Call { +func (_c *Database_AverageCompletedTime_Call) Run(run func(r db.PaymentDateRange, workspace string)) *Database_AverageCompletedTime_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(db.PaymentDateRange)) + run(args[0].(db.PaymentDateRange), args[1].(string)) }) return _c } @@ -352,22 +353,22 @@ func (_c *Database_AverageCompletedTime_Call) Return(_a0 uint) *Database_Average return _c } -func (_c *Database_AverageCompletedTime_Call) RunAndReturn(run func(db.PaymentDateRange) uint) *Database_AverageCompletedTime_Call { +func (_c *Database_AverageCompletedTime_Call) RunAndReturn(run func(db.PaymentDateRange, string) uint) *Database_AverageCompletedTime_Call { _c.Call.Return(run) return _c } -// AveragePaidTime provides a mock function with given fields: r -func (_m *Database) AveragePaidTime(r db.PaymentDateRange) uint { - ret := _m.Called(r) +// AveragePaidTime provides a mock function with given fields: r, workspace +func (_m *Database) AveragePaidTime(r db.PaymentDateRange, workspace string) uint { + ret := _m.Called(r, workspace) if len(ret) == 0 { panic("no return value specified for AveragePaidTime") } var r0 uint - if rf, ok := ret.Get(0).(func(db.PaymentDateRange) uint); ok { - r0 = rf(r) + if rf, ok := ret.Get(0).(func(db.PaymentDateRange, string) uint); ok { + r0 = rf(r, workspace) } else { r0 = ret.Get(0).(uint) } @@ -382,13 +383,14 @@ type Database_AveragePaidTime_Call struct { // AveragePaidTime is a helper method to define mock.On call // - r db.PaymentDateRange -func (_e *Database_Expecter) AveragePaidTime(r interface{}) *Database_AveragePaidTime_Call { - return &Database_AveragePaidTime_Call{Call: _e.mock.On("AveragePaidTime", r)} +// - workspace string +func (_e *Database_Expecter) AveragePaidTime(r interface{}, workspace interface{}) *Database_AveragePaidTime_Call { + return &Database_AveragePaidTime_Call{Call: _e.mock.On("AveragePaidTime", r, workspace)} } -func (_c *Database_AveragePaidTime_Call) Run(run func(r db.PaymentDateRange)) *Database_AveragePaidTime_Call { +func (_c *Database_AveragePaidTime_Call) Run(run func(r db.PaymentDateRange, workspace string)) *Database_AveragePaidTime_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(db.PaymentDateRange)) + run(args[0].(db.PaymentDateRange), args[1].(string)) }) return _c } @@ -398,22 +400,22 @@ func (_c *Database_AveragePaidTime_Call) Return(_a0 uint) *Database_AveragePaidT return _c } -func (_c *Database_AveragePaidTime_Call) RunAndReturn(run func(db.PaymentDateRange) uint) *Database_AveragePaidTime_Call { +func (_c *Database_AveragePaidTime_Call) RunAndReturn(run func(db.PaymentDateRange, string) uint) *Database_AveragePaidTime_Call { _c.Call.Return(run) return _c } -// BountiesPaidPercentage provides a mock function with given fields: r -func (_m *Database) BountiesPaidPercentage(r db.PaymentDateRange) uint { - ret := _m.Called(r) +// BountiesPaidPercentage provides a mock function with given fields: r, workspace +func (_m *Database) BountiesPaidPercentage(r db.PaymentDateRange, workspace string) uint { + ret := _m.Called(r, workspace) if len(ret) == 0 { panic("no return value specified for BountiesPaidPercentage") } var r0 uint - if rf, ok := ret.Get(0).(func(db.PaymentDateRange) uint); ok { - r0 = rf(r) + if rf, ok := ret.Get(0).(func(db.PaymentDateRange, string) uint); ok { + r0 = rf(r, workspace) } else { r0 = ret.Get(0).(uint) } @@ -428,13 +430,14 @@ type Database_BountiesPaidPercentage_Call struct { // BountiesPaidPercentage is a helper method to define mock.On call // - r db.PaymentDateRange -func (_e *Database_Expecter) BountiesPaidPercentage(r interface{}) *Database_BountiesPaidPercentage_Call { - return &Database_BountiesPaidPercentage_Call{Call: _e.mock.On("BountiesPaidPercentage", r)} +// - workspace string +func (_e *Database_Expecter) BountiesPaidPercentage(r interface{}, workspace interface{}) *Database_BountiesPaidPercentage_Call { + return &Database_BountiesPaidPercentage_Call{Call: _e.mock.On("BountiesPaidPercentage", r, workspace)} } -func (_c *Database_BountiesPaidPercentage_Call) Run(run func(r db.PaymentDateRange)) *Database_BountiesPaidPercentage_Call { +func (_c *Database_BountiesPaidPercentage_Call) Run(run func(r db.PaymentDateRange, workspace string)) *Database_BountiesPaidPercentage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(db.PaymentDateRange)) + run(args[0].(db.PaymentDateRange), args[1].(string)) }) return _c } @@ -444,7 +447,7 @@ func (_c *Database_BountiesPaidPercentage_Call) Return(_a0 uint) *Database_Bount return _c } -func (_c *Database_BountiesPaidPercentage_Call) RunAndReturn(run func(db.PaymentDateRange) uint) *Database_BountiesPaidPercentage_Call { +func (_c *Database_BountiesPaidPercentage_Call) RunAndReturn(run func(db.PaymentDateRange, string) uint) *Database_BountiesPaidPercentage_Call { _c.Call.Return(run) return _c } @@ -5035,17 +5038,17 @@ func (_c *Database_GetWorkspacesCount_Call) RunAndReturn(run func() int64) *Data return _c } -// NewHuntersPaid provides a mock function with given fields: r -func (_m *Database) NewHuntersPaid(r db.PaymentDateRange) int64 { - ret := _m.Called(r) +// NewHuntersPaid provides a mock function with given fields: r, workspace +func (_m *Database) NewHuntersPaid(r db.PaymentDateRange, workspace string) int64 { + ret := _m.Called(r, workspace) if len(ret) == 0 { panic("no return value specified for NewHuntersPaid") } var r0 int64 - if rf, ok := ret.Get(0).(func(db.PaymentDateRange) int64); ok { - r0 = rf(r) + if rf, ok := ret.Get(0).(func(db.PaymentDateRange, string) int64); ok { + r0 = rf(r, workspace) } else { r0 = ret.Get(0).(int64) } @@ -5060,13 +5063,14 @@ type Database_NewHuntersPaid_Call struct { // NewHuntersPaid is a helper method to define mock.On call // - r db.PaymentDateRange -func (_e *Database_Expecter) NewHuntersPaid(r interface{}) *Database_NewHuntersPaid_Call { - return &Database_NewHuntersPaid_Call{Call: _e.mock.On("NewHuntersPaid", r)} +// - workspace string +func (_e *Database_Expecter) NewHuntersPaid(r interface{}, workspace interface{}) *Database_NewHuntersPaid_Call { + return &Database_NewHuntersPaid_Call{Call: _e.mock.On("NewHuntersPaid", r, workspace)} } -func (_c *Database_NewHuntersPaid_Call) Run(run func(r db.PaymentDateRange)) *Database_NewHuntersPaid_Call { +func (_c *Database_NewHuntersPaid_Call) Run(run func(r db.PaymentDateRange, workspace string)) *Database_NewHuntersPaid_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(db.PaymentDateRange)) + run(args[0].(db.PaymentDateRange), args[1].(string)) }) return _c } @@ -5076,7 +5080,7 @@ func (_c *Database_NewHuntersPaid_Call) Return(_a0 int64) *Database_NewHuntersPa return _c } -func (_c *Database_NewHuntersPaid_Call) RunAndReturn(run func(db.PaymentDateRange) int64) *Database_NewHuntersPaid_Call { +func (_c *Database_NewHuntersPaid_Call) RunAndReturn(run func(db.PaymentDateRange, string) int64) *Database_NewHuntersPaid_Call { _c.Call.Return(run) return _c } @@ -5170,17 +5174,17 @@ func (_c *Database_ProcessAlerts_Call) RunAndReturn(run func(db.Person)) *Databa return _c } -// SatsPaidPercentage provides a mock function with given fields: r -func (_m *Database) SatsPaidPercentage(r db.PaymentDateRange) uint { - ret := _m.Called(r) +// SatsPaidPercentage provides a mock function with given fields: r, workspace +func (_m *Database) SatsPaidPercentage(r db.PaymentDateRange, workspace string) uint { + ret := _m.Called(r, workspace) if len(ret) == 0 { panic("no return value specified for SatsPaidPercentage") } var r0 uint - if rf, ok := ret.Get(0).(func(db.PaymentDateRange) uint); ok { - r0 = rf(r) + if rf, ok := ret.Get(0).(func(db.PaymentDateRange, string) uint); ok { + r0 = rf(r, workspace) } else { r0 = ret.Get(0).(uint) } @@ -5195,13 +5199,14 @@ type Database_SatsPaidPercentage_Call struct { // SatsPaidPercentage is a helper method to define mock.On call // - r db.PaymentDateRange -func (_e *Database_Expecter) SatsPaidPercentage(r interface{}) *Database_SatsPaidPercentage_Call { - return &Database_SatsPaidPercentage_Call{Call: _e.mock.On("SatsPaidPercentage", r)} +// - workspace string +func (_e *Database_Expecter) SatsPaidPercentage(r interface{}, workspace interface{}) *Database_SatsPaidPercentage_Call { + return &Database_SatsPaidPercentage_Call{Call: _e.mock.On("SatsPaidPercentage", r, workspace)} } -func (_c *Database_SatsPaidPercentage_Call) Run(run func(r db.PaymentDateRange)) *Database_SatsPaidPercentage_Call { +func (_c *Database_SatsPaidPercentage_Call) Run(run func(r db.PaymentDateRange, workspace string)) *Database_SatsPaidPercentage_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(db.PaymentDateRange)) + run(args[0].(db.PaymentDateRange), args[1].(string)) }) return _c } @@ -5211,7 +5216,7 @@ func (_c *Database_SatsPaidPercentage_Call) Return(_a0 uint) *Database_SatsPaidP return _c } -func (_c *Database_SatsPaidPercentage_Call) RunAndReturn(run func(db.PaymentDateRange) uint) *Database_SatsPaidPercentage_Call { +func (_c *Database_SatsPaidPercentage_Call) RunAndReturn(run func(db.PaymentDateRange, string) uint) *Database_SatsPaidPercentage_Call { _c.Call.Return(run) return _c } @@ -5364,17 +5369,17 @@ func (_c *Database_SearchTribes_Call) RunAndReturn(run func(string) []db.Tribe) return _c } -// TotalBountiesPosted provides a mock function with given fields: r -func (_m *Database) TotalBountiesPosted(r db.PaymentDateRange) int64 { - ret := _m.Called(r) +// TotalBountiesPosted provides a mock function with given fields: r, workspace +func (_m *Database) TotalBountiesPosted(r db.PaymentDateRange, workspace string) int64 { + ret := _m.Called(r, workspace) if len(ret) == 0 { panic("no return value specified for TotalBountiesPosted") } var r0 int64 - if rf, ok := ret.Get(0).(func(db.PaymentDateRange) int64); ok { - r0 = rf(r) + if rf, ok := ret.Get(0).(func(db.PaymentDateRange, string) int64); ok { + r0 = rf(r, workspace) } else { r0 = ret.Get(0).(int64) } @@ -5389,13 +5394,14 @@ type Database_TotalBountiesPosted_Call struct { // TotalBountiesPosted is a helper method to define mock.On call // - r db.PaymentDateRange -func (_e *Database_Expecter) TotalBountiesPosted(r interface{}) *Database_TotalBountiesPosted_Call { - return &Database_TotalBountiesPosted_Call{Call: _e.mock.On("TotalBountiesPosted", r)} +// - workspace string +func (_e *Database_Expecter) TotalBountiesPosted(r interface{}, workspace interface{}) *Database_TotalBountiesPosted_Call { + return &Database_TotalBountiesPosted_Call{Call: _e.mock.On("TotalBountiesPosted", r, workspace)} } -func (_c *Database_TotalBountiesPosted_Call) Run(run func(r db.PaymentDateRange)) *Database_TotalBountiesPosted_Call { +func (_c *Database_TotalBountiesPosted_Call) Run(run func(r db.PaymentDateRange, workspace string)) *Database_TotalBountiesPosted_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(db.PaymentDateRange)) + run(args[0].(db.PaymentDateRange), args[1].(string)) }) return _c } @@ -5405,22 +5411,22 @@ func (_c *Database_TotalBountiesPosted_Call) Return(_a0 int64) *Database_TotalBo return _c } -func (_c *Database_TotalBountiesPosted_Call) RunAndReturn(run func(db.PaymentDateRange) int64) *Database_TotalBountiesPosted_Call { +func (_c *Database_TotalBountiesPosted_Call) RunAndReturn(run func(db.PaymentDateRange, string) int64) *Database_TotalBountiesPosted_Call { _c.Call.Return(run) return _c } -// TotalHuntersPaid provides a mock function with given fields: r -func (_m *Database) TotalHuntersPaid(r db.PaymentDateRange) int64 { - ret := _m.Called(r) +// TotalHuntersPaid provides a mock function with given fields: r, workspace +func (_m *Database) TotalHuntersPaid(r db.PaymentDateRange, workspace string) int64 { + ret := _m.Called(r, workspace) if len(ret) == 0 { panic("no return value specified for TotalHuntersPaid") } var r0 int64 - if rf, ok := ret.Get(0).(func(db.PaymentDateRange) int64); ok { - r0 = rf(r) + if rf, ok := ret.Get(0).(func(db.PaymentDateRange, string) int64); ok { + r0 = rf(r, workspace) } else { r0 = ret.Get(0).(int64) } @@ -5435,13 +5441,14 @@ type Database_TotalHuntersPaid_Call struct { // TotalHuntersPaid is a helper method to define mock.On call // - r db.PaymentDateRange -func (_e *Database_Expecter) TotalHuntersPaid(r interface{}) *Database_TotalHuntersPaid_Call { - return &Database_TotalHuntersPaid_Call{Call: _e.mock.On("TotalHuntersPaid", r)} +// - workspace string +func (_e *Database_Expecter) TotalHuntersPaid(r interface{}, workspace interface{}) *Database_TotalHuntersPaid_Call { + return &Database_TotalHuntersPaid_Call{Call: _e.mock.On("TotalHuntersPaid", r, workspace)} } -func (_c *Database_TotalHuntersPaid_Call) Run(run func(r db.PaymentDateRange)) *Database_TotalHuntersPaid_Call { +func (_c *Database_TotalHuntersPaid_Call) Run(run func(r db.PaymentDateRange, workspace string)) *Database_TotalHuntersPaid_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(db.PaymentDateRange)) + run(args[0].(db.PaymentDateRange), args[1].(string)) }) return _c } @@ -5451,22 +5458,22 @@ func (_c *Database_TotalHuntersPaid_Call) Return(_a0 int64) *Database_TotalHunte return _c } -func (_c *Database_TotalHuntersPaid_Call) RunAndReturn(run func(db.PaymentDateRange) int64) *Database_TotalHuntersPaid_Call { +func (_c *Database_TotalHuntersPaid_Call) RunAndReturn(run func(db.PaymentDateRange, string) int64) *Database_TotalHuntersPaid_Call { _c.Call.Return(run) return _c } -// TotalPaidBounties provides a mock function with given fields: r -func (_m *Database) TotalPaidBounties(r db.PaymentDateRange) int64 { - ret := _m.Called(r) +// TotalPaidBounties provides a mock function with given fields: r, workspace +func (_m *Database) TotalPaidBounties(r db.PaymentDateRange, workspace string) int64 { + ret := _m.Called(r, workspace) if len(ret) == 0 { panic("no return value specified for TotalPaidBounties") } var r0 int64 - if rf, ok := ret.Get(0).(func(db.PaymentDateRange) int64); ok { - r0 = rf(r) + if rf, ok := ret.Get(0).(func(db.PaymentDateRange, string) int64); ok { + r0 = rf(r, workspace) } else { r0 = ret.Get(0).(int64) } @@ -5481,13 +5488,14 @@ type Database_TotalPaidBounties_Call struct { // TotalPaidBounties is a helper method to define mock.On call // - r db.PaymentDateRange -func (_e *Database_Expecter) TotalPaidBounties(r interface{}) *Database_TotalPaidBounties_Call { - return &Database_TotalPaidBounties_Call{Call: _e.mock.On("TotalPaidBounties", r)} +// - workspace string +func (_e *Database_Expecter) TotalPaidBounties(r interface{}, workspace interface{}) *Database_TotalPaidBounties_Call { + return &Database_TotalPaidBounties_Call{Call: _e.mock.On("TotalPaidBounties", r, workspace)} } -func (_c *Database_TotalPaidBounties_Call) Run(run func(r db.PaymentDateRange)) *Database_TotalPaidBounties_Call { +func (_c *Database_TotalPaidBounties_Call) Run(run func(r db.PaymentDateRange, workspace string)) *Database_TotalPaidBounties_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(db.PaymentDateRange)) + run(args[0].(db.PaymentDateRange), args[1].(string)) }) return _c } @@ -5497,22 +5505,22 @@ func (_c *Database_TotalPaidBounties_Call) Return(_a0 int64) *Database_TotalPaid return _c } -func (_c *Database_TotalPaidBounties_Call) RunAndReturn(run func(db.PaymentDateRange) int64) *Database_TotalPaidBounties_Call { +func (_c *Database_TotalPaidBounties_Call) RunAndReturn(run func(db.PaymentDateRange, string) int64) *Database_TotalPaidBounties_Call { _c.Call.Return(run) return _c } -// TotalSatsPaid provides a mock function with given fields: r -func (_m *Database) TotalSatsPaid(r db.PaymentDateRange) uint { - ret := _m.Called(r) +// TotalSatsPaid provides a mock function with given fields: r, workspace +func (_m *Database) TotalSatsPaid(r db.PaymentDateRange, workspace string) uint { + ret := _m.Called(r, workspace) if len(ret) == 0 { panic("no return value specified for TotalSatsPaid") } var r0 uint - if rf, ok := ret.Get(0).(func(db.PaymentDateRange) uint); ok { - r0 = rf(r) + if rf, ok := ret.Get(0).(func(db.PaymentDateRange, string) uint); ok { + r0 = rf(r, workspace) } else { r0 = ret.Get(0).(uint) } @@ -5527,13 +5535,14 @@ type Database_TotalSatsPaid_Call struct { // TotalSatsPaid is a helper method to define mock.On call // - r db.PaymentDateRange -func (_e *Database_Expecter) TotalSatsPaid(r interface{}) *Database_TotalSatsPaid_Call { - return &Database_TotalSatsPaid_Call{Call: _e.mock.On("TotalSatsPaid", r)} +// - workspace string +func (_e *Database_Expecter) TotalSatsPaid(r interface{}, workspace interface{}) *Database_TotalSatsPaid_Call { + return &Database_TotalSatsPaid_Call{Call: _e.mock.On("TotalSatsPaid", r, workspace)} } -func (_c *Database_TotalSatsPaid_Call) Run(run func(r db.PaymentDateRange)) *Database_TotalSatsPaid_Call { +func (_c *Database_TotalSatsPaid_Call) Run(run func(r db.PaymentDateRange, workspace string)) *Database_TotalSatsPaid_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(db.PaymentDateRange)) + run(args[0].(db.PaymentDateRange), args[1].(string)) }) return _c } @@ -5543,22 +5552,22 @@ func (_c *Database_TotalSatsPaid_Call) Return(_a0 uint) *Database_TotalSatsPaid_ return _c } -func (_c *Database_TotalSatsPaid_Call) RunAndReturn(run func(db.PaymentDateRange) uint) *Database_TotalSatsPaid_Call { +func (_c *Database_TotalSatsPaid_Call) RunAndReturn(run func(db.PaymentDateRange, string) uint) *Database_TotalSatsPaid_Call { _c.Call.Return(run) return _c } -// TotalSatsPosted provides a mock function with given fields: r -func (_m *Database) TotalSatsPosted(r db.PaymentDateRange) uint { - ret := _m.Called(r) +// TotalSatsPosted provides a mock function with given fields: r, workspace +func (_m *Database) TotalSatsPosted(r db.PaymentDateRange, workspace string) uint { + ret := _m.Called(r, workspace) if len(ret) == 0 { panic("no return value specified for TotalSatsPosted") } var r0 uint - if rf, ok := ret.Get(0).(func(db.PaymentDateRange) uint); ok { - r0 = rf(r) + if rf, ok := ret.Get(0).(func(db.PaymentDateRange, string) uint); ok { + r0 = rf(r, workspace) } else { r0 = ret.Get(0).(uint) } @@ -5573,13 +5582,14 @@ type Database_TotalSatsPosted_Call struct { // TotalSatsPosted is a helper method to define mock.On call // - r db.PaymentDateRange -func (_e *Database_Expecter) TotalSatsPosted(r interface{}) *Database_TotalSatsPosted_Call { - return &Database_TotalSatsPosted_Call{Call: _e.mock.On("TotalSatsPosted", r)} +// - workspace string +func (_e *Database_Expecter) TotalSatsPosted(r interface{}, workspace interface{}) *Database_TotalSatsPosted_Call { + return &Database_TotalSatsPosted_Call{Call: _e.mock.On("TotalSatsPosted", r, workspace)} } -func (_c *Database_TotalSatsPosted_Call) Run(run func(r db.PaymentDateRange)) *Database_TotalSatsPosted_Call { +func (_c *Database_TotalSatsPosted_Call) Run(run func(r db.PaymentDateRange, workspace string)) *Database_TotalSatsPosted_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(db.PaymentDateRange)) + run(args[0].(db.PaymentDateRange), args[1].(string)) }) return _c } @@ -5589,7 +5599,7 @@ func (_c *Database_TotalSatsPosted_Call) Return(_a0 uint) *Database_TotalSatsPos return _c } -func (_c *Database_TotalSatsPosted_Call) RunAndReturn(run func(db.PaymentDateRange) uint) *Database_TotalSatsPosted_Call { +func (_c *Database_TotalSatsPosted_Call) RunAndReturn(run func(db.PaymentDateRange, string) uint) *Database_TotalSatsPosted_Call { _c.Call.Return(run) return _c } From 298a07bb4fbfa315da61fc7ab77ef388dbf895b2 Mon Sep 17 00:00:00 2001 From: Abdul Wahab Date: Thu, 2 May 2024 16:04:37 +0500 Subject: [PATCH 09/44] 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 ff72560bfdd6def8113ef1470cfc2ba7bef928df Mon Sep 17 00:00:00 2001 From: elraphty Date: Thu, 2 May 2024 16:22:45 +0100 Subject: [PATCH 10/44] updated workspace for admin --- db/workspaces.go | 9 +++++++-- handlers/metrics.go | 15 +++++++++++++++ routes/metrics.go | 2 ++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/db/workspaces.go b/db/workspaces.go index f737bdd31..be9bb403c 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -13,8 +13,13 @@ func (db database) GetWorkspaces(r *http.Request) []Workspace { ms := []Workspace{} offset, limit, sortBy, direction, search := utils.GetPaginationParams(r) - // return if like owner_alias, unique_name, or equals pubkey - db.db.Offset(offset).Limit(limit).Order(sortBy+" "+direction+" ").Where("LOWER(name) LIKE ?", "%"+search+"%").Where("deleted != ?", false).Find(&ms) + query := db.db.Model(&ms).Where("LOWER(name) LIKE ?", "%"+search+"%").Where("deleted != ?", true) + + if limit > 1 { + query.Offset(offset).Limit(limit).Order(sortBy + " " + direction + " ") + } + + query.Find(&ms) return ms } diff --git a/handlers/metrics.go b/handlers/metrics.go index df7e99513..46f2b3c44 100644 --- a/handlers/metrics.go +++ b/handlers/metrics.go @@ -304,6 +304,21 @@ func MetricsCsv(w http.ResponseWriter, r *http.Request) { } } +func GetAdminWorkspaces(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 + } + + workspaces := db.DB.GetWorkspaces(r) + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(workspaces) +} + func (mh *metricHandler) GetMetricsBountiesData(metricBounties []db.NewBounty) []db.BountyData { var metricBountiesData []db.BountyData for _, bounty := range metricBounties { diff --git a/routes/metrics.go b/routes/metrics.go index e24a1386d..19065e175 100644 --- a/routes/metrics.go +++ b/routes/metrics.go @@ -13,6 +13,8 @@ func MetricsRoutes() chi.Router { r.Group(func(r chi.Router) { r.Use(auth.PubKeyContextSuperAdmin) + r.Get("/workspaces", handlers.GetAdminWorkspaces) + r.Post("/payment", handlers.PaymentMetrics) r.Post("/people", handlers.PeopleMetrics) r.Post("/organization", handlers.WorkspacetMetrics) From 30081336f1c90b8884e950cc60aab7608401cc94 Mon Sep 17 00:00:00 2001 From: Abdul Wahab Date: Thu, 2 May 2024 21:46:34 +0500 Subject: [PATCH 11/44] 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 { From 1861bf53a6c363d2f463a4b2effc20938eb29cc4 Mon Sep 17 00:00:00 2001 From: elraphty Date: Thu, 2 May 2024 19:10:40 +0100 Subject: [PATCH 12/44] changes --- db/metrics.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/db/metrics.go b/db/metrics.go index f0914fd91..1de6350ee 100644 --- a/db/metrics.go +++ b/db/metrics.go @@ -88,7 +88,7 @@ func (db database) TotalHuntersPaid(r PaymentDateRange, workspace string) int64 var workspaceQuery string if workspace != "" { - workspaceQuery = fmt.Sprintf("AND workspace_uuid = %s", workspace) + workspaceQuery = fmt.Sprintf("AND workspace_uuid = '%s'", workspace) } allQuery := query + " " + workspaceQuery @@ -148,7 +148,7 @@ func (db database) PaidDifference(r PaymentDateRange, workspace string) []DateDi var workspaceQuery string if workspace != "" { - workspaceQuery = fmt.Sprintf("AND workspace_uuid = %s", workspace) + workspaceQuery = fmt.Sprintf("AND workspace_uuid = `%s`", workspace) } allQuery := query + " " + workspaceQuery @@ -180,7 +180,7 @@ func (db database) CompletedDifference(r PaymentDateRange, workspace string) []D var workspaceQuery string if workspace != "" { - workspaceQuery = fmt.Sprintf("AND workspace_uuid = %s", workspace) + workspaceQuery = fmt.Sprintf("AND workspace_uuid = `%s`", workspace) } allQuery := query + " " + workspaceQuery @@ -224,6 +224,8 @@ func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) providers := keys.Get("provider") workspace := keys.Get("workspace") + fmt.Println("WORSKPACE === workspace", workspace) + orderQuery := "" limitQuery := "" workspaceQuery := "" @@ -256,7 +258,7 @@ func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) limitQuery = fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset) } if workspace != "" { - workspaceQuery = fmt.Sprintf("AND workspace_uuid = %s", workspace) + workspaceQuery = fmt.Sprintf("AND workspace_uuid = '%s'", workspace) } providerCondition := "" @@ -270,6 +272,11 @@ func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) b := []NewBounty{} db.db.Raw(allQuery).Find(&b) + + if workspace != "" { + fmt.Println("Bounties1 ===", b) + fmt.Println("Query1 ===", allQuery) + } return b } @@ -307,7 +314,7 @@ func (db database) GetBountiesByDateRangeCount(r PaymentDateRange, re *http.Requ } var workspaceQuery string if workspace != "" { - workspaceQuery = fmt.Sprintf("AND workspace_uuid = %s", workspace) + workspaceQuery = fmt.Sprintf("AND workspace_uuid = '%s'", workspace) } var count int64 From b8d2b1f6c242317abd42755de189d64cc743fce8 Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 3 May 2024 11:28:04 +0100 Subject: [PATCH 13/44] added assignee stats --- db/interface.go | 1 + db/metrics.go | 26 +++++++++++++++---------- db/structs.go | 1 + handlers/metrics.go | 2 ++ mocks/Database.go | 47 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 10 deletions(-) diff --git a/db/interface.go b/db/interface.go index c1a714a0e..05d328654 100644 --- a/db/interface.go +++ b/db/interface.go @@ -130,6 +130,7 @@ type Database interface { AverageCompletedTime(r PaymentDateRange, workspace string) uint TotalBountiesPosted(r PaymentDateRange, workspace string) int64 TotalPaidBounties(r PaymentDateRange, workspace string) int64 + TotalAssignedBounties(r PaymentDateRange, workspace string) int64 NewHuntersPaid(r PaymentDateRange, workspace string) int64 TotalHuntersPaid(r PaymentDateRange, workspace string) int64 GetPersonByPubkey(pubkey string) Person diff --git a/db/metrics.go b/db/metrics.go index 1de6350ee..0fd676c2b 100644 --- a/db/metrics.go +++ b/db/metrics.go @@ -82,6 +82,18 @@ func (db database) TotalPaidBounties(r PaymentDateRange, workspace string) int64 return count } +func (db database) TotalAssignedBounties(r PaymentDateRange, workspace string) int64 { + var count int64 + query := db.db.Model(&NewBounty{}).Where("assignee != ''").Where("paid = ?", false).Where("created >= ?", r.StartDate).Where("created <= ?", r.EndDate) + + if workspace != "" { + query.Where("workspace_uuid", workspace) + } + + query.Count(&count) + return count +} + func (db database) TotalHuntersPaid(r PaymentDateRange, workspace string) int64 { var count int64 query := fmt.Sprintf(`SELECT COUNT(DISTINCT assignee) FROM bounty WHERE assignee !='' AND paid=true AND created >= %s AND created <= %s`, r.StartDate, r.EndDate) @@ -144,11 +156,11 @@ func (db database) BountiesPaidPercentage(r PaymentDateRange, workspace string) func (db database) PaidDifference(r PaymentDateRange, workspace string) []DateDifference { ms := []DateDifference{} - query := fmt.Sprintf("SELECT EXTRACT(EPOCH FROM (paid_date - TO_TIMESTAMP(created))) as diff FROM public.bounty WHERE paid_date IS NOT NULL AND created >= %s AND created <= %s", "`"+r.StartDate+"`", "`"+r.EndDate+"`") + query := fmt.Sprintf("SELECT EXTRACT(EPOCH FROM (paid_date - TO_TIMESTAMP(created))) as diff FROM public.bounty WHERE paid_date IS NOT NULL AND created >= %s AND created <= %s", r.StartDate, r.EndDate) var workspaceQuery string if workspace != "" { - workspaceQuery = fmt.Sprintf("AND workspace_uuid = `%s`", workspace) + workspaceQuery = fmt.Sprintf("AND workspace_uuid = '%s'", workspace) } allQuery := query + " " + workspaceQuery @@ -176,11 +188,11 @@ func (db database) AveragePaidTime(r PaymentDateRange, workspace string) uint { func (db database) CompletedDifference(r PaymentDateRange, workspace string) []DateDifference { ms := []DateDifference{} - query := fmt.Sprintf("SELECT EXTRACT(EPOCH FROM (completion_date - TO_TIMESTAMP(created))) as diff FROM public.bounty WHERE completion_date IS NOT NULL AND created >= %s AND created <= %s ", "`"+r.StartDate+"`", "`"+r.EndDate+"`") + query := fmt.Sprintf("SELECT EXTRACT(EPOCH FROM (completion_date - TO_TIMESTAMP(created))) as diff FROM public.bounty WHERE completion_date IS NOT NULL AND created >= %s AND created <= %s", r.StartDate, r.EndDate) var workspaceQuery string if workspace != "" { - workspaceQuery = fmt.Sprintf("AND workspace_uuid = `%s`", workspace) + workspaceQuery = fmt.Sprintf("AND workspace_uuid = '%s'", workspace) } allQuery := query + " " + workspaceQuery @@ -224,8 +236,6 @@ func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) providers := keys.Get("provider") workspace := keys.Get("workspace") - fmt.Println("WORSKPACE === workspace", workspace) - orderQuery := "" limitQuery := "" workspaceQuery := "" @@ -273,10 +283,6 @@ func (db database) GetBountiesByDateRange(r PaymentDateRange, re *http.Request) b := []NewBounty{} db.db.Raw(allQuery).Find(&b) - if workspace != "" { - fmt.Println("Bounties1 ===", b) - fmt.Println("Query1 ===", allQuery) - } return b } diff --git a/db/structs.go b/db/structs.go index 637e9c295..ebcfe0e2e 100644 --- a/db/structs.go +++ b/db/structs.go @@ -800,6 +800,7 @@ type DateDifference struct { type BountyMetrics struct { BountiesPosted int64 `json:"bounties_posted"` BountiesPaid int64 `json:"bounties_paid"` + BountiesAssigned int64 `json:"bounties_assigned"` BountiesPaidPercentage uint `json:"bounties_paid_average"` SatsPosted uint `json:"sats_posted"` SatsPaid uint `json:"sats_paid"` diff --git a/handlers/metrics.go b/handlers/metrics.go index 46f2b3c44..27f1378b7 100644 --- a/handlers/metrics.go +++ b/handlers/metrics.go @@ -152,6 +152,7 @@ func (mh *metricHandler) BountyMetrics(w http.ResponseWriter, r *http.Request) { totalBountiesPosted := mh.db.TotalBountiesPosted(request, workspace) totalBountiesPaid := mh.db.TotalPaidBounties(request, workspace) + totalBountiesAssigned := mh.db.TotalAssignedBounties(request, workspace) bountiesPaidPercentage := mh.db.BountiesPaidPercentage(request, workspace) totalSatsPosted := mh.db.TotalSatsPosted(request, workspace) totalSatsPaid := mh.db.TotalSatsPaid(request, workspace) @@ -164,6 +165,7 @@ func (mh *metricHandler) BountyMetrics(w http.ResponseWriter, r *http.Request) { bountyMetrics := db.BountyMetrics{ BountiesPosted: totalBountiesPosted, BountiesPaid: totalBountiesPaid, + BountiesAssigned: totalBountiesAssigned, BountiesPaidPercentage: bountiesPaidPercentage, SatsPosted: totalSatsPosted, SatsPaid: totalSatsPaid, diff --git a/mocks/Database.go b/mocks/Database.go index 152ef1159..8c727b024 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -5369,6 +5369,53 @@ func (_c *Database_SearchTribes_Call) RunAndReturn(run func(string) []db.Tribe) return _c } +// TotalAssignedBounties provides a mock function with given fields: r, workspace +func (_m *Database) TotalAssignedBounties(r db.PaymentDateRange, workspace string) int64 { + ret := _m.Called(r, workspace) + + if len(ret) == 0 { + panic("no return value specified for TotalAssignedBounties") + } + + var r0 int64 + if rf, ok := ret.Get(0).(func(db.PaymentDateRange, string) int64); ok { + r0 = rf(r, workspace) + } else { + r0 = ret.Get(0).(int64) + } + + return r0 +} + +// Database_TotalAssignedBounties_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TotalAssignedBounties' +type Database_TotalAssignedBounties_Call struct { + *mock.Call +} + +// TotalAssignedBounties is a helper method to define mock.On call +// - r db.PaymentDateRange +// - workspace string +func (_e *Database_Expecter) TotalAssignedBounties(r interface{}, workspace interface{}) *Database_TotalAssignedBounties_Call { + return &Database_TotalAssignedBounties_Call{Call: _e.mock.On("TotalAssignedBounties", r, workspace)} +} + +func (_c *Database_TotalAssignedBounties_Call) Run(run func(r db.PaymentDateRange, workspace string)) *Database_TotalAssignedBounties_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(db.PaymentDateRange), args[1].(string)) + }) + return _c +} + +func (_c *Database_TotalAssignedBounties_Call) Return(_a0 int64) *Database_TotalAssignedBounties_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_TotalAssignedBounties_Call) RunAndReturn(run func(db.PaymentDateRange, string) int64) *Database_TotalAssignedBounties_Call { + _c.Call.Return(run) + return _c +} + // TotalBountiesPosted provides a mock function with given fields: r, workspace func (_m *Database) TotalBountiesPosted(r db.PaymentDateRange, workspace string) int64 { ret := _m.Called(r, workspace) From 1438e4475cbc3597c763e02d83bbd10e42c9cead Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 3 May 2024 13:26:01 +0100 Subject: [PATCH 14/44] fixed tests --- handlers/metrics_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/handlers/metrics_test.go b/handlers/metrics_test.go index f9a2ee619..d278831a4 100644 --- a/handlers/metrics_test.go +++ b/handlers/metrics_test.go @@ -71,6 +71,7 @@ func TestBountyMetrics(t *testing.T) { } mockDb.On("TotalBountiesPosted", dateRange, workspace).Return(int64(1)).Once() mockDb.On("TotalPaidBounties", dateRange, workspace).Return(int64(1)).Once() + mockDb.On("TotalAssignedBounties", dateRange, workspace).Return(int64(2)).Once() mockDb.On("BountiesPaidPercentage", dateRange, workspace).Return(uint(1)).Once() mockDb.On("TotalSatsPosted", dateRange, workspace).Return(uint(1)).Once() mockDb.On("TotalSatsPaid", dateRange, workspace).Return(uint(1)).Once() @@ -84,6 +85,7 @@ func TestBountyMetrics(t *testing.T) { expectedMetricRes := db.BountyMetrics{ BountiesPosted: 1, BountiesPaid: 1, + BountiesAssigned: 2, BountiesPaidPercentage: 1, SatsPosted: 1, SatsPaid: 1, From 97b8b4b7a51ec900f8ab3a5b91b32072821d6ac0 Mon Sep 17 00:00:00 2001 From: Mirza Hanan Date: Fri, 3 May 2024 23:28:09 +0500 Subject: [PATCH 15/44] Add Endpoints for Workspace Repositories --- cypress/e2e/02_repositories.cy.ts | 6 +- cypress/support/objects/objects.ts | 2 +- db/config.go | 1 + db/interface.go | 2 + db/structs.go | 12 ++++ db/workspaces.go | 24 +++++++ handlers/workspaces.go | 57 ++++++++++++++++ mocks/Database.go | 104 +++++++++++++++++++++++++++++ routes/workspaces.go | 3 + 9 files changed, 208 insertions(+), 3 deletions(-) diff --git a/cypress/e2e/02_repositories.cy.ts b/cypress/e2e/02_repositories.cy.ts index f3484ba1b..dba204270 100644 --- a/cypress/e2e/02_repositories.cy.ts +++ b/cypress/e2e/02_repositories.cy.ts @@ -10,8 +10,10 @@ describe('Create Repositories for Workspace', () => { url: `${HostName}/workspaces/repositories`, headers: { 'x-jwt': `${value}` }, body: Repositories[i] - }).its('body').should('have.property', 'name', Repositories[i].name.trim()) - .its('body').should('have.property', 'url', Repositories[i].url.trim()); + }).its('body').then(body => { + expect(body).to.have.property('name').and.equal(Repositories[i].name.trim()); + expect(body).to.have.property('url').and.equal(Repositories[i].url.trim()); + }); } }) }) diff --git a/cypress/support/objects/objects.ts b/cypress/support/objects/objects.ts index bfae6de06..e1a2f7328 100644 --- a/cypress/support/objects/objects.ts +++ b/cypress/support/objects/objects.ts @@ -63,7 +63,7 @@ export const Repositories = [ url: ' https://github.com/stakwork/sphinx-tribes-frontend ' }, { - uuid: 'com1t3gn1e4a4qu3tnlg', + uuid: 'com1t3gn1e4a4qu3thss', workspace_uuid: 'cohob00n1e4808utqel0', name: ' backend ', url: ' https://github.com/stakwork/sphinx-tribes ' diff --git a/db/config.go b/db/config.go index 4b50a4502..547ca97a1 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(&WorkspaceRepositories{}) DB.MigrateTablesWithOrgUuid() DB.MigrateOrganizationToWorkspace() diff --git a/db/interface.go b/db/interface.go index e3cc95163..691c737dd 100644 --- a/db/interface.go +++ b/db/interface.go @@ -139,4 +139,6 @@ type Database interface { PersonUniqueNameFromName(name string) (string, error) ProcessAlerts(p Person) UserHasAccess(pubKeyFromAuth string, uuid string, role string) bool + CreateWorkspaceRepository(m WorkspaceRepositories) (WorkspaceRepositories, error) + GetWorkspaceRepositorByWorkspaceUuid(uuid string) []WorkspaceRepositories } diff --git a/db/structs.go b/db/structs.go index e6a25a7de..c4aba003e 100644 --- a/db/structs.go +++ b/db/structs.go @@ -547,6 +547,18 @@ type WorkspaceUsersData struct { Person } +type WorkspaceRepositories struct { + ID uint `json:"id"` + Uuid string `json:"uuid"` + WorkspaceUuid string `json:"workspace_uuid"` + Name string `json:"name"` + Url string `json:"url"` + 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/db/workspaces.go b/db/workspaces.go index d1e77b596..406be7d01 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "strings" "time" "github.com/stakwork/sphinx-tribes/utils" @@ -52,6 +53,29 @@ func (db database) CreateOrEditWorkspace(m Workspace) (Workspace, error) { return m, nil } +func (db database) CreateWorkspaceRepository(m WorkspaceRepositories) (WorkspaceRepositories, error) { + m.Name = strings.TrimSpace(m.Name) + m.Url = strings.TrimSpace(m.Url) + + 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) + } + + return m, nil +} + +func (db database) GetWorkspaceRepositorByWorkspaceUuid(uuid string) []WorkspaceRepositories { + ms := []WorkspaceRepositories{} + + db.db.Model(&WorkspaceRepositories{}).Where("workspace_uuid = ?", uuid).Order("Created").Find(&ms) + + return ms +} + func (db database) GetWorkspaceUsers(uuid string) ([]WorkspaceUsersData, error) { ms := []WorkspaceUsersData{} diff --git a/handlers/workspaces.go b/handlers/workspaces.go index 0b152c96d..5c8138d11 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -795,3 +795,60 @@ func (oh *workspaceHandler) UpdateWorkspace(w http.ResponseWriter, r *http.Reque w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(p) } + +func (oh *workspaceHandler) CreateWorkspaceRepository(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 + } + + workspaceRepo := db.WorkspaceRepositories{} + body, _ := io.ReadAll(r.Body) + r.Body.Close() + err := json.Unmarshal(body, &workspaceRepo) + + if err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusNotAcceptable) + return + } + + workspaceRepo.CreatedBy = pubKeyFromAuth + + // Validate struct data + err = db.Validate.Struct(workspaceRepo) + 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.CreateWorkspaceRepository(workspaceRepo) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(p) +} + +func (oh *workspaceHandler) GetWorkspaceRepositorByWorkspaceUuid(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.GetWorkspaceRepositorByWorkspaceUuid(uuid) + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(workspaceFeatures) +} diff --git a/mocks/Database.go b/mocks/Database.go index 784a2492a..aeff38189 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -1191,6 +1191,62 @@ func (_c *Database_CreateWorkspaceBudget_Call) RunAndReturn(run func(db.NewBount return _c } +// CreateWorkspaceRepository provides a mock function with given fields: m +func (_m *Database) CreateWorkspaceRepository(m db.WorkspaceRepositories) (db.WorkspaceRepositories, error) { + ret := _m.Called(m) + + if len(ret) == 0 { + panic("no return value specified for CreateWorkspaceRepository") + } + + var r0 db.WorkspaceRepositories + var r1 error + if rf, ok := ret.Get(0).(func(db.WorkspaceRepositories) (db.WorkspaceRepositories, error)); ok { + return rf(m) + } + if rf, ok := ret.Get(0).(func(db.WorkspaceRepositories) db.WorkspaceRepositories); ok { + r0 = rf(m) + } else { + r0 = ret.Get(0).(db.WorkspaceRepositories) + } + + if rf, ok := ret.Get(1).(func(db.WorkspaceRepositories) error); ok { + r1 = rf(m) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Database_CreateWorkspaceRepository_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateWorkspaceRepository' +type Database_CreateWorkspaceRepository_Call struct { + *mock.Call +} + +// CreateWorkspaceRepository is a helper method to define mock.On call +// - m db.WorkspaceRepositories +func (_e *Database_Expecter) CreateWorkspaceRepository(m interface{}) *Database_CreateWorkspaceRepository_Call { + return &Database_CreateWorkspaceRepository_Call{Call: _e.mock.On("CreateWorkspaceRepository", m)} +} + +func (_c *Database_CreateWorkspaceRepository_Call) Run(run func(m db.WorkspaceRepositories)) *Database_CreateWorkspaceRepository_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(db.WorkspaceRepositories)) + }) + return _c +} + +func (_c *Database_CreateWorkspaceRepository_Call) Return(_a0 db.WorkspaceRepositories, _a1 error) *Database_CreateWorkspaceRepository_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Database_CreateWorkspaceRepository_Call) RunAndReturn(run func(db.WorkspaceRepositories) (db.WorkspaceRepositories, error)) *Database_CreateWorkspaceRepository_Call { + _c.Call.Return(run) + return _c +} + // CreateWorkspaceUser provides a mock function with given fields: orgUser func (_m *Database) CreateWorkspaceUser(orgUser db.WorkspaceUsers) db.WorkspaceUsers { ret := _m.Called(orgUser) @@ -4745,6 +4801,54 @@ func (_c *Database_GetWorkspaceInvoicesCount_Call) RunAndReturn(run func(string) return _c } +// GetWorkspaceRepositorByWorkspaceUuid provides a mock function with given fields: uuid +func (_m *Database) GetWorkspaceRepositorByWorkspaceUuid(uuid string) []db.WorkspaceRepositories { + ret := _m.Called(uuid) + + if len(ret) == 0 { + panic("no return value specified for GetWorkspaceRepositorByWorkspaceUuid") + } + + var r0 []db.WorkspaceRepositories + if rf, ok := ret.Get(0).(func(string) []db.WorkspaceRepositories); ok { + r0 = rf(uuid) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.WorkspaceRepositories) + } + } + + return r0 +} + +// Database_GetWorkspaceRepositorByWorkspaceUuid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWorkspaceRepositorByWorkspaceUuid' +type Database_GetWorkspaceRepositorByWorkspaceUuid_Call struct { + *mock.Call +} + +// GetWorkspaceRepositorByWorkspaceUuid is a helper method to define mock.On call +// - uuid string +func (_e *Database_Expecter) GetWorkspaceRepositorByWorkspaceUuid(uuid interface{}) *Database_GetWorkspaceRepositorByWorkspaceUuid_Call { + return &Database_GetWorkspaceRepositorByWorkspaceUuid_Call{Call: _e.mock.On("GetWorkspaceRepositorByWorkspaceUuid", uuid)} +} + +func (_c *Database_GetWorkspaceRepositorByWorkspaceUuid_Call) Run(run func(uuid string)) *Database_GetWorkspaceRepositorByWorkspaceUuid_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Database_GetWorkspaceRepositorByWorkspaceUuid_Call) Return(_a0 []db.WorkspaceRepositories) *Database_GetWorkspaceRepositorByWorkspaceUuid_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_GetWorkspaceRepositorByWorkspaceUuid_Call) RunAndReturn(run func(string) []db.WorkspaceRepositories) *Database_GetWorkspaceRepositorByWorkspaceUuid_Call { + _c.Call.Return(run) + return _c +} + // GetWorkspaceStatusBudget provides a mock function with given fields: workspace_uuid func (_m *Database) GetWorkspaceStatusBudget(workspace_uuid string) db.StatusBudget { ret := _m.Called(workspace_uuid) diff --git a/routes/workspaces.go b/routes/workspaces.go index d79a457bd..d02c6e956 100644 --- a/routes/workspaces.go +++ b/routes/workspaces.go @@ -42,6 +42,9 @@ func WorkspaceRoutes() chi.Router { r.Post("/mission", workspaceHandlers.UpdateWorkspace) r.Post("/tactics", workspaceHandlers.UpdateWorkspace) r.Post("/schematicurl", workspaceHandlers.UpdateWorkspace) + + r.Post("/repositories", workspaceHandlers.CreateWorkspaceRepository) + r.Get("/repositories/{uuid}", workspaceHandlers.GetWorkspaceRepositorByWorkspaceUuid) }) return r } From 66008d92a182058d574d48b65e7e4908d7f2563b Mon Sep 17 00:00:00 2001 From: elraphty Date: Tue, 7 May 2024 14:51:56 +0100 Subject: [PATCH 16/44] fixeed payment history --- db/structs.go | 32 ++++++++++++++++---------------- handlers/workspaces.go | 10 +++++----- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/db/structs.go b/db/structs.go index 8cefb2bbf..ddae676fc 100644 --- a/db/structs.go +++ b/db/structs.go @@ -552,17 +552,17 @@ type WorkspaceUsersData struct { } 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"` + 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 { @@ -689,11 +689,11 @@ type NewPaymentHistory struct { } type PaymentHistoryData struct { - PaymentHistory NewPaymentHistory - SenderName string `json:"sender_name"` - ReceiverName string `json:"receiver_name"` - SenderImg string `json:"sender_img"` - ReceiverImg string `json:"receiver_img"` + NewPaymentHistory + SenderName string `json:"sender_name"` + ReceiverName string `json:"receiver_name"` + SenderImg string `json:"sender_img"` + ReceiverImg string `json:"receiver_img"` } type PaymentData struct { diff --git a/handlers/workspaces.go b/handlers/workspaces.go index 0b152c96d..bcd365154 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -640,11 +640,11 @@ func GetPaymentHistory(w http.ResponseWriter, r *http.Request) { sender := db.DB.GetPersonByPubkey(payment.SenderPubKey) receiver := db.DB.GetPersonByPubkey(payment.ReceiverPubKey) paymentData := db.PaymentHistoryData{ - PaymentHistory: payment, - SenderName: sender.UniqueName, - SenderImg: sender.Img, - ReceiverName: receiver.UniqueName, - ReceiverImg: receiver.Img, + NewPaymentHistory: payment, + SenderName: sender.UniqueName, + SenderImg: sender.Img, + ReceiverName: receiver.UniqueName, + ReceiverImg: receiver.Img, } paymentHistoryData = append(paymentHistoryData, paymentData) } From bbbbe461d48c547a92d2c1b4d3600f6dee4464ae Mon Sep 17 00:00:00 2001 From: elraphty Date: Tue, 7 May 2024 17:33:56 +0100 Subject: [PATCH 17/44] fixed bounty budgets addition --- db/workspaces.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/db/workspaces.go b/db/workspaces.go index be9bb403c..224b344ea 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -146,42 +146,41 @@ func (db database) UpdateWorkspaceBudget(budget NewBountyBudget) NewBountyBudget func (db database) GetPaymentHistoryByCreated(created *time.Time, workspace_uuid string) NewPaymentHistory { ms := NewPaymentHistory{} - db.db.Where("created = ?", created).Where("workspace_uuid = ? ", workspace_uuid).Find(&ms) + db.db.Model(&NewPaymentHistory{}).Where("created = ?", created).Where("workspace_uuid = ? ", workspace_uuid).Find(&ms) return ms } func (db database) GetWorkspaceBudget(workspace_uuid string) NewBountyBudget { ms := NewBountyBudget{} - db.db.Where("workspace_uuid = ?", workspace_uuid).Find(&ms) + db.db.Model(&NewBountyBudget{}).Where("workspace_uuid = ?", workspace_uuid).Find(&ms) return ms } func (db database) GetWorkspaceStatusBudget(workspace_uuid string) StatusBudget { - orgBudget := db.GetWorkspaceBudget(workspace_uuid) var openBudget uint - db.db.Model(&Bounty{}).Where("assignee = '' ").Where("paid != true").Select("SUM(price)").Row().Scan(&openBudget) + db.db.Model(&NewBounty{}).Where("assignee = '' ").Where("paid != true").Select("SUM(price)").Row().Scan(&openBudget) var openCount int64 - db.db.Model(&Bounty{}).Where("assignee = '' ").Where("paid != true").Count(&openCount) + db.db.Model(&NewBounty{}).Where("assignee = '' ").Where("paid != true").Count(&openCount) var openDifference int = int(orgBudget.TotalBudget - openBudget) var assignedBudget uint - db.db.Model(&Bounty{}).Where("assignee != '' ").Where("paid != true").Select("SUM(price)").Row().Scan(&assignedBudget) + db.db.Model(&NewBounty{}).Where("assignee != '' ").Where("paid != true").Select("SUM(price)").Row().Scan(&assignedBudget) var assignedCount int64 - db.db.Model(&Bounty{}).Where("assignee != '' ").Where("paid != true").Count(&assignedCount) + db.db.Model(&NewBounty{}).Where("assignee != '' ").Where("paid != true").Count(&assignedCount) var assignedDifference int = int(orgBudget.TotalBudget - assignedBudget) var completedBudget uint - db.db.Model(&Bounty{}).Where("completed = true ").Where("paid != true").Select("SUM(price)").Row().Scan(&completedBudget) + db.db.Model(&NewBounty{}).Where("completed = true ").Where("paid != true").Select("SUM(price)").Row().Scan(&completedBudget) var completedCount int64 - db.db.Model(&Bounty{}).Where("completed = true ").Where("paid != true").Count(&completedCount) + db.db.Model(&NewBounty{}).Where("completed = true ").Where("paid != true").Count(&completedCount) var completedDifference int = int(orgBudget.TotalBudget - completedBudget) @@ -223,7 +222,7 @@ func (db database) AddAndUpdateBudget(invoice NewInvoiceList) NewPaymentHistory // get Workspace budget and add payment to total budget WorkspaceBudget := db.GetWorkspaceBudget(workspace_uuid) - if WorkspaceBudget.OrgUuid == "" { + if WorkspaceBudget.WorkspaceUuid == "" { now := time.Now() orgBudget := NewBountyBudget{ WorkspaceUuid: workspace_uuid, From 39bf36b0a009792799e649ab37095336f76827ae Mon Sep 17 00:00:00 2001 From: elraphty Date: Tue, 7 May 2024 21:17:23 +0100 Subject: [PATCH 18/44] workspace --- db/workspaces.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/workspaces.go b/db/workspaces.go index 224b344ea..2349f4b4b 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -138,7 +138,7 @@ func (db database) CreateWorkspaceBudget(budget NewBountyBudget) NewBountyBudget } func (db database) UpdateWorkspaceBudget(budget NewBountyBudget) NewBountyBudget { - db.db.Model(&NewBountyBudget{}).Where("workspace_uuid = ?", budget.OrgUuid).Updates(map[string]interface{}{ + db.db.Model(&NewBountyBudget{}).Where("workspace_uuid = ?", budget.WorkspaceUuid).Updates(map[string]interface{}{ "total_budget": budget.TotalBudget, }) return budget From ee9f22fdecb9d78ecbba22e4a23835bbc7ad1333 Mon Sep 17 00:00:00 2001 From: elraphty Date: Tue, 7 May 2024 21:34:40 +0100 Subject: [PATCH 19/44] rename org --- db/workspaces.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/db/workspaces.go b/db/workspaces.go index 2349f4b4b..48731ce32 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -224,13 +224,13 @@ func (db database) AddAndUpdateBudget(invoice NewInvoiceList) NewPaymentHistory if WorkspaceBudget.WorkspaceUuid == "" { now := time.Now() - orgBudget := NewBountyBudget{ + workBudget := NewBountyBudget{ WorkspaceUuid: workspace_uuid, TotalBudget: paymentHistory.Amount, Created: &now, Updated: &now, } - db.CreateWorkspaceBudget(orgBudget) + db.CreateWorkspaceBudget(workBudget) } else { totalBudget := WorkspaceBudget.TotalBudget WorkspaceBudget.TotalBudget = totalBudget + paymentHistory.Amount From 96fb0f9c4990446ffd8c5fb827f554f88fefa60e Mon Sep 17 00:00:00 2001 From: elraphty Date: Thu, 9 May 2024 14:04:00 +0100 Subject: [PATCH 20/44] fixed deduct payment --- db/workspaces.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/workspaces.go b/db/workspaces.go index 48731ce32..5a7799af3 100644 --- a/db/workspaces.go +++ b/db/workspaces.go @@ -271,7 +271,7 @@ func (db database) AddPaymentHistory(payment NewPaymentHistory) NewPaymentHistor db.db.Create(&payment) // get Workspace budget and subtract payment from total budget - WorkspaceBudget := db.GetWorkspaceBudget(payment.OrgUuid) + WorkspaceBudget := db.GetWorkspaceBudget(payment.WorkspaceUuid) totalBudget := WorkspaceBudget.TotalBudget // deduct amount if it's a bounty payment From c534f7345e17ebdcbdf0b761d5ac89a92925049e Mon Sep 17 00:00:00 2001 From: l Date: Fri, 10 May 2024 19:46:30 -0400 Subject: [PATCH 21/44] Added Tests --- cypress/e2e/06_phases.cy.js | 116 +++++++++++++++++++++++++++++ cypress/support/objects/objects.ts | 6 +- 2 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 cypress/e2e/06_phases.cy.js diff --git a/cypress/e2e/06_phases.cy.js b/cypress/e2e/06_phases.cy.js new file mode 100644 index 000000000..d74b0bbf2 --- /dev/null +++ b/cypress/e2e/06_phases.cy.js @@ -0,0 +1,116 @@ +import { User, HostName, UserStories, Phases } from '../support/objects/objects'; + +describe('Create Phases for Feature', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 2; i++) { + cy.request({ + method: 'POST', + url: `${HostName}/features/phase`, + headers: { 'x-jwt': `${value}` }, + body: Phases[i] + }).its('body').then(body => { + expect(body).to.have.property('uuid').and.equal(Phases[i].uuid.trim()); + expect(body).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); + expect(body).to.have.property('name').and.equal(Phases[i].name.trim()); + expect(body).to.have.property('priority').and.equal(Phases[i].priority); + }); + } + }) + }) +}) + +describe('Modify phases name', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 2; i++) { + cy.request({ + method: 'POST', + url: `${HostName}/features/phase`, + headers: { 'x-jwt': `${value}` }, + body: { + uuid: Phases[i].uuid, + name: Phases[i].name + "_addtext" + } + }).its('body').then(body => { + expect(body).to.have.property('uuid').and.equal(Phases[i].uuid.trim()); + expect(body).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); + expect(body).to.have.property('name').and.equal(Phases[i].name.trim() + "_addtext"); + expect(body).to.have.property('priority').and.equal(Phases[i].priority); + }); + } + }) + }) +}) + +describe('Get phases for feature', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + cy.request({ + method: 'GET', + url: `${HostName}/features/${Phases[0].feature_uuid}/phase`, + 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('uuid').and.equal(Phases[i].uuid.trim()); + expect(resp.body[i]).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); + expect(resp.body[i]).to.have.property('name').and.equal(Phases[i].name.trim() + "_addtext"); + expect(resp.body[i]).to.have.property('priority').and.equal(Phases[i].priority); + } + }) + }) + }) +}) + +describe('Get phase by uuid', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 2; i++) { + cy.request({ + method: 'GET', + url: `${HostName}/features/${Phases[0].feature_uuid}/phase/${Phases[i].uuid}`, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(200) + expect(resp.body[i]).to.have.property('uuid').and.equal(Phases[i].uuid.trim()); + expect(resp.body[i]).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); + expect(resp.body[i]).to.have.property('name').and.equal(Phases[i].name.trim() + "_addtext"); + expect(resp.body[i]).to.have.property('priority').and.equal(Phases[i].priority); + }) + } + }) + }) +}) + +describe('Delete phase by uuid', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + cy.request({ + method: 'DELETE', + url: `${HostName}/features/${Phases[0].feature_uuid}/phase/${Phases[0].uuid}`, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(200) + }) + }) + }) +}) + +describe('Check delete by uuid', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + cy.request({ + method: 'GET', + url: `${HostName}/features/${Phases[0].feature_uuid}/phase/${Phases[0].uuid}`, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(404); + }) + }) + }) +}) diff --git a/cypress/support/objects/objects.ts b/cypress/support/objects/objects.ts index bcbe8215d..18ec6065f 100644 --- a/cypress/support/objects/objects.ts +++ b/cypress/support/objects/objects.ts @@ -138,7 +138,7 @@ export const UserStories = [ ]; export const Phases = [ - { uuid: 'com1msgn1e4a0ts5kls0', feature_uuid: 'com1kson1e49th88dbg0', name: ' MVP ' }, - { uuid: 'com1mvgn1e4a1879uiv0', feature_uuid: 'com1kson1e49th88dbg0', name: ' Phase 2 ' }, - { uuid: 'com1n2gn1e4a1i8p60p0', feature_uuid: 'com1kson1e49th88dbg0', name: ' Phase 3 ' }, + { uuid: 'com1msgn1e4a0ts5kls0', feature_uuid: 'com1kson1e49th88dbg0', name: ' MVP ', priority: 0 }, + { uuid: 'com1mvgn1e4a1879uiv0', feature_uuid: 'com1kson1e49th88dbg0', name: ' Phase 2 ', priority: 1 }, + { uuid: 'com1n2gn1e4a1i8p60p0', feature_uuid: 'com1kson1e49th88dbg0', name: ' Phase 3 ', priority: 2 }, ]; \ No newline at end of file From 7c11757a3d9d0ebfe79acd0e3b3621f20d9988c2 Mon Sep 17 00:00:00 2001 From: elraphty Date: Tue, 14 May 2024 18:37:06 +0100 Subject: [PATCH 22/44] added paginationto feature --- db/features.go | 37 ++++++++++++++++++++--- db/interface.go | 3 +- db/structs.go | 12 ++++---- handlers/features.go | 25 +++++++++++++++- handlers/workspaces.go | 19 ++++-------- mocks/Database.go | 67 +++++++++++++++++++++++++++++++++++------- routes/features.go | 1 + 7 files changed, 129 insertions(+), 35 deletions(-) diff --git a/db/features.go b/db/features.go index c978124be..a4f0aad45 100644 --- a/db/features.go +++ b/db/features.go @@ -1,18 +1,49 @@ package db import ( + "fmt" + "net/http" "strings" "time" + + "github.com/stakwork/sphinx-tribes/utils" ) -func (db database) GetFeaturesByWorkspaceUuid(uuid string) []WorkspaceFeatures { +func (db database) GetFeaturesByWorkspaceUuid(uuid string, r *http.Request) []WorkspaceFeatures { + offset, limit, sortBy, direction, _ := utils.GetPaginationParams(r) + + orderQuery := "" + limitQuery := "" + ms := []WorkspaceFeatures{} - db.db.Model(&WorkspaceFeatures{}).Where("workspace_uuid = ?", uuid).Order("Created").Find(&ms) + if sortBy != "" && direction != "" { + orderQuery = "ORDER BY " + sortBy + " " + direction + } else { + orderQuery = "ORDER BY created DESC" + } + + if limit > 1 { + limitQuery = fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset) + } + + query := `SELECT * FROM public.workspace_features WHERE workspace_uuid = '` + uuid + `'` + + allQuery := query + " " + orderQuery + " " + limitQuery + + theQuery := db.db.Raw(allQuery) + + theQuery.Scan(&ms) return ms } +func (db database) GetWorkspaceFeaturesCount(uuid string) int64 { + var count int64 + db.db.Model(&WorkspaceFeatures{}).Where("workspace_uuid = ?", uuid).Count(&count) + return count +} + func (db database) GetFeatureByUuid(uuid string) WorkspaceFeatures { ms := WorkspaceFeatures{} @@ -35,7 +66,5 @@ func (db database) CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, 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 a5c27c430..0124592ad 100644 --- a/db/interface.go +++ b/db/interface.go @@ -143,6 +143,7 @@ type Database interface { CreateWorkspaceRepository(m WorkspaceRepositories) (WorkspaceRepositories, error) GetWorkspaceRepositorByWorkspaceUuid(uuid string) []WorkspaceRepositories CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, error) - GetFeaturesByWorkspaceUuid(uuid string) []WorkspaceFeatures + GetFeaturesByWorkspaceUuid(uuid string, r *http.Request) []WorkspaceFeatures + GetWorkspaceFeaturesCount(uuid string) int64 GetFeatureByUuid(uuid string) WorkspaceFeatures } diff --git a/db/structs.go b/db/structs.go index 68547b7fb..06c6100de 100644 --- a/db/structs.go +++ b/db/structs.go @@ -553,9 +553,9 @@ type WorkspaceUsersData struct { type WorkspaceRepositories struct { ID uint `json:"id"` - Uuid string `json:"uuid"` - WorkspaceUuid string `json:"workspace_uuid"` - Name string `json:"name"` + Uuid string `gorm:"not null" json:"uuid"` + WorkspaceUuid string `gorm:"not null" json:"workspace_uuid"` + Name string `gorm:"not null" json:"name"` Url string `json:"url"` Created *time.Time `json:"created"` Updated *time.Time `json:"updated"` @@ -565,9 +565,9 @@ type WorkspaceRepositories struct { type WorkspaceFeatures struct { ID uint `json:"id"` - Uuid string `json:"uuid"` - WorkspaceUuid string `json:"workspace_uuid"` - Name string `json:"name"` + Uuid string `gorm:"not null" json:"uuid"` + WorkspaceUuid string `gorm:"not null" json:"workspace_uuid"` + Name string `gorm:"not null" json:"name"` Brief string `json:"brief"` Requirements string `json:"requirements"` Architecture string `json:"architecture"` diff --git a/handlers/features.go b/handlers/features.go index 9c4f9cd68..ea634cfb0 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -7,6 +7,7 @@ import ( "net/http" "github.com/go-chi/chi" + "github.com/rs/xid" "github.com/stakwork/sphinx-tribes/auth" "github.com/stakwork/sphinx-tribes/db" ) @@ -43,6 +44,12 @@ func (oh *featureHandler) CreateOrEditFeatures(w http.ResponseWriter, r *http.Re features.CreatedBy = pubKeyFromAuth + if features.Uuid == "" { + features.Uuid = xid.New().String() + } else { + features.UpdatedBy = pubKeyFromAuth + } + // Validate struct data err = db.Validate.Struct(features) if err != nil { @@ -72,7 +79,23 @@ func (oh *featureHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r *h } uuid := chi.URLParam(r, "uuid") - workspaceFeatures := oh.db.GetFeaturesByWorkspaceUuid(uuid) + workspaceFeatures := oh.db.GetFeaturesByWorkspaceUuid(uuid, r) + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(workspaceFeatures) +} + +func (oh *featureHandler) GetWorkspaceFeaturesCount(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.GetWorkspaceFeaturesCount(uuid) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(workspaceFeatures) diff --git a/handlers/workspaces.go b/handlers/workspaces.go index 5175574d3..5ed2f09e6 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -126,19 +126,6 @@ func (oh *workspaceHandler) CreateOrEditWorkspace(w http.ResponseWriter, r *http } workspace.Name = name } - } else { - // if workspace.ID == 0 { - // // can't create that already exists - // fmt.Println("can't create existing organization") - // w.WriteHeader(http.StatusUnauthorized) - // return - // } - - // if workspace.ID != existing.ID { // can't edit someone else's - // fmt.Println("cant edit another organization") - // w.WriteHeader(http.StatusUnauthorized) - // return - // } } p, err := oh.db.CreateOrEditWorkspace(workspace) @@ -818,6 +805,12 @@ func (oh *workspaceHandler) CreateWorkspaceRepository(w http.ResponseWriter, r * workspaceRepo.CreatedBy = pubKeyFromAuth + if workspaceRepo.Uuid == "" { + workspaceRepo.Uuid = xid.New().String() + } else { + workspaceRepo.UpdatedBy = pubKeyFromAuth + } + // Validate struct data err = db.Validate.Struct(workspaceRepo) if err != nil { diff --git a/mocks/Database.go b/mocks/Database.go index 06093fbe1..6f68771bc 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -2681,17 +2681,17 @@ func (_c *Database_GetFeatureByUuid_Call) RunAndReturn(run func(string) db.Works return _c } -// GetFeaturesByWorkspaceUuid provides a mock function with given fields: uuid -func (_m *Database) GetFeaturesByWorkspaceUuid(uuid string) []db.WorkspaceFeatures { - ret := _m.Called(uuid) +// GetFeaturesByWorkspaceUuid provides a mock function with given fields: uuid, r +func (_m *Database) GetFeaturesByWorkspaceUuid(uuid string, r *http.Request) []db.WorkspaceFeatures { + ret := _m.Called(uuid, r) 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) + if rf, ok := ret.Get(0).(func(string, *http.Request) []db.WorkspaceFeatures); ok { + r0 = rf(uuid, r) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]db.WorkspaceFeatures) @@ -2708,13 +2708,14 @@ type Database_GetFeaturesByWorkspaceUuid_Call struct { // 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)} +// - r *http.Request +func (_e *Database_Expecter) GetFeaturesByWorkspaceUuid(uuid interface{}, r interface{}) *Database_GetFeaturesByWorkspaceUuid_Call { + return &Database_GetFeaturesByWorkspaceUuid_Call{Call: _e.mock.On("GetFeaturesByWorkspaceUuid", uuid, r)} } -func (_c *Database_GetFeaturesByWorkspaceUuid_Call) Run(run func(uuid string)) *Database_GetFeaturesByWorkspaceUuid_Call { +func (_c *Database_GetFeaturesByWorkspaceUuid_Call) Run(run func(uuid string, r *http.Request)) *Database_GetFeaturesByWorkspaceUuid_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(string)) + run(args[0].(string), args[1].(*http.Request)) }) return _c } @@ -2724,7 +2725,7 @@ func (_c *Database_GetFeaturesByWorkspaceUuid_Call) Return(_a0 []db.WorkspaceFea return _c } -func (_c *Database_GetFeaturesByWorkspaceUuid_Call) RunAndReturn(run func(string) []db.WorkspaceFeatures) *Database_GetFeaturesByWorkspaceUuid_Call { +func (_c *Database_GetFeaturesByWorkspaceUuid_Call) RunAndReturn(run func(string, *http.Request) []db.WorkspaceFeatures) *Database_GetFeaturesByWorkspaceUuid_Call { _c.Call.Return(run) return _c } @@ -4860,6 +4861,52 @@ func (_c *Database_GetWorkspaceByUuid_Call) RunAndReturn(run func(string) db.Wor return _c } +// GetWorkspaceFeaturesCount provides a mock function with given fields: uuid +func (_m *Database) GetWorkspaceFeaturesCount(uuid string) int64 { + ret := _m.Called(uuid) + + if len(ret) == 0 { + panic("no return value specified for GetWorkspaceFeaturesCount") + } + + var r0 int64 + if rf, ok := ret.Get(0).(func(string) int64); ok { + r0 = rf(uuid) + } else { + r0 = ret.Get(0).(int64) + } + + return r0 +} + +// Database_GetWorkspaceFeaturesCount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWorkspaceFeaturesCount' +type Database_GetWorkspaceFeaturesCount_Call struct { + *mock.Call +} + +// GetWorkspaceFeaturesCount is a helper method to define mock.On call +// - uuid string +func (_e *Database_Expecter) GetWorkspaceFeaturesCount(uuid interface{}) *Database_GetWorkspaceFeaturesCount_Call { + return &Database_GetWorkspaceFeaturesCount_Call{Call: _e.mock.On("GetWorkspaceFeaturesCount", uuid)} +} + +func (_c *Database_GetWorkspaceFeaturesCount_Call) Run(run func(uuid string)) *Database_GetWorkspaceFeaturesCount_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Database_GetWorkspaceFeaturesCount_Call) Return(_a0 int64) *Database_GetWorkspaceFeaturesCount_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_GetWorkspaceFeaturesCount_Call) RunAndReturn(run func(string) int64) *Database_GetWorkspaceFeaturesCount_Call { + _c.Call.Return(run) + return _c +} + // GetWorkspaceInvoices provides a mock function with given fields: workspace_uuid func (_m *Database) GetWorkspaceInvoices(workspace_uuid string) []db.NewInvoiceList { ret := _m.Called(workspace_uuid) diff --git a/routes/features.go b/routes/features.go index 0b2449399..a6189793a 100644 --- a/routes/features.go +++ b/routes/features.go @@ -16,6 +16,7 @@ func FeatureRoutes() chi.Router { r.Post("/", featureHandlers.CreateOrEditFeatures) r.Get("/forworkspace/{uuid}", featureHandlers.GetFeaturesByWorkspaceUuid) r.Get("/{uuid}", featureHandlers.GetFeatureByUuid) + r.Get("/workspace/count/{uuid}", featureHandlers.GetWorkspaceFeaturesCount) }) return r } From 133800dc09320a46607bdbe539553df83b073f2b Mon Sep 17 00:00:00 2001 From: l Date: Fri, 10 May 2024 19:46:30 -0400 Subject: [PATCH 23/44] Added Tests --- cypress/e2e/06_phases.cy.js | 116 +++++++++++++++++++++++++++++ cypress/support/objects/objects.ts | 6 +- 2 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 cypress/e2e/06_phases.cy.js diff --git a/cypress/e2e/06_phases.cy.js b/cypress/e2e/06_phases.cy.js new file mode 100644 index 000000000..d74b0bbf2 --- /dev/null +++ b/cypress/e2e/06_phases.cy.js @@ -0,0 +1,116 @@ +import { User, HostName, UserStories, Phases } from '../support/objects/objects'; + +describe('Create Phases for Feature', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 2; i++) { + cy.request({ + method: 'POST', + url: `${HostName}/features/phase`, + headers: { 'x-jwt': `${value}` }, + body: Phases[i] + }).its('body').then(body => { + expect(body).to.have.property('uuid').and.equal(Phases[i].uuid.trim()); + expect(body).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); + expect(body).to.have.property('name').and.equal(Phases[i].name.trim()); + expect(body).to.have.property('priority').and.equal(Phases[i].priority); + }); + } + }) + }) +}) + +describe('Modify phases name', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 2; i++) { + cy.request({ + method: 'POST', + url: `${HostName}/features/phase`, + headers: { 'x-jwt': `${value}` }, + body: { + uuid: Phases[i].uuid, + name: Phases[i].name + "_addtext" + } + }).its('body').then(body => { + expect(body).to.have.property('uuid').and.equal(Phases[i].uuid.trim()); + expect(body).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); + expect(body).to.have.property('name').and.equal(Phases[i].name.trim() + "_addtext"); + expect(body).to.have.property('priority').and.equal(Phases[i].priority); + }); + } + }) + }) +}) + +describe('Get phases for feature', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + cy.request({ + method: 'GET', + url: `${HostName}/features/${Phases[0].feature_uuid}/phase`, + 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('uuid').and.equal(Phases[i].uuid.trim()); + expect(resp.body[i]).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); + expect(resp.body[i]).to.have.property('name').and.equal(Phases[i].name.trim() + "_addtext"); + expect(resp.body[i]).to.have.property('priority').and.equal(Phases[i].priority); + } + }) + }) + }) +}) + +describe('Get phase by uuid', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 2; i++) { + cy.request({ + method: 'GET', + url: `${HostName}/features/${Phases[0].feature_uuid}/phase/${Phases[i].uuid}`, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(200) + expect(resp.body[i]).to.have.property('uuid').and.equal(Phases[i].uuid.trim()); + expect(resp.body[i]).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); + expect(resp.body[i]).to.have.property('name').and.equal(Phases[i].name.trim() + "_addtext"); + expect(resp.body[i]).to.have.property('priority').and.equal(Phases[i].priority); + }) + } + }) + }) +}) + +describe('Delete phase by uuid', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + cy.request({ + method: 'DELETE', + url: `${HostName}/features/${Phases[0].feature_uuid}/phase/${Phases[0].uuid}`, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(200) + }) + }) + }) +}) + +describe('Check delete by uuid', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + cy.request({ + method: 'GET', + url: `${HostName}/features/${Phases[0].feature_uuid}/phase/${Phases[0].uuid}`, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(404); + }) + }) + }) +}) diff --git a/cypress/support/objects/objects.ts b/cypress/support/objects/objects.ts index 9c1908ca8..9e4910605 100644 --- a/cypress/support/objects/objects.ts +++ b/cypress/support/objects/objects.ts @@ -138,7 +138,7 @@ export const UserStories = [ ]; export const Phases = [ - { uuid: 'com1msgn1e4a0ts5kls0', feature_uuid: 'com1kson1e49th88dbg0', name: ' MVP ' }, - { uuid: 'com1mvgn1e4a1879uiv0', feature_uuid: 'com1kson1e49th88dbg0', name: ' Phase 2 ' }, - { uuid: 'com1n2gn1e4a1i8p60p0', feature_uuid: 'com1kson1e49th88dbg0', name: ' Phase 3 ' }, + { uuid: 'com1msgn1e4a0ts5kls0', feature_uuid: 'com1kson1e49th88dbg0', name: ' MVP ', priority: 0 }, + { uuid: 'com1mvgn1e4a1879uiv0', feature_uuid: 'com1kson1e49th88dbg0', name: ' Phase 2 ', priority: 1 }, + { uuid: 'com1n2gn1e4a1i8p60p0', feature_uuid: 'com1kson1e49th88dbg0', name: ' Phase 3 ', priority: 2 }, ]; \ No newline at end of file From f745cdd11ee82e14d8321761c0ce57ed4f379bb6 Mon Sep 17 00:00:00 2001 From: elraphty Date: Wed, 15 May 2024 06:38:45 +0100 Subject: [PATCH 24/44] fixed merge conflicts --- cypress/e2e/06_phases.cy.js | 34 +++++++++-------- db/config.go | 1 + db/features.go | 50 +++++++++++++++++++++++++ db/interface.go | 4 ++ db/structs.go | 11 ++++++ handlers/features.go | 73 +++++++++++++++++++++++++++++++++++-- routes/features.go | 8 +++- 7 files changed, 162 insertions(+), 19 deletions(-) diff --git a/cypress/e2e/06_phases.cy.js b/cypress/e2e/06_phases.cy.js index d74b0bbf2..bcc7dbd32 100644 --- a/cypress/e2e/06_phases.cy.js +++ b/cypress/e2e/06_phases.cy.js @@ -35,7 +35,7 @@ describe('Modify phases name', () => { }).its('body').then(body => { expect(body).to.have.property('uuid').and.equal(Phases[i].uuid.trim()); expect(body).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); - expect(body).to.have.property('name').and.equal(Phases[i].name.trim() + "_addtext"); + expect(body).to.have.property('name').and.equal(Phases[i].name.trim() + " _addtext"); expect(body).to.have.property('priority').and.equal(Phases[i].priority); }); } @@ -50,15 +50,18 @@ describe('Get phases for feature', () => { method: 'GET', url: `${HostName}/features/${Phases[0].feature_uuid}/phase`, headers: { 'x-jwt': `${ value }` }, - body: {} + body: {} }).then((resp) => { expect(resp.status).to.eq(200) - for(let i = 0; i <= 2; i++) { - expect(resp.body[i]).to.have.property('uuid').and.equal(Phases[i].uuid.trim()); - expect(resp.body[i]).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); - expect(resp.body[i]).to.have.property('name').and.equal(Phases[i].name.trim() + "_addtext"); - expect(resp.body[i]).to.have.property('priority').and.equal(Phases[i].priority); - } + + resp.body.forEach((phase, index) => { + // Directly use index to compare with the expected phase in the same order + const expectedPhase = Phases[index]; + expect(phase.uuid).to.equal(expectedPhase.uuid.trim()); + expect(phase.feature_uuid).to.equal(expectedPhase.feature_uuid.trim()); + expect(phase.name).to.equal(expectedPhase.name.trim() + " _addtext"); + expect(phase.priority).to.equal(expectedPhase.priority); + }); }) }) }) @@ -72,13 +75,13 @@ describe('Get phase by uuid', () => { method: 'GET', url: `${HostName}/features/${Phases[0].feature_uuid}/phase/${Phases[i].uuid}`, headers: { 'x-jwt': `${ value }` }, - body: {} + body: {} }).then((resp) => { expect(resp.status).to.eq(200) - expect(resp.body[i]).to.have.property('uuid').and.equal(Phases[i].uuid.trim()); - expect(resp.body[i]).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); - expect(resp.body[i]).to.have.property('name').and.equal(Phases[i].name.trim() + "_addtext"); - expect(resp.body[i]).to.have.property('priority').and.equal(Phases[i].priority); + expect(resp.body).to.have.property('uuid').and.equal(Phases[i].uuid.trim()); + expect(resp.body).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); + expect(resp.body).to.have.property('name').and.equal(Phases[i].name.trim() + " _addtext"); + expect(resp.body).to.have.property('priority').and.equal(Phases[i].priority); }) } }) @@ -92,7 +95,7 @@ describe('Delete phase by uuid', () => { method: 'DELETE', url: `${HostName}/features/${Phases[0].feature_uuid}/phase/${Phases[0].uuid}`, headers: { 'x-jwt': `${ value }` }, - body: {} + body: {} }).then((resp) => { expect(resp.status).to.eq(200) }) @@ -107,7 +110,8 @@ describe('Check delete by uuid', () => { method: 'GET', url: `${HostName}/features/${Phases[0].feature_uuid}/phase/${Phases[0].uuid}`, headers: { 'x-jwt': `${ value }` }, - body: {} + body: {}, + failOnStatusCode: false }).then((resp) => { expect(resp.status).to.eq(404); }) diff --git a/db/config.go b/db/config.go index 3ffc2c3a1..b7ab4fa20 100644 --- a/db/config.go +++ b/db/config.go @@ -69,6 +69,7 @@ func InitDB() { db.AutoMigrate(&UserInvoiceData{}) db.AutoMigrate(&WorkspaceRepositories{}) db.AutoMigrate(&WorkspaceFeatures{}) + db.AutoMigrate(&FeaturePhase{}) DB.MigrateTablesWithOrgUuid() DB.MigrateOrganizationToWorkspace() diff --git a/db/features.go b/db/features.go index a4f0aad45..8b628fca4 100644 --- a/db/features.go +++ b/db/features.go @@ -1,8 +1,12 @@ package db import ( +<<<<<<< HEAD "fmt" "net/http" +======= + "errors" +>>>>>>> b77bffb1 (tests for Add Phases to Features) "strings" "time" @@ -68,3 +72,49 @@ func (db database) CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, return m, nil } + +func (db database) CreateOrEditFeaturePhase(phase FeaturePhase) (FeaturePhase, error) { + phase.Name = strings.TrimSpace(phase.Name) + + now := time.Now() + phase.Updated = &now + + existingPhase := FeaturePhase{} + result := db.db.Model(&FeaturePhase{}).Where("uuid = ?", phase.Uuid).First(&existingPhase) + + if result.RowsAffected == 0 { + + phase.Created = &now + db.db.Create(&phase) + } else { + + db.db.Model(&FeaturePhase{}).Where("uuid = ?", phase.Uuid).Updates(phase) + } + + db.db.Model(&FeaturePhase{}).Where("uuid = ?", phase.Uuid).Find(&phase) + + return phase, nil +} + +func (db database) GetFeaturePhasesByFeatureUuid(featureUuid string) []FeaturePhase { + phases := []FeaturePhase{} + db.db.Model(&FeaturePhase{}).Where("feature_uuid = ?", featureUuid).Order("Created ASC").Find(&phases) + return phases +} + +func (db database) GetFeaturePhaseByUuid(featureUuid, phaseUuid string) (FeaturePhase, error) { + phase := FeaturePhase{} + result := db.db.Model(&FeaturePhase{}).Where("feature_uuid = ? AND uuid = ?", featureUuid, phaseUuid).First(&phase) + if result.RowsAffected == 0 { + return phase, errors.New("no phase found") + } + return phase, nil +} + +func (db database) DeleteFeaturePhase(featureUuid, phaseUuid string) error { + result := db.db.Where("feature_uuid = ? AND uuid = ?", featureUuid, phaseUuid).Delete(&FeaturePhase{}) + if result.RowsAffected == 0 { + return errors.New("no phase found to delete") + } + return nil +} diff --git a/db/interface.go b/db/interface.go index 0124592ad..97ce145d0 100644 --- a/db/interface.go +++ b/db/interface.go @@ -146,4 +146,8 @@ type Database interface { GetFeaturesByWorkspaceUuid(uuid string, r *http.Request) []WorkspaceFeatures GetWorkspaceFeaturesCount(uuid string) int64 GetFeatureByUuid(uuid string) WorkspaceFeatures + CreateOrEditFeaturePhase(phase FeaturePhase) (FeaturePhase, error) + GetFeaturePhasesByFeatureUuid(featureUUID string) []FeaturePhase + GetFeaturePhaseByUuid(featureUUID, phaseUUID string) (FeaturePhase, error) + DeleteFeaturePhase(featureUUID, phaseUUID string) error } diff --git a/db/structs.go b/db/structs.go index 06c6100de..020b5e4b8 100644 --- a/db/structs.go +++ b/db/structs.go @@ -577,6 +577,17 @@ type WorkspaceFeatures struct { UpdatedBy string `json:"updated_by"` } +type FeaturePhase struct { + Uuid string `json:"uuid" gorm:"primary_key"` + FeatureUuid string `json:"feature_uuid"` + Name string `json:"name"` + Priority int `json:"priority"` + 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 index ea634cfb0..e81e71671 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -3,13 +3,12 @@ package handlers import ( "encoding/json" "fmt" - "io" - "net/http" - "github.com/go-chi/chi" "github.com/rs/xid" "github.com/stakwork/sphinx-tribes/auth" "github.com/stakwork/sphinx-tribes/db" + "io" + "net/http" ) type featureHandler struct { @@ -116,3 +115,71 @@ func (oh *featureHandler) GetFeatureByUuid(w http.ResponseWriter, r *http.Reques w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(workspaceFeature) } + +func (oh *featureHandler) CreateOrEditFeaturePhase(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 + } + + newPhase := db.FeaturePhase{} + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&newPhase) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "Error decoding request body: %v", err) + return + } + + newPhase.CreatedBy = pubKeyFromAuth + + phase, err := oh.db.CreateOrEditFeaturePhase(newPhase) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error creating feature phase: %v", err) + return + } + + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(phase) +} + +func (oh *featureHandler) GetFeaturePhases(w http.ResponseWriter, r *http.Request) { + uuid := chi.URLParam(r, "feature_uuid") + phases := oh.db.GetFeaturePhasesByFeatureUuid(uuid) + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(phases) +} + +func (oh *featureHandler) GetFeaturePhaseByUUID(w http.ResponseWriter, r *http.Request) { + featureUUID := chi.URLParam(r, "feature_uuid") + phaseUUID := chi.URLParam(r, "phase_uuid") + + phase, err := oh.db.GetFeaturePhaseByUuid(featureUUID, phaseUUID) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(phase) +} + +func (oh *featureHandler) DeleteFeaturePhase(w http.ResponseWriter, r *http.Request) { + featureUUID := chi.URLParam(r, "feature_uuid") + phaseUUID := chi.URLParam(r, "phase_uuid") + + err := oh.db.DeleteFeaturePhase(featureUUID, phaseUUID) + if err != nil { + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"message": "Phase deleted successfully"}) +} diff --git a/routes/features.go b/routes/features.go index a6189793a..405b7ce60 100644 --- a/routes/features.go +++ b/routes/features.go @@ -9,7 +9,7 @@ import ( func FeatureRoutes() chi.Router { r := chi.NewRouter() - featureHandlers := handlers.NewFeatureHandler(db.DB) + featureHandlers := handlers.NewFeatureHandler(&db.DB) r.Group(func(r chi.Router) { r.Use(auth.PubKeyContext) @@ -17,6 +17,12 @@ func FeatureRoutes() chi.Router { r.Get("/forworkspace/{uuid}", featureHandlers.GetFeaturesByWorkspaceUuid) r.Get("/{uuid}", featureHandlers.GetFeatureByUuid) r.Get("/workspace/count/{uuid}", featureHandlers.GetWorkspaceFeaturesCount) + + r.Post("/phase", featureHandlers.CreateOrEditFeaturePhase) + r.Get("/{feature_uuid}/phase", featureHandlers.GetFeaturePhases) + r.Get("/{feature_uuid}/phase/{phase_uuid}", featureHandlers.GetFeaturePhaseByUUID) + r.Delete("/{feature_uuid}/phase/{phase_uuid}", featureHandlers.DeleteFeaturePhase) + }) return r } From 7a2e1dffbbbb61b085e1c12a0aa68368e6febd94 Mon Sep 17 00:00:00 2001 From: MahtabBukhari Date: Tue, 14 May 2024 03:10:25 +0500 Subject: [PATCH 25/44] again push due to error --- db/features.go | 2 +- db/interface.go | 2 +- handlers/features.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/db/features.go b/db/features.go index 8b628fca4..2ae964533 100644 --- a/db/features.go +++ b/db/features.go @@ -96,7 +96,7 @@ func (db database) CreateOrEditFeaturePhase(phase FeaturePhase) (FeaturePhase, e return phase, nil } -func (db database) GetFeaturePhasesByFeatureUuid(featureUuid string) []FeaturePhase { +func (db database) GetPhasesByFeatureUuid(featureUuid string) []FeaturePhase { phases := []FeaturePhase{} db.db.Model(&FeaturePhase{}).Where("feature_uuid = ?", featureUuid).Order("Created ASC").Find(&phases) return phases diff --git a/db/interface.go b/db/interface.go index 97ce145d0..7253b9f9d 100644 --- a/db/interface.go +++ b/db/interface.go @@ -147,7 +147,7 @@ type Database interface { GetWorkspaceFeaturesCount(uuid string) int64 GetFeatureByUuid(uuid string) WorkspaceFeatures CreateOrEditFeaturePhase(phase FeaturePhase) (FeaturePhase, error) - GetFeaturePhasesByFeatureUuid(featureUUID string) []FeaturePhase + GetPhasesByFeatureUuid(featureUUID string) []FeaturePhase GetFeaturePhaseByUuid(featureUUID, phaseUUID string) (FeaturePhase, error) DeleteFeaturePhase(featureUUID, phaseUUID string) error } diff --git a/handlers/features.go b/handlers/features.go index e81e71671..ed31eb5d0 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -149,7 +149,7 @@ func (oh *featureHandler) CreateOrEditFeaturePhase(w http.ResponseWriter, r *htt func (oh *featureHandler) GetFeaturePhases(w http.ResponseWriter, r *http.Request) { uuid := chi.URLParam(r, "feature_uuid") - phases := oh.db.GetFeaturePhasesByFeatureUuid(uuid) + phases := oh.db.GetPhasesByFeatureUuid(uuid) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(phases) From 6cf499db7b325af8142fa3252ae045b58f76d1ff Mon Sep 17 00:00:00 2001 From: MahtabBukhari Date: Tue, 14 May 2024 04:11:53 +0500 Subject: [PATCH 26/44] fixed unit test errors --- db/interface.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/interface.go b/db/interface.go index 7253b9f9d..1643e352c 100644 --- a/db/interface.go +++ b/db/interface.go @@ -147,7 +147,7 @@ type Database interface { GetWorkspaceFeaturesCount(uuid string) int64 GetFeatureByUuid(uuid string) WorkspaceFeatures CreateOrEditFeaturePhase(phase FeaturePhase) (FeaturePhase, error) - GetPhasesByFeatureUuid(featureUUID string) []FeaturePhase - GetFeaturePhaseByUuid(featureUUID, phaseUUID string) (FeaturePhase, error) - DeleteFeaturePhase(featureUUID, phaseUUID string) error + GetPhasesByFeatureUuid(featureUuid string) []FeaturePhase + GetFeaturePhaseByUuid(featureUuid, phaseUuid string) (FeaturePhase, error) + DeleteFeaturePhase(featureUuid, phaseUuid string) error } From 32a45ddabeb69140868acb382938e08c768c1cec Mon Sep 17 00:00:00 2001 From: MahtabBukhari Date: Tue, 14 May 2024 04:12:08 +0500 Subject: [PATCH 27/44] fixed unit test errors --- handlers/features.go | 16 +++++----- mocks/Database.go | 72 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/handlers/features.go b/handlers/features.go index ed31eb5d0..aae7ff398 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -148,18 +148,18 @@ func (oh *featureHandler) CreateOrEditFeaturePhase(w http.ResponseWriter, r *htt } func (oh *featureHandler) GetFeaturePhases(w http.ResponseWriter, r *http.Request) { - uuid := chi.URLParam(r, "feature_uuid") - phases := oh.db.GetPhasesByFeatureUuid(uuid) + featureUuid := chi.URLParam(r, "feature_uuid") + phases := oh.db.GetPhasesByFeatureUuid(featureUuid) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(phases) } func (oh *featureHandler) GetFeaturePhaseByUUID(w http.ResponseWriter, r *http.Request) { - featureUUID := chi.URLParam(r, "feature_uuid") - phaseUUID := chi.URLParam(r, "phase_uuid") + featureUuid := chi.URLParam(r, "feature_uuid") + phaseUuid := chi.URLParam(r, "phase_uuid") - phase, err := oh.db.GetFeaturePhaseByUuid(featureUUID, phaseUUID) + phase, err := oh.db.GetFeaturePhaseByUuid(featureUuid, phaseUuid) if err != nil { w.WriteHeader(http.StatusNotFound) return @@ -170,10 +170,10 @@ func (oh *featureHandler) GetFeaturePhaseByUUID(w http.ResponseWriter, r *http.R } func (oh *featureHandler) DeleteFeaturePhase(w http.ResponseWriter, r *http.Request) { - featureUUID := chi.URLParam(r, "feature_uuid") - phaseUUID := chi.URLParam(r, "phase_uuid") + featureUuid := chi.URLParam(r, "feature_uuid") + phaseUuid := chi.URLParam(r, "phase_uuid") - err := oh.db.DeleteFeaturePhase(featureUUID, phaseUUID) + err := oh.db.DeleteFeaturePhase(featureUuid, phaseUuid) if err != nil { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) diff --git a/mocks/Database.go b/mocks/Database.go index 6f68771bc..92bbc00de 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -6811,3 +6811,75 @@ func NewDatabase(t interface { return mock } + +// CreateOrEditFeaturePhase provides a mock function with given fields: phase +func (_m *Database) CreateOrEditFeaturePhase(phase db.FeaturePhase) (db.FeaturePhase, error) { + ret := _m.Called(phase) + + var r0 db.FeaturePhase + var r1 error + if rf, ok := ret.Get(0).(func(db.FeaturePhase) db.FeaturePhase); ok { + r0 = rf(phase) + } else { + r0 = ret.Get(0).(db.FeaturePhase) + } + + if rf, ok := ret.Get(1).(func(db.FeaturePhase) error); ok { + r1 = rf(phase) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetPhasesByFeatureUuid provides a mock function with given fields: featureUuid +func (_m *Database) GetPhasesByFeatureUuid(featureUuid string) []db.FeaturePhase { + ret := _m.Called(featureUuid) + + var r0 []db.FeaturePhase + if rf, ok := ret.Get(0).(func(string) []db.FeaturePhase); ok { + r0 = rf(featureUuid) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.FeaturePhase) + } + } + + return r0 +} + +// GetFeaturePhaseByUuid provides a mock function with given fields: featureUuid, phaseUuid +func (_m *Database) GetFeaturePhaseByUuid(featureUuid, phaseUuid string) (db.FeaturePhase, error) { + ret := _m.Called(featureUuid, phaseUuid) + + var r0 db.FeaturePhase + var r1 error + if rf, ok := ret.Get(0).(func(string, string) db.FeaturePhase); ok { + r0 = rf(featureUuid, phaseUuid) + } else { + r0 = ret.Get(0).(db.FeaturePhase) + } + + if rf, ok := ret.Get(1).(func(string, string) error); ok { + r1 = rf(featureUuid, phaseUuid) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeleteFeaturePhase provides a mock function with given fields: featureUuid, phaseUuid +func (_m *Database) DeleteFeaturePhase(featureUuid string, phaseUuid string) error { + ret := _m.Called(featureUuid, phaseUuid) + + var r1 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r1 = rf(featureUuid, phaseUuid) + } else { + r1 = ret.Error(0) + } + + return r1 +} From 3c7cc872b8fe1f4217b310f72d769e4c4c6afb6b Mon Sep 17 00:00:00 2001 From: MahtabBukhari Date: Tue, 14 May 2024 22:58:04 +0500 Subject: [PATCH 28/44] update code to fulfill requirements --- handlers/features.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/handlers/features.go b/handlers/features.go index aae7ff398..f9e41a42a 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -134,7 +134,15 @@ func (oh *featureHandler) CreateOrEditFeaturePhase(w http.ResponseWriter, r *htt return } - newPhase.CreatedBy = pubKeyFromAuth + if newPhase.Uuid == "" { + newPhase.Uuid = xid.New().String() + } + + if newPhase.CreatedBy != "" { + newPhase.UpdatedBy = pubKeyFromAuth + } else { + newPhase.CreatedBy = pubKeyFromAuth + } phase, err := oh.db.CreateOrEditFeaturePhase(newPhase) if err != nil { From d1f84285b6d7b998d8ea9610ba19eadd660501a6 Mon Sep 17 00:00:00 2001 From: MahtabBukhari Date: Wed, 15 May 2024 03:55:38 +0500 Subject: [PATCH 29/44] update code logic --- handlers/features.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/handlers/features.go b/handlers/features.go index f9e41a42a..1e2096623 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -138,12 +138,14 @@ func (oh *featureHandler) CreateOrEditFeaturePhase(w http.ResponseWriter, r *htt newPhase.Uuid = xid.New().String() } - if newPhase.CreatedBy != "" { - newPhase.UpdatedBy = pubKeyFromAuth - } else { + existingPhase, _ := oh.db.GetFeaturePhaseByUuid(newPhase.FeatureUuid, newPhase.Uuid) + + if existingPhase.CreatedBy == "" { newPhase.CreatedBy = pubKeyFromAuth } + newPhase.UpdatedBy = pubKeyFromAuth + phase, err := oh.db.CreateOrEditFeaturePhase(newPhase) if err != nil { w.WriteHeader(http.StatusInternalServerError) From 8f304852b4133f53479ae0689beca7b237d4709e Mon Sep 17 00:00:00 2001 From: MahtabBukhari Date: Wed, 15 May 2024 05:18:28 +0500 Subject: [PATCH 30/44] change file name --- cypress/e2e/{06_phases.cy.js => 06_phases.cy.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cypress/e2e/{06_phases.cy.js => 06_phases.cy.ts} (100%) diff --git a/cypress/e2e/06_phases.cy.js b/cypress/e2e/06_phases.cy.ts similarity index 100% rename from cypress/e2e/06_phases.cy.js rename to cypress/e2e/06_phases.cy.ts From 50cc848661f726b08618ca48e935a5e52ed8329e Mon Sep 17 00:00:00 2001 From: elraphty Date: Wed, 15 May 2024 06:40:35 +0100 Subject: [PATCH 31/44] fixed merge conflicts --- db/features.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/db/features.go b/db/features.go index 2ae964533..1372674f1 100644 --- a/db/features.go +++ b/db/features.go @@ -1,12 +1,9 @@ package db import ( -<<<<<<< HEAD + "errors" "fmt" "net/http" -======= - "errors" ->>>>>>> b77bffb1 (tests for Add Phases to Features) "strings" "time" From cb02084b3590d5f042fc43a37828ef344e50a626 Mon Sep 17 00:00:00 2001 From: l Date: Fri, 10 May 2024 16:59:47 -0400 Subject: [PATCH 32/44] Add Tests --- cypress/e2e/04_user_stories.ts | 116 +++++++++++++++++++++++++++++ cypress/support/objects/objects.ts | 12 +-- 2 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 cypress/e2e/04_user_stories.ts diff --git a/cypress/e2e/04_user_stories.ts b/cypress/e2e/04_user_stories.ts new file mode 100644 index 000000000..537a6764d --- /dev/null +++ b/cypress/e2e/04_user_stories.ts @@ -0,0 +1,116 @@ +import { User, HostName, UserStories } from '../support/objects/objects'; + +describe('Create user stories for Feature', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 5; i++) { + cy.request({ + method: 'POST', + url: `${HostName}/features/story`, + headers: { 'x-jwt': `${value}` }, + body: UserStories[i] + }).its('body').then(body => { + expect(body).to.have.property('uuid').and.equal(UserStories[i].uuid.trim()); + expect(body).to.have.property('feature_uuid').and.equal(UserStories[i].feature_uuid.trim()); + expect(body).to.have.property('description').and.equal(UserStories[i].description.trim()); + expect(body).to.have.property('priority').and.equal(UserStories[i].priority); + }); + } + }) + }) +}) + +describe('Modify user story description', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 5; i++) { + cy.request({ + method: 'POST', + url: `${HostName}/features/story`, + headers: { 'x-jwt': `${value}` }, + body: { + uuid: UserStories[i].uuid, + description: UserStories[i].description + "_addtext" + } + }).its('body').then(body => { + expect(body).to.have.property('uuid').and.equal(UserStories[i].uuid.trim()); + expect(body).to.have.property('feature_uuid').and.equal(UserStories[i].feature_uuid.trim()); + expect(body).to.have.property('description').and.equal(UserStories[i].description.trim() + "_addtext"); + expect(body).to.have.property('priority').and.equal(UserStories[i].priority); + }); + } + }) + }) +}) + +describe('Get user stories for feature', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + cy.request({ + method: 'GET', + url: `${HostName}/features/${UserStories[0].feature_uuid}/story`, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(200) + for(let i = 0; i <= 5; i++) { + expect(resp.body[i]).to.have.property('uuid').and.equal(UserStories[i].uuid.trim()); + expect(resp.body[i]).to.have.property('feature_uuid').and.equal(UserStories[i].feature_uuid.trim()); + expect(resp.body[i]).to.have.property('description').and.equal(UserStories[i].description.trim() + "_addtext"); + expect(resp.body[i]).to.have.property('priority').and.equal(UserStories[i].priority); + } + }) + }) + }) +}) + +describe('Get story by uuid', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + for(let i = 0; i <= 5; i++) { + cy.request({ + method: 'GET', + url: `${HostName}/features/${UserStories[0].feature_uuid}/story/${UserStories[i].uuid}`, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(200) + expect(resp.body[i]).to.have.property('uuid').and.equal(UserStories[i].uuid.trim()); + expect(resp.body[i]).to.have.property('feature_uuid').and.equal(UserStories[i].feature_uuid.trim()); + expect(resp.body[i]).to.have.property('description').and.equal(UserStories[i].description.trim() + "_addtext"); + expect(resp.body[i]).to.have.property('priority').and.equal(UserStories[i].priority); + }) + } + }) + }) +}) + +describe('Delete story by uuid', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + cy.request({ + method: 'DELETE', + url: `${HostName}/features/${UserStories[0].feature_uuid}/story/${UserStories[0].uuid}`, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(200) + }) + }) + }) +}) + +describe('Check delete by uuid', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + cy.request({ + method: 'GET', + url: `${HostName}/features/${UserStories[0].feature_uuid}/story/${UserStories[0].uuid}`, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(404); + }) + }) + }) +}) diff --git a/cypress/support/objects/objects.ts b/cypress/support/objects/objects.ts index 9e4910605..973ab70fe 100644 --- a/cypress/support/objects/objects.ts +++ b/cypress/support/objects/objects.ts @@ -129,12 +129,12 @@ export const Features = [ ]; export const UserStories = [ - { 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} ' }, + { uuid: 'com1lh0n1e49ug76noig', feature_uuid: 'com1kson1e49th88dbg0', description: ' As a {PM} I want to {make providers \"hive ready\"}, so I can {leverage the hive process ', priority: 0 }, + { uuid: 'com1lk8n1e49uqfe3l40', feature_uuid: 'com1kson1e49th88dbg0', description: ' As a {PM} I want to {CRUD Features}, so I can {use the system to manage my features} ', priority: 1 }, + { uuid: 'com1ln8n1e49v4159gug', feature_uuid: 'com1kson1e49th88dbg0', description: ' As a {PM} I want to {follow best practices}, so I can {make more valuable features} ', priority: 2 }, + { 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} ', priority: 3 }, + { uuid: 'com1lt8n1e49voquoq90', feature_uuid: 'com1kson1e49th88dbg0', description: ' As a {PM} I want to {create phases}, so I can {divide the work in several deliverable stages} ', priority: 4 }, + { uuid: 'com1m08n1e4a02r6j0pg', feature_uuid: 'com1kson1e49th88dbg0', description: ' As a {PM} I want to {assign bounties to features}, so I can {group bounties together} ', priority: 5 }, ]; export const Phases = [ From b72ac7ffcbe094c97ff48f7ffc70425b2a136cb4 Mon Sep 17 00:00:00 2001 From: l Date: Fri, 10 May 2024 17:30:17 -0400 Subject: [PATCH 33/44] Change file name --- cypress/e2e/{04_user_stories.ts => 04_user_stories.cy.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cypress/e2e/{04_user_stories.ts => 04_user_stories.cy.ts} (100%) diff --git a/cypress/e2e/04_user_stories.ts b/cypress/e2e/04_user_stories.cy.ts similarity index 100% rename from cypress/e2e/04_user_stories.ts rename to cypress/e2e/04_user_stories.cy.ts From 89bc0dc8b9b06dca4c0008834e467e53a9211e62 Mon Sep 17 00:00:00 2001 From: elraphty Date: Thu, 16 May 2024 18:39:21 +0100 Subject: [PATCH 34/44] fixed conflicts --- cypress/e2e/04_user_stories.cy.ts | 31 +- db/config.go | 1 + db/features.go | 51 +++ db/interface.go | 4 + db/structs.go | 12 + handlers/features.go | 87 +++++- mocks/Database.go | 498 +++++++++++++++++++++++++----- routes/features.go | 4 + 8 files changed, 599 insertions(+), 89 deletions(-) diff --git a/cypress/e2e/04_user_stories.cy.ts b/cypress/e2e/04_user_stories.cy.ts index 537a6764d..1321ce441 100644 --- a/cypress/e2e/04_user_stories.cy.ts +++ b/cypress/e2e/04_user_stories.cy.ts @@ -35,7 +35,7 @@ describe('Modify user story description', () => { }).its('body').then(body => { expect(body).to.have.property('uuid').and.equal(UserStories[i].uuid.trim()); expect(body).to.have.property('feature_uuid').and.equal(UserStories[i].feature_uuid.trim()); - expect(body).to.have.property('description').and.equal(UserStories[i].description.trim() + "_addtext"); + expect(body).to.have.property('description').and.equal(UserStories[i].description.trim() + " _addtext"); expect(body).to.have.property('priority').and.equal(UserStories[i].priority); }); } @@ -56,7 +56,7 @@ describe('Get user stories for feature', () => { for(let i = 0; i <= 5; i++) { expect(resp.body[i]).to.have.property('uuid').and.equal(UserStories[i].uuid.trim()); expect(resp.body[i]).to.have.property('feature_uuid').and.equal(UserStories[i].feature_uuid.trim()); - expect(resp.body[i]).to.have.property('description').and.equal(UserStories[i].description.trim() + "_addtext"); + expect(resp.body[i]).to.have.property('description').and.equal(UserStories[i].description.trim() + " _addtext"); expect(resp.body[i]).to.have.property('priority').and.equal(UserStories[i].priority); } }) @@ -71,19 +71,19 @@ describe('Get story by uuid', () => { cy.request({ method: 'GET', url: `${HostName}/features/${UserStories[0].feature_uuid}/story/${UserStories[i].uuid}`, - headers: { 'x-jwt': `${ value }` }, - body: {} + headers: { 'x-jwt': `${value}` }, + body: {} }).then((resp) => { - expect(resp.status).to.eq(200) - expect(resp.body[i]).to.have.property('uuid').and.equal(UserStories[i].uuid.trim()); - expect(resp.body[i]).to.have.property('feature_uuid').and.equal(UserStories[i].feature_uuid.trim()); - expect(resp.body[i]).to.have.property('description').and.equal(UserStories[i].description.trim() + "_addtext"); - expect(resp.body[i]).to.have.property('priority').and.equal(UserStories[i].priority); - }) + expect(resp.status).to.eq(200); + expect(resp.body).to.have.property('uuid').and.equal(UserStories[i].uuid.trim()); + expect(resp.body).to.have.property('feature_uuid').and.equal(UserStories[i].feature_uuid.trim()); + expect(resp.body).to.have.property('description').and.equal(UserStories[i].description.trim() + " _addtext"); + expect(resp.body).to.have.property('priority').and.equal(UserStories[i].priority); + }); } - }) - }) -}) + }); + }); +}); describe('Delete story by uuid', () => { it('passes', () => { @@ -104,10 +104,11 @@ describe('Check delete by uuid', () => { it('passes', () => { cy.upsertlogin(User).then(value => { cy.request({ - method: 'GET', + method: 'DELETE', url: `${HostName}/features/${UserStories[0].feature_uuid}/story/${UserStories[0].uuid}`, headers: { 'x-jwt': `${ value }` }, - body: {} + body: {}, + failOnStatusCode: false }).then((resp) => { expect(resp.status).to.eq(404); }) diff --git a/db/config.go b/db/config.go index b7ab4fa20..d54c162ca 100644 --- a/db/config.go +++ b/db/config.go @@ -70,6 +70,7 @@ func InitDB() { db.AutoMigrate(&WorkspaceRepositories{}) db.AutoMigrate(&WorkspaceFeatures{}) db.AutoMigrate(&FeaturePhase{}) + db.AutoMigrate(&FeatureStory{}) DB.MigrateTablesWithOrgUuid() DB.MigrateOrganizationToWorkspace() diff --git a/db/features.go b/db/features.go index 1372674f1..2c7c1a9a4 100644 --- a/db/features.go +++ b/db/features.go @@ -115,3 +115,54 @@ func (db database) DeleteFeaturePhase(featureUuid, phaseUuid string) error { } return nil } + +func (db database) CreateOrEditFeatureStory(story FeatureStory) (FeatureStory, error) { + story.Description = strings.TrimSpace(story.Description) + + now := time.Now() + story.Updated = &now + + existingStory := FeatureStory{} + result := db.db.Model(&FeatureStory{}).Where("uuid = ?", story.Uuid).First(&existingStory) + + if result.RowsAffected == 0 { + story.Created = &now + db.db.Create(&story) + } else { + db.db.Model(&FeatureStory{}).Where("uuid = ?", story.Uuid).Updates(story) + } + + db.db.Model(&FeatureStory{}).Where("uuid = ?", story.Uuid).Find(&story) + + return story, nil +} + +func (db database) GetFeatureStoriesByFeatureUuid(featureUuid string) ([]FeatureStory, error) { + var stories []FeatureStory + result := db.db.Where("feature_uuid = ?", featureUuid).Find(&stories) + if result.Error != nil { + return nil, result.Error + } + + for i := range stories { + stories[i].Description = strings.TrimSpace(stories[i].Description) + } + return stories, nil +} + +func (db database) GetFeatureStoryByUuid(featureUuid, storyUuid string) (FeatureStory, error) { + story := FeatureStory{} + result := db.db.Model(&FeatureStory{}).Where("feature_uuid = ? AND uuid = ?", featureUuid, storyUuid).First(&story) + if result.RowsAffected == 0 { + return story, errors.New("no story found") + } + return story, nil +} + +func (db database) DeleteFeatureStoryByUuid(featureUuid, storyUuid string) error { + result := db.db.Where("feature_uuid = ? AND uuid = ?", featureUuid, storyUuid).Delete(&FeatureStory{}) + if result.RowsAffected == 0 { + return errors.New("no story found to delete") + } + return nil +} diff --git a/db/interface.go b/db/interface.go index 1643e352c..4b4585b99 100644 --- a/db/interface.go +++ b/db/interface.go @@ -150,4 +150,8 @@ type Database interface { GetPhasesByFeatureUuid(featureUuid string) []FeaturePhase GetFeaturePhaseByUuid(featureUuid, phaseUuid string) (FeaturePhase, error) DeleteFeaturePhase(featureUuid, phaseUuid string) error + CreateOrEditFeatureStory(story FeatureStory) (FeatureStory, error) + GetFeatureStoriesByFeatureUuid(featureUuid string) ([]FeatureStory, error) + GetFeatureStoryByUuid(featureUuid, storyUuid string) (FeatureStory, error) + DeleteFeatureStoryByUuid(featureUuid, storyUuid string) error } diff --git a/db/structs.go b/db/structs.go index 020b5e4b8..6988a5511 100644 --- a/db/structs.go +++ b/db/structs.go @@ -679,6 +679,18 @@ type BudgetHistory struct { PaymentType PaymentType `json:"payment_type"` } +type FeatureStory struct { + ID uint `json:"id"` + Uuid string `json:"uuid"` + FeatureUuid string `json:"feature_uuid"` + Description string `json:"description"` + Priority int `json:"priority"` + Created *time.Time `json:"created"` + Updated *time.Time `json:"updated"` + CreatedBy string `json:"created_by"` + UpdatedBy string `json:"updated_by"` +} + type BudgetHistoryData struct { BudgetHistory SenderName string `json:"sender_name"` diff --git a/handlers/features.go b/handlers/features.go index 1e2096623..b00358821 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -3,12 +3,13 @@ package handlers import ( "encoding/json" "fmt" + "io" + "net/http" + "github.com/go-chi/chi" "github.com/rs/xid" "github.com/stakwork/sphinx-tribes/auth" "github.com/stakwork/sphinx-tribes/db" - "io" - "net/http" ) type featureHandler struct { @@ -193,3 +194,85 @@ func (oh *featureHandler) DeleteFeaturePhase(w http.ResponseWriter, r *http.Requ w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "Phase deleted successfully"}) } + +func (oh *featureHandler) CreateOrEditStory(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 + } + + newStory := db.FeatureStory{} + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&newStory) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "Error decoding request body: %v", err) + return + } + + if newStory.Uuid == "" { + newStory.Uuid = xid.New().String() + } + + existingStory, _ := oh.db.GetFeatureStoryByUuid(newStory.FeatureUuid, newStory.Uuid) + + if existingStory.CreatedBy == "" { + newStory.CreatedBy = pubKeyFromAuth + } + + newStory.UpdatedBy = pubKeyFromAuth + + story, err := oh.db.CreateOrEditFeatureStory(newStory) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Fprintf(w, "Error creating feature story: %v", err) + return + } + + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(story) +} + +func (oh *featureHandler) GetStoriesByFeatureUuid(w http.ResponseWriter, r *http.Request) { + featureUuid := chi.URLParam(r, "feature_uuid") + stories, err := oh.db.GetFeatureStoriesByFeatureUuid(featureUuid) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(stories) +} + +func (oh *featureHandler) GetStoryByUuid(w http.ResponseWriter, r *http.Request) { + featureUuid := chi.URLParam(r, "feature_uuid") + storyUuid := chi.URLParam(r, "story_uuid") + + story, err := oh.db.GetFeatureStoryByUuid(featureUuid, storyUuid) + if err != nil { + w.WriteHeader(http.StatusNotFound) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(story) +} + +func (oh *featureHandler) DeleteStory(w http.ResponseWriter, r *http.Request) { + featureUuid := chi.URLParam(r, "feature_uuid") + storyUuid := chi.URLParam(r, "story_uuid") + + err := oh.db.DeleteFeatureStoryByUuid(featureUuid, storyUuid) + if err != nil { + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) + return + } + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(map[string]string{"message": "Story deleted successfully"}) +} diff --git a/mocks/Database.go b/mocks/Database.go index 92bbc00de..3028b31a2 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -986,6 +986,118 @@ func (_c *Database_CreateOrEditFeature_Call) RunAndReturn(run func(db.WorkspaceF return _c } +// CreateOrEditFeaturePhase provides a mock function with given fields: phase +func (_m *Database) CreateOrEditFeaturePhase(phase db.FeaturePhase) (db.FeaturePhase, error) { + ret := _m.Called(phase) + + if len(ret) == 0 { + panic("no return value specified for CreateOrEditFeaturePhase") + } + + var r0 db.FeaturePhase + var r1 error + if rf, ok := ret.Get(0).(func(db.FeaturePhase) (db.FeaturePhase, error)); ok { + return rf(phase) + } + if rf, ok := ret.Get(0).(func(db.FeaturePhase) db.FeaturePhase); ok { + r0 = rf(phase) + } else { + r0 = ret.Get(0).(db.FeaturePhase) + } + + if rf, ok := ret.Get(1).(func(db.FeaturePhase) error); ok { + r1 = rf(phase) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Database_CreateOrEditFeaturePhase_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateOrEditFeaturePhase' +type Database_CreateOrEditFeaturePhase_Call struct { + *mock.Call +} + +// CreateOrEditFeaturePhase is a helper method to define mock.On call +// - phase db.FeaturePhase +func (_e *Database_Expecter) CreateOrEditFeaturePhase(phase interface{}) *Database_CreateOrEditFeaturePhase_Call { + return &Database_CreateOrEditFeaturePhase_Call{Call: _e.mock.On("CreateOrEditFeaturePhase", phase)} +} + +func (_c *Database_CreateOrEditFeaturePhase_Call) Run(run func(phase db.FeaturePhase)) *Database_CreateOrEditFeaturePhase_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(db.FeaturePhase)) + }) + return _c +} + +func (_c *Database_CreateOrEditFeaturePhase_Call) Return(_a0 db.FeaturePhase, _a1 error) *Database_CreateOrEditFeaturePhase_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Database_CreateOrEditFeaturePhase_Call) RunAndReturn(run func(db.FeaturePhase) (db.FeaturePhase, error)) *Database_CreateOrEditFeaturePhase_Call { + _c.Call.Return(run) + return _c +} + +// CreateOrEditFeatureStory provides a mock function with given fields: story +func (_m *Database) CreateOrEditFeatureStory(story db.FeatureStory) (db.FeatureStory, error) { + ret := _m.Called(story) + + if len(ret) == 0 { + panic("no return value specified for CreateOrEditFeatureStory") + } + + var r0 db.FeatureStory + var r1 error + if rf, ok := ret.Get(0).(func(db.FeatureStory) (db.FeatureStory, error)); ok { + return rf(story) + } + if rf, ok := ret.Get(0).(func(db.FeatureStory) db.FeatureStory); ok { + r0 = rf(story) + } else { + r0 = ret.Get(0).(db.FeatureStory) + } + + if rf, ok := ret.Get(1).(func(db.FeatureStory) error); ok { + r1 = rf(story) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Database_CreateOrEditFeatureStory_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateOrEditFeatureStory' +type Database_CreateOrEditFeatureStory_Call struct { + *mock.Call +} + +// CreateOrEditFeatureStory is a helper method to define mock.On call +// - story db.FeatureStory +func (_e *Database_Expecter) CreateOrEditFeatureStory(story interface{}) *Database_CreateOrEditFeatureStory_Call { + return &Database_CreateOrEditFeatureStory_Call{Call: _e.mock.On("CreateOrEditFeatureStory", story)} +} + +func (_c *Database_CreateOrEditFeatureStory_Call) Run(run func(story db.FeatureStory)) *Database_CreateOrEditFeatureStory_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(db.FeatureStory)) + }) + return _c +} + +func (_c *Database_CreateOrEditFeatureStory_Call) Return(_a0 db.FeatureStory, _a1 error) *Database_CreateOrEditFeatureStory_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Database_CreateOrEditFeatureStory_Call) RunAndReturn(run func(db.FeatureStory) (db.FeatureStory, error)) *Database_CreateOrEditFeatureStory_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) @@ -1455,6 +1567,100 @@ func (_c *Database_DeleteBounty_Call) RunAndReturn(run func(string, string) (db. return _c } +// DeleteFeaturePhase provides a mock function with given fields: featureUuid, phaseUuid +func (_m *Database) DeleteFeaturePhase(featureUuid string, phaseUuid string) error { + ret := _m.Called(featureUuid, phaseUuid) + + if len(ret) == 0 { + panic("no return value specified for DeleteFeaturePhase") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(featureUuid, phaseUuid) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Database_DeleteFeaturePhase_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteFeaturePhase' +type Database_DeleteFeaturePhase_Call struct { + *mock.Call +} + +// DeleteFeaturePhase is a helper method to define mock.On call +// - featureUuid string +// - phaseUuid string +func (_e *Database_Expecter) DeleteFeaturePhase(featureUuid interface{}, phaseUuid interface{}) *Database_DeleteFeaturePhase_Call { + return &Database_DeleteFeaturePhase_Call{Call: _e.mock.On("DeleteFeaturePhase", featureUuid, phaseUuid)} +} + +func (_c *Database_DeleteFeaturePhase_Call) Run(run func(featureUuid string, phaseUuid string)) *Database_DeleteFeaturePhase_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(string)) + }) + return _c +} + +func (_c *Database_DeleteFeaturePhase_Call) Return(_a0 error) *Database_DeleteFeaturePhase_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_DeleteFeaturePhase_Call) RunAndReturn(run func(string, string) error) *Database_DeleteFeaturePhase_Call { + _c.Call.Return(run) + return _c +} + +// DeleteFeatureStoryByUuid provides a mock function with given fields: featureUuid, storyUuid +func (_m *Database) DeleteFeatureStoryByUuid(featureUuid string, storyUuid string) error { + ret := _m.Called(featureUuid, storyUuid) + + if len(ret) == 0 { + panic("no return value specified for DeleteFeatureStoryByUuid") + } + + var r0 error + if rf, ok := ret.Get(0).(func(string, string) error); ok { + r0 = rf(featureUuid, storyUuid) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Database_DeleteFeatureStoryByUuid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteFeatureStoryByUuid' +type Database_DeleteFeatureStoryByUuid_Call struct { + *mock.Call +} + +// DeleteFeatureStoryByUuid is a helper method to define mock.On call +// - featureUuid string +// - storyUuid string +func (_e *Database_Expecter) DeleteFeatureStoryByUuid(featureUuid interface{}, storyUuid interface{}) *Database_DeleteFeatureStoryByUuid_Call { + return &Database_DeleteFeatureStoryByUuid_Call{Call: _e.mock.On("DeleteFeatureStoryByUuid", featureUuid, storyUuid)} +} + +func (_c *Database_DeleteFeatureStoryByUuid_Call) Run(run func(featureUuid string, storyUuid string)) *Database_DeleteFeatureStoryByUuid_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(string)) + }) + return _c +} + +func (_c *Database_DeleteFeatureStoryByUuid_Call) Return(_a0 error) *Database_DeleteFeatureStoryByUuid_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_DeleteFeatureStoryByUuid_Call) RunAndReturn(run func(string, string) error) *Database_DeleteFeatureStoryByUuid_Call { + _c.Call.Return(run) + return _c +} + // DeleteUserInvoiceData provides a mock function with given fields: payment_request func (_m *Database) DeleteUserInvoiceData(payment_request string) db.UserInvoiceData { ret := _m.Called(payment_request) @@ -2681,6 +2887,178 @@ func (_c *Database_GetFeatureByUuid_Call) RunAndReturn(run func(string) db.Works return _c } +// GetFeaturePhaseByUuid provides a mock function with given fields: featureUuid, phaseUuid +func (_m *Database) GetFeaturePhaseByUuid(featureUuid string, phaseUuid string) (db.FeaturePhase, error) { + ret := _m.Called(featureUuid, phaseUuid) + + if len(ret) == 0 { + panic("no return value specified for GetFeaturePhaseByUuid") + } + + var r0 db.FeaturePhase + var r1 error + if rf, ok := ret.Get(0).(func(string, string) (db.FeaturePhase, error)); ok { + return rf(featureUuid, phaseUuid) + } + if rf, ok := ret.Get(0).(func(string, string) db.FeaturePhase); ok { + r0 = rf(featureUuid, phaseUuid) + } else { + r0 = ret.Get(0).(db.FeaturePhase) + } + + if rf, ok := ret.Get(1).(func(string, string) error); ok { + r1 = rf(featureUuid, phaseUuid) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Database_GetFeaturePhaseByUuid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFeaturePhaseByUuid' +type Database_GetFeaturePhaseByUuid_Call struct { + *mock.Call +} + +// GetFeaturePhaseByUuid is a helper method to define mock.On call +// - featureUuid string +// - phaseUuid string +func (_e *Database_Expecter) GetFeaturePhaseByUuid(featureUuid interface{}, phaseUuid interface{}) *Database_GetFeaturePhaseByUuid_Call { + return &Database_GetFeaturePhaseByUuid_Call{Call: _e.mock.On("GetFeaturePhaseByUuid", featureUuid, phaseUuid)} +} + +func (_c *Database_GetFeaturePhaseByUuid_Call) Run(run func(featureUuid string, phaseUuid string)) *Database_GetFeaturePhaseByUuid_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(string)) + }) + return _c +} + +func (_c *Database_GetFeaturePhaseByUuid_Call) Return(_a0 db.FeaturePhase, _a1 error) *Database_GetFeaturePhaseByUuid_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Database_GetFeaturePhaseByUuid_Call) RunAndReturn(run func(string, string) (db.FeaturePhase, error)) *Database_GetFeaturePhaseByUuid_Call { + _c.Call.Return(run) + return _c +} + +// GetFeatureStoriesByFeatureUuid provides a mock function with given fields: featureUuid +func (_m *Database) GetFeatureStoriesByFeatureUuid(featureUuid string) ([]db.FeatureStory, error) { + ret := _m.Called(featureUuid) + + if len(ret) == 0 { + panic("no return value specified for GetFeatureStoriesByFeatureUuid") + } + + var r0 []db.FeatureStory + var r1 error + if rf, ok := ret.Get(0).(func(string) ([]db.FeatureStory, error)); ok { + return rf(featureUuid) + } + if rf, ok := ret.Get(0).(func(string) []db.FeatureStory); ok { + r0 = rf(featureUuid) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.FeatureStory) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(featureUuid) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Database_GetFeatureStoriesByFeatureUuid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFeatureStoriesByFeatureUuid' +type Database_GetFeatureStoriesByFeatureUuid_Call struct { + *mock.Call +} + +// GetFeatureStoriesByFeatureUuid is a helper method to define mock.On call +// - featureUuid string +func (_e *Database_Expecter) GetFeatureStoriesByFeatureUuid(featureUuid interface{}) *Database_GetFeatureStoriesByFeatureUuid_Call { + return &Database_GetFeatureStoriesByFeatureUuid_Call{Call: _e.mock.On("GetFeatureStoriesByFeatureUuid", featureUuid)} +} + +func (_c *Database_GetFeatureStoriesByFeatureUuid_Call) Run(run func(featureUuid string)) *Database_GetFeatureStoriesByFeatureUuid_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Database_GetFeatureStoriesByFeatureUuid_Call) Return(_a0 []db.FeatureStory, _a1 error) *Database_GetFeatureStoriesByFeatureUuid_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Database_GetFeatureStoriesByFeatureUuid_Call) RunAndReturn(run func(string) ([]db.FeatureStory, error)) *Database_GetFeatureStoriesByFeatureUuid_Call { + _c.Call.Return(run) + return _c +} + +// GetFeatureStoryByUuid provides a mock function with given fields: featureUuid, storyUuid +func (_m *Database) GetFeatureStoryByUuid(featureUuid string, storyUuid string) (db.FeatureStory, error) { + ret := _m.Called(featureUuid, storyUuid) + + if len(ret) == 0 { + panic("no return value specified for GetFeatureStoryByUuid") + } + + var r0 db.FeatureStory + var r1 error + if rf, ok := ret.Get(0).(func(string, string) (db.FeatureStory, error)); ok { + return rf(featureUuid, storyUuid) + } + if rf, ok := ret.Get(0).(func(string, string) db.FeatureStory); ok { + r0 = rf(featureUuid, storyUuid) + } else { + r0 = ret.Get(0).(db.FeatureStory) + } + + if rf, ok := ret.Get(1).(func(string, string) error); ok { + r1 = rf(featureUuid, storyUuid) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Database_GetFeatureStoryByUuid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFeatureStoryByUuid' +type Database_GetFeatureStoryByUuid_Call struct { + *mock.Call +} + +// GetFeatureStoryByUuid is a helper method to define mock.On call +// - featureUuid string +// - storyUuid string +func (_e *Database_Expecter) GetFeatureStoryByUuid(featureUuid interface{}, storyUuid interface{}) *Database_GetFeatureStoryByUuid_Call { + return &Database_GetFeatureStoryByUuid_Call{Call: _e.mock.On("GetFeatureStoryByUuid", featureUuid, storyUuid)} +} + +func (_c *Database_GetFeatureStoryByUuid_Call) Run(run func(featureUuid string, storyUuid string)) *Database_GetFeatureStoryByUuid_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string), args[1].(string)) + }) + return _c +} + +func (_c *Database_GetFeatureStoryByUuid_Call) Return(_a0 db.FeatureStory, _a1 error) *Database_GetFeatureStoryByUuid_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Database_GetFeatureStoryByUuid_Call) RunAndReturn(run func(string, string) (db.FeatureStory, error)) *Database_GetFeatureStoryByUuid_Call { + _c.Call.Return(run) + return _c +} + // GetFeaturesByWorkspaceUuid provides a mock function with given fields: uuid, r func (_m *Database) GetFeaturesByWorkspaceUuid(uuid string, r *http.Request) []db.WorkspaceFeatures { ret := _m.Called(uuid, r) @@ -3858,6 +4236,54 @@ func (_c *Database_GetPersonByUuid_Call) RunAndReturn(run func(string) db.Person return _c } +// GetPhasesByFeatureUuid provides a mock function with given fields: featureUuid +func (_m *Database) GetPhasesByFeatureUuid(featureUuid string) []db.FeaturePhase { + ret := _m.Called(featureUuid) + + if len(ret) == 0 { + panic("no return value specified for GetPhasesByFeatureUuid") + } + + var r0 []db.FeaturePhase + if rf, ok := ret.Get(0).(func(string) []db.FeaturePhase); ok { + r0 = rf(featureUuid) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]db.FeaturePhase) + } + } + + return r0 +} + +// Database_GetPhasesByFeatureUuid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetPhasesByFeatureUuid' +type Database_GetPhasesByFeatureUuid_Call struct { + *mock.Call +} + +// GetPhasesByFeatureUuid is a helper method to define mock.On call +// - featureUuid string +func (_e *Database_Expecter) GetPhasesByFeatureUuid(featureUuid interface{}) *Database_GetPhasesByFeatureUuid_Call { + return &Database_GetPhasesByFeatureUuid_Call{Call: _e.mock.On("GetPhasesByFeatureUuid", featureUuid)} +} + +func (_c *Database_GetPhasesByFeatureUuid_Call) Run(run func(featureUuid string)) *Database_GetPhasesByFeatureUuid_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Database_GetPhasesByFeatureUuid_Call) Return(_a0 []db.FeaturePhase) *Database_GetPhasesByFeatureUuid_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_GetPhasesByFeatureUuid_Call) RunAndReturn(run func(string) []db.FeaturePhase) *Database_GetPhasesByFeatureUuid_Call { + _c.Call.Return(run) + return _c +} + // GetPreviousBountyByCreated provides a mock function with given fields: r func (_m *Database) GetPreviousBountyByCreated(r *http.Request) (uint, error) { ret := _m.Called(r) @@ -6811,75 +7237,3 @@ func NewDatabase(t interface { return mock } - -// CreateOrEditFeaturePhase provides a mock function with given fields: phase -func (_m *Database) CreateOrEditFeaturePhase(phase db.FeaturePhase) (db.FeaturePhase, error) { - ret := _m.Called(phase) - - var r0 db.FeaturePhase - var r1 error - if rf, ok := ret.Get(0).(func(db.FeaturePhase) db.FeaturePhase); ok { - r0 = rf(phase) - } else { - r0 = ret.Get(0).(db.FeaturePhase) - } - - if rf, ok := ret.Get(1).(func(db.FeaturePhase) error); ok { - r1 = rf(phase) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// GetPhasesByFeatureUuid provides a mock function with given fields: featureUuid -func (_m *Database) GetPhasesByFeatureUuid(featureUuid string) []db.FeaturePhase { - ret := _m.Called(featureUuid) - - var r0 []db.FeaturePhase - if rf, ok := ret.Get(0).(func(string) []db.FeaturePhase); ok { - r0 = rf(featureUuid) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]db.FeaturePhase) - } - } - - return r0 -} - -// GetFeaturePhaseByUuid provides a mock function with given fields: featureUuid, phaseUuid -func (_m *Database) GetFeaturePhaseByUuid(featureUuid, phaseUuid string) (db.FeaturePhase, error) { - ret := _m.Called(featureUuid, phaseUuid) - - var r0 db.FeaturePhase - var r1 error - if rf, ok := ret.Get(0).(func(string, string) db.FeaturePhase); ok { - r0 = rf(featureUuid, phaseUuid) - } else { - r0 = ret.Get(0).(db.FeaturePhase) - } - - if rf, ok := ret.Get(1).(func(string, string) error); ok { - r1 = rf(featureUuid, phaseUuid) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// DeleteFeaturePhase provides a mock function with given fields: featureUuid, phaseUuid -func (_m *Database) DeleteFeaturePhase(featureUuid string, phaseUuid string) error { - ret := _m.Called(featureUuid, phaseUuid) - - var r1 error - if rf, ok := ret.Get(0).(func(string, string) error); ok { - r1 = rf(featureUuid, phaseUuid) - } else { - r1 = ret.Error(0) - } - - return r1 -} diff --git a/routes/features.go b/routes/features.go index 405b7ce60..e43bb1b3b 100644 --- a/routes/features.go +++ b/routes/features.go @@ -23,6 +23,10 @@ func FeatureRoutes() chi.Router { r.Get("/{feature_uuid}/phase/{phase_uuid}", featureHandlers.GetFeaturePhaseByUUID) r.Delete("/{feature_uuid}/phase/{phase_uuid}", featureHandlers.DeleteFeaturePhase) + r.Post("/story", featureHandlers.CreateOrEditStory) + r.Get("/{feature_uuid}/story", featureHandlers.GetStoriesByFeatureUuid) + r.Get("/{feature_uuid}/story/{story_uuid}", featureHandlers.GetStoryByUuid) + r.Delete("/{feature_uuid}/story/{story_uuid}", featureHandlers.DeleteStory) }) return r } From 734ac40f7fd7b64ee578df2edbb1724220162352 Mon Sep 17 00:00:00 2001 From: Mirza Date: Wed, 15 May 2024 12:23:22 +0500 Subject: [PATCH 35/44] Add User Stories to Features small changes --- handlers/features.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/handlers/features.go b/handlers/features.go index b00358821..ce684ee15 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -3,13 +3,12 @@ package handlers import ( "encoding/json" "fmt" - "io" - "net/http" - "github.com/go-chi/chi" "github.com/rs/xid" "github.com/stakwork/sphinx-tribes/auth" "github.com/stakwork/sphinx-tribes/db" + "io" + "net/http" ) type featureHandler struct { @@ -261,7 +260,6 @@ func (oh *featureHandler) GetStoryByUuid(w http.ResponseWriter, r *http.Request) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(story) } - func (oh *featureHandler) DeleteStory(w http.ResponseWriter, r *http.Request) { featureUuid := chi.URLParam(r, "feature_uuid") storyUuid := chi.URLParam(r, "story_uuid") From b45748d4dae59368d0c29a094ee1b0ee57d8457d Mon Sep 17 00:00:00 2001 From: elraphty Date: Thu, 16 May 2024 17:11:58 +0100 Subject: [PATCH 36/44] fixed feature cypress test --- cypress/e2e/03_features.cy.ts | 37 ++++++++++++++++--------------- cypress/e2e/04_user_stories.cy.ts | 18 +++++++-------- db/features.go | 2 ++ handlers/features.go | 5 +++-- 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/cypress/e2e/03_features.cy.ts b/cypress/e2e/03_features.cy.ts index dd2c66424..10e56a834 100644 --- a/cypress/e2e/03_features.cy.ts +++ b/cypress/e2e/03_features.cy.ts @@ -1,11 +1,11 @@ -import { User, HostName, Workspaces, Repositories, Features } from '../support/objects/objects'; +import { User, HostName, Features } from '../support/objects/objects'; describe('Create Features for Workspace', () => { it('passes', () => { cy.upsertlogin(User).then(value => { - for(let i = 0; i <= 2; i++) { + for (let i = 0; i <= 2; i++) { cy.request({ method: 'POST', url: `${HostName}/features`, @@ -25,7 +25,7 @@ describe('Create Features for Workspace', () => { describe('Modify name for Feature', () => { it('passes', () => { cy.upsertlogin(User).then(value => { - for(let i = 0; i <= 2; i++) { + for (let i = 0; i <= 2; i++) { cy.request({ method: 'POST', url: `${HostName}/features`, @@ -48,7 +48,7 @@ describe('Modify name for Feature', () => { describe('Modify brief for Feature', () => { it('passes', () => { cy.upsertlogin(User).then(value => { - for(let i = 0; i <= 2; i++) { + for (let i = 0; i <= 2; i++) { cy.request({ method: 'POST', url: `${HostName}/features`, @@ -71,7 +71,7 @@ describe('Modify brief for Feature', () => { describe('Modify requirements for Feature', () => { it('passes', () => { cy.upsertlogin(User).then(value => { - for(let i = 0; i <= 2; i++) { + for (let i = 0; i <= 2; i++) { cy.request({ method: 'POST', url: `${HostName}/features`, @@ -94,7 +94,7 @@ describe('Modify requirements for Feature', () => { describe('Modify architecture for Feature', () => { it('passes', () => { cy.upsertlogin(User).then(value => { - for(let i = 0; i <= 2; i++) { + for (let i = 0; i <= 2; i++) { cy.request({ method: 'POST', url: `${HostName}/features`, @@ -121,17 +121,18 @@ describe('Get Features for Workspace', () => { cy.request({ method: 'GET', url: `${HostName}/features/forworkspace/` + Features[0].workspace_uuid, - headers: { 'x-jwt': `${ value }` }, - body: {} + 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") + expect(resp.status).to.eq(200); + const body = resp.body.reverse(); + for (let i = 0; i <= 2; i++) { + expect(body[i]).to.have.property('name', Features[i].name.trim() + " _addtext") + expect(body[i]).to.have.property('brief', Features[i].brief.trim() + " _addtext") + expect(body[i]).to.have.property('requirements', Features[i].requirements.trim() + " _addtext") + expect(body[i]).to.have.property('architecture', Features[i].architecture.trim() + " _addtext") } - }) + }); }) }) }) @@ -139,12 +140,12 @@ describe('Get Features for Workspace', () => { describe('Get Feature by uuid', () => { it('passes', () => { cy.upsertlogin(User).then(value => { - for(let i = 0; i <= 2; i++) { + for (let i = 0; i <= 2; i++) { cy.request({ method: 'GET', url: `${HostName}/features/` + Features[i].uuid, - headers: { 'x-jwt': `${ value }` }, - body: {} + 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") diff --git a/cypress/e2e/04_user_stories.cy.ts b/cypress/e2e/04_user_stories.cy.ts index 1321ce441..3bca14d24 100644 --- a/cypress/e2e/04_user_stories.cy.ts +++ b/cypress/e2e/04_user_stories.cy.ts @@ -3,7 +3,7 @@ import { User, HostName, UserStories } from '../support/objects/objects'; describe('Create user stories for Feature', () => { it('passes', () => { cy.upsertlogin(User).then(value => { - for(let i = 0; i <= 5; i++) { + for (let i = 0; i <= 5; i++) { cy.request({ method: 'POST', url: `${HostName}/features/story`, @@ -23,7 +23,7 @@ describe('Create user stories for Feature', () => { describe('Modify user story description', () => { it('passes', () => { cy.upsertlogin(User).then(value => { - for(let i = 0; i <= 5; i++) { + for (let i = 0; i <= 5; i++) { cy.request({ method: 'POST', url: `${HostName}/features/story`, @@ -49,11 +49,11 @@ describe('Get user stories for feature', () => { cy.request({ method: 'GET', url: `${HostName}/features/${UserStories[0].feature_uuid}/story`, - headers: { 'x-jwt': `${ value }` }, - body: {} + headers: { 'x-jwt': `${value}` }, + body: {} }).then((resp) => { expect(resp.status).to.eq(200) - for(let i = 0; i <= 5; i++) { + for (let i = 0; i <= 5; i++) { expect(resp.body[i]).to.have.property('uuid').and.equal(UserStories[i].uuid.trim()); expect(resp.body[i]).to.have.property('feature_uuid').and.equal(UserStories[i].feature_uuid.trim()); expect(resp.body[i]).to.have.property('description').and.equal(UserStories[i].description.trim() + " _addtext"); @@ -67,7 +67,7 @@ describe('Get user stories for feature', () => { describe('Get story by uuid', () => { it('passes', () => { cy.upsertlogin(User).then(value => { - for(let i = 0; i <= 5; i++) { + for (let i = 0; i <= 5; i++) { cy.request({ method: 'GET', url: `${HostName}/features/${UserStories[0].feature_uuid}/story/${UserStories[i].uuid}`, @@ -91,8 +91,8 @@ describe('Delete story by uuid', () => { cy.request({ method: 'DELETE', url: `${HostName}/features/${UserStories[0].feature_uuid}/story/${UserStories[0].uuid}`, - headers: { 'x-jwt': `${ value }` }, - body: {} + headers: { 'x-jwt': `${value}` }, + body: {} }).then((resp) => { expect(resp.status).to.eq(200) }) @@ -106,7 +106,7 @@ describe('Check delete by uuid', () => { cy.request({ method: 'DELETE', url: `${HostName}/features/${UserStories[0].feature_uuid}/story/${UserStories[0].uuid}`, - headers: { 'x-jwt': `${ value }` }, + headers: { 'x-jwt': `${value}` }, body: {}, failOnStatusCode: false }).then((resp) => { diff --git a/db/features.go b/db/features.go index 2c7c1a9a4..3d4f585a0 100644 --- a/db/features.go +++ b/db/features.go @@ -67,6 +67,8 @@ func (db database) CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, db.db.Create(&m) } + db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", m.Uuid).Find(&m) + return m, nil } diff --git a/handlers/features.go b/handlers/features.go index ce684ee15..7dd7b4fe2 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -3,12 +3,13 @@ package handlers import ( "encoding/json" "fmt" + "io" + "net/http" + "github.com/go-chi/chi" "github.com/rs/xid" "github.com/stakwork/sphinx-tribes/auth" "github.com/stakwork/sphinx-tribes/db" - "io" - "net/http" ) type featureHandler struct { From e07cb5e5f616134fde1cc33c2d321ddeac28076f Mon Sep 17 00:00:00 2001 From: elraphty Date: Thu, 16 May 2024 18:42:31 +0100 Subject: [PATCH 37/44] deleted JS file --- .../e2e/{06_phases.cy.ts => 05_phases.cy.ts} | 0 cypress/e2e/06_phases.cy.js | 116 ------------------ 2 files changed, 116 deletions(-) rename cypress/e2e/{06_phases.cy.ts => 05_phases.cy.ts} (100%) delete mode 100644 cypress/e2e/06_phases.cy.js diff --git a/cypress/e2e/06_phases.cy.ts b/cypress/e2e/05_phases.cy.ts similarity index 100% rename from cypress/e2e/06_phases.cy.ts rename to cypress/e2e/05_phases.cy.ts diff --git a/cypress/e2e/06_phases.cy.js b/cypress/e2e/06_phases.cy.js deleted file mode 100644 index d74b0bbf2..000000000 --- a/cypress/e2e/06_phases.cy.js +++ /dev/null @@ -1,116 +0,0 @@ -import { User, HostName, UserStories, Phases } from '../support/objects/objects'; - -describe('Create Phases for Feature', () => { - it('passes', () => { - cy.upsertlogin(User).then(value => { - for(let i = 0; i <= 2; i++) { - cy.request({ - method: 'POST', - url: `${HostName}/features/phase`, - headers: { 'x-jwt': `${value}` }, - body: Phases[i] - }).its('body').then(body => { - expect(body).to.have.property('uuid').and.equal(Phases[i].uuid.trim()); - expect(body).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); - expect(body).to.have.property('name').and.equal(Phases[i].name.trim()); - expect(body).to.have.property('priority').and.equal(Phases[i].priority); - }); - } - }) - }) -}) - -describe('Modify phases name', () => { - it('passes', () => { - cy.upsertlogin(User).then(value => { - for(let i = 0; i <= 2; i++) { - cy.request({ - method: 'POST', - url: `${HostName}/features/phase`, - headers: { 'x-jwt': `${value}` }, - body: { - uuid: Phases[i].uuid, - name: Phases[i].name + "_addtext" - } - }).its('body').then(body => { - expect(body).to.have.property('uuid').and.equal(Phases[i].uuid.trim()); - expect(body).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); - expect(body).to.have.property('name').and.equal(Phases[i].name.trim() + "_addtext"); - expect(body).to.have.property('priority').and.equal(Phases[i].priority); - }); - } - }) - }) -}) - -describe('Get phases for feature', () => { - it('passes', () => { - cy.upsertlogin(User).then(value => { - cy.request({ - method: 'GET', - url: `${HostName}/features/${Phases[0].feature_uuid}/phase`, - 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('uuid').and.equal(Phases[i].uuid.trim()); - expect(resp.body[i]).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); - expect(resp.body[i]).to.have.property('name').and.equal(Phases[i].name.trim() + "_addtext"); - expect(resp.body[i]).to.have.property('priority').and.equal(Phases[i].priority); - } - }) - }) - }) -}) - -describe('Get phase by uuid', () => { - it('passes', () => { - cy.upsertlogin(User).then(value => { - for(let i = 0; i <= 2; i++) { - cy.request({ - method: 'GET', - url: `${HostName}/features/${Phases[0].feature_uuid}/phase/${Phases[i].uuid}`, - headers: { 'x-jwt': `${ value }` }, - body: {} - }).then((resp) => { - expect(resp.status).to.eq(200) - expect(resp.body[i]).to.have.property('uuid').and.equal(Phases[i].uuid.trim()); - expect(resp.body[i]).to.have.property('feature_uuid').and.equal(Phases[i].feature_uuid.trim()); - expect(resp.body[i]).to.have.property('name').and.equal(Phases[i].name.trim() + "_addtext"); - expect(resp.body[i]).to.have.property('priority').and.equal(Phases[i].priority); - }) - } - }) - }) -}) - -describe('Delete phase by uuid', () => { - it('passes', () => { - cy.upsertlogin(User).then(value => { - cy.request({ - method: 'DELETE', - url: `${HostName}/features/${Phases[0].feature_uuid}/phase/${Phases[0].uuid}`, - headers: { 'x-jwt': `${ value }` }, - body: {} - }).then((resp) => { - expect(resp.status).to.eq(200) - }) - }) - }) -}) - -describe('Check delete by uuid', () => { - it('passes', () => { - cy.upsertlogin(User).then(value => { - cy.request({ - method: 'GET', - url: `${HostName}/features/${Phases[0].feature_uuid}/phase/${Phases[0].uuid}`, - headers: { 'x-jwt': `${ value }` }, - body: {} - }).then((resp) => { - expect(resp.status).to.eq(404); - }) - }) - }) -}) From 8cddf92e678ab92eed5a7260c729643aa37b77b8 Mon Sep 17 00:00:00 2001 From: l Date: Sat, 11 May 2024 20:39:29 -0400 Subject: [PATCH 38/44] Added TDD --- cypress/e2e/03_features.cy.ts | 36 ++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/cypress/e2e/03_features.cy.ts b/cypress/e2e/03_features.cy.ts index 10e56a834..54c14ece6 100644 --- a/cypress/e2e/03_features.cy.ts +++ b/cypress/e2e/03_features.cy.ts @@ -120,9 +120,9 @@ describe('Get Features for Workspace', () => { cy.upsertlogin(User).then(value => { cy.request({ method: 'GET', - url: `${HostName}/features/forworkspace/` + Features[0].workspace_uuid, - headers: { 'x-jwt': `${value}` }, - body: {} + url: `${HostName}/workspaces/${Features[0].workspace_uuid}/features`, //changed from url: `${HostName}/features/forworkspace/` + Features[0].workspace_uuid, please update the routes file and any other change needed. + headers: { 'x-jwt': `${ value }` }, + body: {} }).then((resp) => { expect(resp.status).to.eq(200); const body = resp.body.reverse(); @@ -157,3 +157,33 @@ describe('Get Feature by uuid', () => { }) }) }) + +describe('Delete Feature by uuid', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + cy.request({ + method: 'DELETE', + url: `${HostName}/features/${Features[0].uuid}`, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(200) + }) + }) + }) +}) + +describe('Check delete by uuid', () => { + it('passes', () => { + cy.upsertlogin(User).then(value => { + cy.request({ + method: 'GET', + url: `${HostName}/features/${Features[0].uuid}`, + headers: { 'x-jwt': `${ value }` }, + body: {} + }).then((resp) => { + expect(resp.status).to.eq(404); + }) + }) + }) +}) From b156e1990715756a3774cf688b9e135b96d84178 Mon Sep 17 00:00:00 2001 From: MahtabBukhari Date: Thu, 16 May 2024 05:41:13 +0500 Subject: [PATCH 39/44] Modify Features endpoint and add delete feature --- cypress/e2e/03_features.cy.ts | 29 ++++++++++++++++++++++++---- db/config.go | 4 ++++ db/features.go | 36 +++++++++++++++++++++++++++++++++++ db/interface.go | 7 +++++++ db/structs.go | 6 ++++++ handlers/features.go | 24 +++++++++++++++++++++++ handlers/workspaces.go | 9 +++++++++ routes/features.go | 7 +++++++ routes/workspaces.go | 4 ++++ 9 files changed, 122 insertions(+), 4 deletions(-) diff --git a/cypress/e2e/03_features.cy.ts b/cypress/e2e/03_features.cy.ts index 54c14ece6..00a8f08a6 100644 --- a/cypress/e2e/03_features.cy.ts +++ b/cypress/e2e/03_features.cy.ts @@ -122,8 +122,9 @@ describe('Get Features for Workspace', () => { method: 'GET', url: `${HostName}/workspaces/${Features[0].workspace_uuid}/features`, //changed from url: `${HostName}/features/forworkspace/` + Features[0].workspace_uuid, please update the routes file and any other change needed. headers: { 'x-jwt': `${ value }` }, - body: {} + body: {} }).then((resp) => { +<<<<<<< HEAD expect(resp.status).to.eq(200); const body = resp.body.reverse(); for (let i = 0; i <= 2; i++) { @@ -133,6 +134,17 @@ describe('Get Features for Workspace', () => { expect(body[i]).to.have.property('architecture', Features[i].architecture.trim() + " _addtext") } }); +======= + expect(resp.status).to.eq(200) + resp.body.forEach((feature) => { + const expectedFeature = Features.find(f => f.uuid === feature.uuid); + expect(feature).to.have.property('name', expectedFeature.name.trim() + " _addtext"); + expect(feature).to.have.property('brief', expectedFeature.brief.trim() + " _addtext"); + expect(feature).to.have.property('requirements', expectedFeature.requirements.trim() + " _addtext"); + expect(feature).to.have.property('architecture', expectedFeature.architecture.trim() + " _addtext"); + }); + }) +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) }) }) }) @@ -144,7 +156,11 @@ describe('Get Feature by uuid', () => { cy.request({ method: 'GET', url: `${HostName}/features/` + Features[i].uuid, +<<<<<<< HEAD headers: { 'x-jwt': `${value}` }, +======= + headers: { 'x-jwt': `${ value }` }, +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) body: {} }).then((resp) => { expect(resp.status).to.eq(200) @@ -163,7 +179,7 @@ describe('Delete Feature by uuid', () => { cy.upsertlogin(User).then(value => { cy.request({ method: 'DELETE', - url: `${HostName}/features/${Features[0].uuid}`, + url: `${HostName}/features/${Features[2].uuid}`, headers: { 'x-jwt': `${ value }` }, body: {} }).then((resp) => { @@ -177,10 +193,15 @@ describe('Check delete by uuid', () => { it('passes', () => { cy.upsertlogin(User).then(value => { cy.request({ - method: 'GET', - url: `${HostName}/features/${Features[0].uuid}`, + method: 'DELETE', + url: `${HostName}/features/${Features[2].uuid}`, headers: { 'x-jwt': `${ value }` }, +<<<<<<< HEAD body: {} +======= + body: {}, + failOnStatusCode: false +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) }).then((resp) => { expect(resp.status).to.eq(404); }) diff --git a/db/config.go b/db/config.go index d54c162ca..b5824670e 100644 --- a/db/config.go +++ b/db/config.go @@ -67,10 +67,14 @@ func InitDB() { db.AutoMigrate(&ConnectionCodes{}) db.AutoMigrate(&BountyRoles{}) db.AutoMigrate(&UserInvoiceData{}) +<<<<<<< HEAD db.AutoMigrate(&WorkspaceRepositories{}) db.AutoMigrate(&WorkspaceFeatures{}) db.AutoMigrate(&FeaturePhase{}) db.AutoMigrate(&FeatureStory{}) +======= + db.AutoMigrate(&WorkspaceFeatures{}) +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) DB.MigrateTablesWithOrgUuid() DB.MigrateOrganizationToWorkspace() diff --git a/db/features.go b/db/features.go index 3d4f585a0..c4a5a47c0 100644 --- a/db/features.go +++ b/db/features.go @@ -3,11 +3,18 @@ package db import ( "errors" "fmt" +<<<<<<< HEAD "net/http" "strings" "time" "github.com/stakwork/sphinx-tribes/utils" +======= + "github.com/stakwork/sphinx-tribes/utils" + "net/http" + "strings" + "time" +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) ) func (db database) GetFeaturesByWorkspaceUuid(uuid string, r *http.Request) []WorkspaceFeatures { @@ -58,6 +65,7 @@ func (db database) CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, m.Brief = strings.TrimSpace(m.Brief) m.Requirements = strings.TrimSpace(m.Requirements) m.Architecture = strings.TrimSpace(m.Architecture) +<<<<<<< HEAD now := time.Now() m.Updated = &now @@ -167,4 +175,32 @@ func (db database) DeleteFeatureStoryByUuid(featureUuid, storyUuid string) error return errors.New("no story found to delete") } return nil +======= + now := time.Now() + m.Updated = &now + + var existing WorkspaceFeatures + result := db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", m.Uuid).First(&existing) + if result.RowsAffected == 0 { + + m.Created = &now + db.db.Create(&m) + } else { + + db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", m.Uuid).Updates(m) + } + + db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", m.Uuid).First(&m) + return m, nil +} + +func (db database) DeleteFeatureByUuid(uuid string) error { + result := db.db.Where("uuid = ?", uuid).Delete(&WorkspaceFeatures{}) + + if result.RowsAffected == 0 { + return errors.New("no feature found to delete") + } + return nil + +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) } diff --git a/db/interface.go b/db/interface.go index 4b4585b99..7e7d11aa3 100644 --- a/db/interface.go +++ b/db/interface.go @@ -140,12 +140,16 @@ type Database interface { PersonUniqueNameFromName(name string) (string, error) ProcessAlerts(p Person) UserHasAccess(pubKeyFromAuth string, uuid string, role string) bool +<<<<<<< HEAD CreateWorkspaceRepository(m WorkspaceRepositories) (WorkspaceRepositories, error) GetWorkspaceRepositorByWorkspaceUuid(uuid string) []WorkspaceRepositories +======= +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, error) GetFeaturesByWorkspaceUuid(uuid string, r *http.Request) []WorkspaceFeatures GetWorkspaceFeaturesCount(uuid string) int64 GetFeatureByUuid(uuid string) WorkspaceFeatures +<<<<<<< HEAD CreateOrEditFeaturePhase(phase FeaturePhase) (FeaturePhase, error) GetPhasesByFeatureUuid(featureUuid string) []FeaturePhase GetFeaturePhaseByUuid(featureUuid, phaseUuid string) (FeaturePhase, error) @@ -154,4 +158,7 @@ type Database interface { GetFeatureStoriesByFeatureUuid(featureUuid string) ([]FeatureStory, error) GetFeatureStoryByUuid(featureUuid, storyUuid string) (FeatureStory, error) DeleteFeatureStoryByUuid(featureUuid, storyUuid string) error +======= + DeleteFeatureByUuid(uuid string) error +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) } diff --git a/db/structs.go b/db/structs.go index 6988a5511..aee1443ac 100644 --- a/db/structs.go +++ b/db/structs.go @@ -551,6 +551,7 @@ type WorkspaceUsersData struct { Person } +<<<<<<< HEAD type WorkspaceRepositories struct { ID uint `json:"id"` Uuid string `gorm:"not null" json:"uuid"` @@ -563,6 +564,8 @@ type WorkspaceRepositories struct { UpdatedBy string `json:"updated_by"` } +======= +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) type WorkspaceFeatures struct { ID uint `json:"id"` Uuid string `gorm:"not null" json:"uuid"` @@ -577,6 +580,7 @@ type WorkspaceFeatures struct { UpdatedBy string `json:"updated_by"` } +<<<<<<< HEAD type FeaturePhase struct { Uuid string `json:"uuid" gorm:"primary_key"` FeatureUuid string `json:"feature_uuid"` @@ -588,6 +592,8 @@ type FeaturePhase struct { UpdatedBy string `json:"updated_by"` } +======= +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) type BountyRoles struct { Name string `json:"name"` } diff --git a/handlers/features.go b/handlers/features.go index 7dd7b4fe2..3d388601f 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -3,13 +3,21 @@ package handlers import ( "encoding/json" "fmt" +<<<<<<< HEAD "io" "net/http" +======= +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) "github.com/go-chi/chi" "github.com/rs/xid" "github.com/stakwork/sphinx-tribes/auth" "github.com/stakwork/sphinx-tribes/db" +<<<<<<< HEAD +======= + "io" + "net/http" +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) ) type featureHandler struct { @@ -69,6 +77,7 @@ func (oh *featureHandler) CreateOrEditFeatures(w http.ResponseWriter, r *http.Re json.NewEncoder(w).Encode(p) } +<<<<<<< HEAD func (oh *featureHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) @@ -85,6 +94,8 @@ func (oh *featureHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r *h json.NewEncoder(w).Encode(workspaceFeatures) } +======= +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) func (oh *featureHandler) GetWorkspaceFeaturesCount(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) @@ -117,7 +128,11 @@ func (oh *featureHandler) GetFeatureByUuid(w http.ResponseWriter, r *http.Reques json.NewEncoder(w).Encode(workspaceFeature) } +<<<<<<< HEAD func (oh *featureHandler) CreateOrEditFeaturePhase(w http.ResponseWriter, r *http.Request) { +======= +func (oh *featureHandler) DeleteFeature(w http.ResponseWriter, r *http.Request) { +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) if pubKeyFromAuth == "" { @@ -126,6 +141,7 @@ func (oh *featureHandler) CreateOrEditFeaturePhase(w http.ResponseWriter, r *htt return } +<<<<<<< HEAD newPhase := db.FeaturePhase{} decoder := json.NewDecoder(r.Body) err := decoder.Decode(&newPhase) @@ -185,6 +201,10 @@ func (oh *featureHandler) DeleteFeaturePhase(w http.ResponseWriter, r *http.Requ phaseUuid := chi.URLParam(r, "phase_uuid") err := oh.db.DeleteFeaturePhase(featureUuid, phaseUuid) +======= + uuid := chi.URLParam(r, "uuid") + err := oh.db.DeleteFeatureByUuid(uuid) +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) if err != nil { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) @@ -192,6 +212,7 @@ func (oh *featureHandler) DeleteFeaturePhase(w http.ResponseWriter, r *http.Requ } w.WriteHeader(http.StatusOK) +<<<<<<< HEAD json.NewEncoder(w).Encode(map[string]string{"message": "Phase deleted successfully"}) } @@ -274,4 +295,7 @@ func (oh *featureHandler) DeleteStory(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "Story deleted successfully"}) +======= + fmt.Fprint(w, "Feature deleted successfully") +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) } diff --git a/handlers/workspaces.go b/handlers/workspaces.go index 5ed2f09e6..e743e8bdb 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -783,7 +783,11 @@ func (oh *workspaceHandler) UpdateWorkspace(w http.ResponseWriter, r *http.Reque json.NewEncoder(w).Encode(p) } +<<<<<<< HEAD func (oh *workspaceHandler) CreateWorkspaceRepository(w http.ResponseWriter, r *http.Request) { +======= +func (oh *workspaceHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r *http.Request) { +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) if pubKeyFromAuth == "" { @@ -792,6 +796,7 @@ func (oh *workspaceHandler) CreateWorkspaceRepository(w http.ResponseWriter, r * return } +<<<<<<< HEAD workspaceRepo := db.WorkspaceRepositories{} body, _ := io.ReadAll(r.Body) r.Body.Close() @@ -841,6 +846,10 @@ func (oh *workspaceHandler) GetWorkspaceRepositorByWorkspaceUuid(w http.Response uuid := chi.URLParam(r, "uuid") workspaceFeatures := oh.db.GetWorkspaceRepositorByWorkspaceUuid(uuid) +======= + uuid := chi.URLParam(r, "workspace_uuid") + workspaceFeatures := oh.db.GetFeaturesByWorkspaceUuid(uuid, r) +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(workspaceFeatures) diff --git a/routes/features.go b/routes/features.go index e43bb1b3b..3cae724b6 100644 --- a/routes/features.go +++ b/routes/features.go @@ -14,6 +14,7 @@ func FeatureRoutes() chi.Router { r.Use(auth.PubKeyContext) r.Post("/", featureHandlers.CreateOrEditFeatures) +<<<<<<< HEAD r.Get("/forworkspace/{uuid}", featureHandlers.GetFeaturesByWorkspaceUuid) r.Get("/{uuid}", featureHandlers.GetFeatureByUuid) r.Get("/workspace/count/{uuid}", featureHandlers.GetWorkspaceFeaturesCount) @@ -27,6 +28,12 @@ func FeatureRoutes() chi.Router { r.Get("/{feature_uuid}/story", featureHandlers.GetStoriesByFeatureUuid) r.Get("/{feature_uuid}/story/{story_uuid}", featureHandlers.GetStoryByUuid) r.Delete("/{feature_uuid}/story/{story_uuid}", featureHandlers.DeleteStory) +======= + r.Get("/{uuid}", featureHandlers.GetFeatureByUuid) + r.Get("/workspace/count/{uuid}", featureHandlers.GetWorkspaceFeaturesCount) + r.Delete("/{uuid}", featureHandlers.DeleteFeature) + +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) }) return r } diff --git a/routes/workspaces.go b/routes/workspaces.go index d02c6e956..90f13262b 100644 --- a/routes/workspaces.go +++ b/routes/workspaces.go @@ -42,9 +42,13 @@ func WorkspaceRoutes() chi.Router { r.Post("/mission", workspaceHandlers.UpdateWorkspace) r.Post("/tactics", workspaceHandlers.UpdateWorkspace) r.Post("/schematicurl", workspaceHandlers.UpdateWorkspace) +<<<<<<< HEAD r.Post("/repositories", workspaceHandlers.CreateWorkspaceRepository) r.Get("/repositories/{uuid}", workspaceHandlers.GetWorkspaceRepositorByWorkspaceUuid) +======= + r.Get("/{workspace_uuid}/features", workspaceHandlers.GetFeaturesByWorkspaceUuid) +>>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) }) return r } From 9addf23b11d3a0d1e70184d82d3f698ce9aca369 Mon Sep 17 00:00:00 2001 From: l Date: Sat, 11 May 2024 20:39:29 -0400 Subject: [PATCH 40/44] Added TDD --- cypress/e2e/03_features.cy.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/cypress/e2e/03_features.cy.ts b/cypress/e2e/03_features.cy.ts index 00a8f08a6..820932000 100644 --- a/cypress/e2e/03_features.cy.ts +++ b/cypress/e2e/03_features.cy.ts @@ -159,6 +159,7 @@ describe('Get Feature by uuid', () => { <<<<<<< HEAD headers: { 'x-jwt': `${value}` }, ======= + url: `${HostName}/features/`+ Features[i].uuid, headers: { 'x-jwt': `${ value }` }, >>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) body: {} From a79a521338214772dd39d0b16769f45087769330 Mon Sep 17 00:00:00 2001 From: MahtabBukhari Date: Fri, 17 May 2024 11:30:13 +0500 Subject: [PATCH 41/44] resolve conflicts --- db/config.go | 4 --- db/features.go | 58 +++++++++++++----------------------------- db/interface.go | 6 ----- db/structs.go | 6 ----- handlers/features.go | 35 ++++++------------------- handlers/workspaces.go | 21 ++++++++++----- mocks/Database.go | 37 +++++++++++++++++++++++++++ routes/features.go | 9 +------ routes/workspaces.go | 4 +-- 9 files changed, 79 insertions(+), 101 deletions(-) diff --git a/db/config.go b/db/config.go index b5824670e..d54c162ca 100644 --- a/db/config.go +++ b/db/config.go @@ -67,14 +67,10 @@ func InitDB() { db.AutoMigrate(&ConnectionCodes{}) db.AutoMigrate(&BountyRoles{}) db.AutoMigrate(&UserInvoiceData{}) -<<<<<<< HEAD db.AutoMigrate(&WorkspaceRepositories{}) db.AutoMigrate(&WorkspaceFeatures{}) db.AutoMigrate(&FeaturePhase{}) db.AutoMigrate(&FeatureStory{}) -======= - db.AutoMigrate(&WorkspaceFeatures{}) ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) DB.MigrateTablesWithOrgUuid() DB.MigrateOrganizationToWorkspace() diff --git a/db/features.go b/db/features.go index c4a5a47c0..038d645c8 100644 --- a/db/features.go +++ b/db/features.go @@ -3,18 +3,11 @@ package db import ( "errors" "fmt" -<<<<<<< HEAD "net/http" "strings" "time" "github.com/stakwork/sphinx-tribes/utils" -======= - "github.com/stakwork/sphinx-tribes/utils" - "net/http" - "strings" - "time" ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) ) func (db database) GetFeaturesByWorkspaceUuid(uuid string, r *http.Request) []WorkspaceFeatures { @@ -65,21 +58,34 @@ func (db database) CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, m.Brief = strings.TrimSpace(m.Brief) m.Requirements = strings.TrimSpace(m.Requirements) m.Architecture = strings.TrimSpace(m.Architecture) -<<<<<<< HEAD - now := time.Now() m.Updated = &now - if db.db.Model(&m).Where("uuid = ?", m.Uuid).Updates(&m).RowsAffected == 0 { + var existing WorkspaceFeatures + result := db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", m.Uuid).First(&existing) + if result.RowsAffected == 0 { + m.Created = &now db.db.Create(&m) - } + } else { - db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", m.Uuid).Find(&m) + db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", m.Uuid).Updates(m) + } + db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", m.Uuid).First(&m) return m, nil } +func (db database) DeleteFeatureByUuid(uuid string) error { + result := db.db.Where("uuid = ?", uuid).Delete(&WorkspaceFeatures{}) + + if result.RowsAffected == 0 { + return errors.New("no feature found to delete") + } + return nil + +} + func (db database) CreateOrEditFeaturePhase(phase FeaturePhase) (FeaturePhase, error) { phase.Name = strings.TrimSpace(phase.Name) @@ -175,32 +181,4 @@ func (db database) DeleteFeatureStoryByUuid(featureUuid, storyUuid string) error return errors.New("no story found to delete") } return nil -======= - now := time.Now() - m.Updated = &now - - var existing WorkspaceFeatures - result := db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", m.Uuid).First(&existing) - if result.RowsAffected == 0 { - - m.Created = &now - db.db.Create(&m) - } else { - - db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", m.Uuid).Updates(m) - } - - db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", m.Uuid).First(&m) - return m, nil -} - -func (db database) DeleteFeatureByUuid(uuid string) error { - result := db.db.Where("uuid = ?", uuid).Delete(&WorkspaceFeatures{}) - - if result.RowsAffected == 0 { - return errors.New("no feature found to delete") - } - return nil - ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) } diff --git a/db/interface.go b/db/interface.go index 7e7d11aa3..8c0aa4ced 100644 --- a/db/interface.go +++ b/db/interface.go @@ -140,16 +140,12 @@ type Database interface { PersonUniqueNameFromName(name string) (string, error) ProcessAlerts(p Person) UserHasAccess(pubKeyFromAuth string, uuid string, role string) bool -<<<<<<< HEAD CreateWorkspaceRepository(m WorkspaceRepositories) (WorkspaceRepositories, error) GetWorkspaceRepositorByWorkspaceUuid(uuid string) []WorkspaceRepositories -======= ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, error) GetFeaturesByWorkspaceUuid(uuid string, r *http.Request) []WorkspaceFeatures GetWorkspaceFeaturesCount(uuid string) int64 GetFeatureByUuid(uuid string) WorkspaceFeatures -<<<<<<< HEAD CreateOrEditFeaturePhase(phase FeaturePhase) (FeaturePhase, error) GetPhasesByFeatureUuid(featureUuid string) []FeaturePhase GetFeaturePhaseByUuid(featureUuid, phaseUuid string) (FeaturePhase, error) @@ -158,7 +154,5 @@ type Database interface { GetFeatureStoriesByFeatureUuid(featureUuid string) ([]FeatureStory, error) GetFeatureStoryByUuid(featureUuid, storyUuid string) (FeatureStory, error) DeleteFeatureStoryByUuid(featureUuid, storyUuid string) error -======= DeleteFeatureByUuid(uuid string) error ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) } diff --git a/db/structs.go b/db/structs.go index aee1443ac..6988a5511 100644 --- a/db/structs.go +++ b/db/structs.go @@ -551,7 +551,6 @@ type WorkspaceUsersData struct { Person } -<<<<<<< HEAD type WorkspaceRepositories struct { ID uint `json:"id"` Uuid string `gorm:"not null" json:"uuid"` @@ -564,8 +563,6 @@ type WorkspaceRepositories struct { UpdatedBy string `json:"updated_by"` } -======= ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) type WorkspaceFeatures struct { ID uint `json:"id"` Uuid string `gorm:"not null" json:"uuid"` @@ -580,7 +577,6 @@ type WorkspaceFeatures struct { UpdatedBy string `json:"updated_by"` } -<<<<<<< HEAD type FeaturePhase struct { Uuid string `json:"uuid" gorm:"primary_key"` FeatureUuid string `json:"feature_uuid"` @@ -592,8 +588,6 @@ type FeaturePhase struct { UpdatedBy string `json:"updated_by"` } -======= ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) type BountyRoles struct { Name string `json:"name"` } diff --git a/handlers/features.go b/handlers/features.go index 3d388601f..0ae539184 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -3,21 +3,13 @@ package handlers import ( "encoding/json" "fmt" -<<<<<<< HEAD "io" "net/http" -======= ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) "github.com/go-chi/chi" "github.com/rs/xid" "github.com/stakwork/sphinx-tribes/auth" "github.com/stakwork/sphinx-tribes/db" -<<<<<<< HEAD -======= - "io" - "net/http" ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) ) type featureHandler struct { @@ -77,8 +69,7 @@ func (oh *featureHandler) CreateOrEditFeatures(w http.ResponseWriter, r *http.Re json.NewEncoder(w).Encode(p) } -<<<<<<< HEAD -func (oh *featureHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r *http.Request) { +func (oh *featureHandler) DeleteFeature(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) if pubKeyFromAuth == "" { @@ -88,14 +79,17 @@ func (oh *featureHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r *h } uuid := chi.URLParam(r, "uuid") - workspaceFeatures := oh.db.GetFeaturesByWorkspaceUuid(uuid, r) + err := oh.db.DeleteFeatureByUuid(uuid) + if err != nil { + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) + return + } w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(workspaceFeatures) + fmt.Fprint(w, "Feature deleted successfully") } -======= ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) func (oh *featureHandler) GetWorkspaceFeaturesCount(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) @@ -128,11 +122,7 @@ func (oh *featureHandler) GetFeatureByUuid(w http.ResponseWriter, r *http.Reques json.NewEncoder(w).Encode(workspaceFeature) } -<<<<<<< HEAD func (oh *featureHandler) CreateOrEditFeaturePhase(w http.ResponseWriter, r *http.Request) { -======= -func (oh *featureHandler) DeleteFeature(w http.ResponseWriter, r *http.Request) { ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) if pubKeyFromAuth == "" { @@ -141,7 +131,6 @@ func (oh *featureHandler) DeleteFeature(w http.ResponseWriter, r *http.Request) return } -<<<<<<< HEAD newPhase := db.FeaturePhase{} decoder := json.NewDecoder(r.Body) err := decoder.Decode(&newPhase) @@ -201,10 +190,6 @@ func (oh *featureHandler) DeleteFeaturePhase(w http.ResponseWriter, r *http.Requ phaseUuid := chi.URLParam(r, "phase_uuid") err := oh.db.DeleteFeaturePhase(featureUuid, phaseUuid) -======= - uuid := chi.URLParam(r, "uuid") - err := oh.db.DeleteFeatureByUuid(uuid) ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) if err != nil { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) @@ -212,7 +197,6 @@ func (oh *featureHandler) DeleteFeaturePhase(w http.ResponseWriter, r *http.Requ } w.WriteHeader(http.StatusOK) -<<<<<<< HEAD json.NewEncoder(w).Encode(map[string]string{"message": "Phase deleted successfully"}) } @@ -295,7 +279,4 @@ func (oh *featureHandler) DeleteStory(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "Story deleted successfully"}) -======= - fmt.Fprint(w, "Feature deleted successfully") ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) } diff --git a/handlers/workspaces.go b/handlers/workspaces.go index e743e8bdb..f56a45b14 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -783,11 +783,7 @@ func (oh *workspaceHandler) UpdateWorkspace(w http.ResponseWriter, r *http.Reque json.NewEncoder(w).Encode(p) } -<<<<<<< HEAD func (oh *workspaceHandler) CreateWorkspaceRepository(w http.ResponseWriter, r *http.Request) { -======= -func (oh *workspaceHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r *http.Request) { ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) if pubKeyFromAuth == "" { @@ -796,7 +792,6 @@ func (oh *workspaceHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r return } -<<<<<<< HEAD workspaceRepo := db.WorkspaceRepositories{} body, _ := io.ReadAll(r.Body) r.Body.Close() @@ -846,10 +841,22 @@ func (oh *workspaceHandler) GetWorkspaceRepositorByWorkspaceUuid(w http.Response uuid := chi.URLParam(r, "uuid") workspaceFeatures := oh.db.GetWorkspaceRepositorByWorkspaceUuid(uuid) -======= + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(workspaceFeatures) +} + +func (oh *workspaceHandler) 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, "workspace_uuid") workspaceFeatures := oh.db.GetFeaturesByWorkspaceUuid(uuid, r) ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(workspaceFeatures) diff --git a/mocks/Database.go b/mocks/Database.go index 3028b31a2..f9adc0bea 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -452,6 +452,43 @@ func (_c *Database_BountiesPaidPercentage_Call) RunAndReturn(run func(db.Payment return _c } +// DeleteFeatureByUuid provides a mock function with given fields: uuid +func (_m *Database) DeleteFeatureByUuid(uuid string) error { + ret := _m.Called(uuid) + if len(ret) == 0 { + panic("no return value specified for DeleteFeatureByUuid") + } + return ret.Error(0) +} + +// Database_DeleteFeatureByUuid_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteFeatureByUuid' +type Database_DeleteFeatureByUuid_Call struct { + *mock.Call +} + +// DeleteFeatureByUuid is a helper method to define mock.On call +// - uuid string +func (_e *Database_Expecter) DeleteFeatureByUuid(uuid interface{}) *Database_DeleteFeatureByUuid_Call { + return &Database_DeleteFeatureByUuid_Call{Call: _e.mock.On("DeleteFeatureByUuid", uuid)} +} + +func (_c *Database_DeleteFeatureByUuid_Call) Run(run func(uuid string)) *Database_DeleteFeatureByUuid_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *Database_DeleteFeatureByUuid_Call) Return(_a0 error) *Database_DeleteFeatureByUuid_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Database_DeleteFeatureByUuid_Call) RunAndReturn(run func(string) error) *Database_DeleteFeatureByUuid_Call { + _c.Call.Return(run) + return _c +} + // ChangeWorkspaceDeleteStatus provides a mock function with given fields: workspace_uuid, status func (_m *Database) ChangeWorkspaceDeleteStatus(workspace_uuid string, status bool) db.Workspace { ret := _m.Called(workspace_uuid, status) diff --git a/routes/features.go b/routes/features.go index 3cae724b6..13c5f38c5 100644 --- a/routes/features.go +++ b/routes/features.go @@ -14,10 +14,9 @@ func FeatureRoutes() chi.Router { r.Use(auth.PubKeyContext) r.Post("/", featureHandlers.CreateOrEditFeatures) -<<<<<<< HEAD - r.Get("/forworkspace/{uuid}", featureHandlers.GetFeaturesByWorkspaceUuid) r.Get("/{uuid}", featureHandlers.GetFeatureByUuid) r.Get("/workspace/count/{uuid}", featureHandlers.GetWorkspaceFeaturesCount) + r.Delete("/{uuid}", featureHandlers.DeleteFeature) r.Post("/phase", featureHandlers.CreateOrEditFeaturePhase) r.Get("/{feature_uuid}/phase", featureHandlers.GetFeaturePhases) @@ -28,12 +27,6 @@ func FeatureRoutes() chi.Router { r.Get("/{feature_uuid}/story", featureHandlers.GetStoriesByFeatureUuid) r.Get("/{feature_uuid}/story/{story_uuid}", featureHandlers.GetStoryByUuid) r.Delete("/{feature_uuid}/story/{story_uuid}", featureHandlers.DeleteStory) -======= - r.Get("/{uuid}", featureHandlers.GetFeatureByUuid) - r.Get("/workspace/count/{uuid}", featureHandlers.GetWorkspaceFeaturesCount) - r.Delete("/{uuid}", featureHandlers.DeleteFeature) - ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) }) return r } diff --git a/routes/workspaces.go b/routes/workspaces.go index 90f13262b..878d9a848 100644 --- a/routes/workspaces.go +++ b/routes/workspaces.go @@ -42,13 +42,11 @@ func WorkspaceRoutes() chi.Router { r.Post("/mission", workspaceHandlers.UpdateWorkspace) r.Post("/tactics", workspaceHandlers.UpdateWorkspace) r.Post("/schematicurl", workspaceHandlers.UpdateWorkspace) -<<<<<<< HEAD r.Post("/repositories", workspaceHandlers.CreateWorkspaceRepository) r.Get("/repositories/{uuid}", workspaceHandlers.GetWorkspaceRepositorByWorkspaceUuid) -======= + r.Get("/{workspace_uuid}/features", workspaceHandlers.GetFeaturesByWorkspaceUuid) ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) }) return r } From b47c3f3d425c5b4cfc9da59e66f700c4f51085f3 Mon Sep 17 00:00:00 2001 From: MahtabBukhari Date: Fri, 17 May 2024 22:25:12 +0500 Subject: [PATCH 42/44] resolve conflicts again --- cypress/e2e/03_features.cy.ts | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/cypress/e2e/03_features.cy.ts b/cypress/e2e/03_features.cy.ts index 820932000..55e9f2df8 100644 --- a/cypress/e2e/03_features.cy.ts +++ b/cypress/e2e/03_features.cy.ts @@ -120,21 +120,10 @@ describe('Get Features for Workspace', () => { cy.upsertlogin(User).then(value => { cy.request({ method: 'GET', - url: `${HostName}/workspaces/${Features[0].workspace_uuid}/features`, //changed from url: `${HostName}/features/forworkspace/` + Features[0].workspace_uuid, please update the routes file and any other change needed. + url: `${HostName}/workspaces/${Features[0].workspace_uuid}/features`, headers: { 'x-jwt': `${ value }` }, body: {} }).then((resp) => { -<<<<<<< HEAD - expect(resp.status).to.eq(200); - const body = resp.body.reverse(); - for (let i = 0; i <= 2; i++) { - expect(body[i]).to.have.property('name', Features[i].name.trim() + " _addtext") - expect(body[i]).to.have.property('brief', Features[i].brief.trim() + " _addtext") - expect(body[i]).to.have.property('requirements', Features[i].requirements.trim() + " _addtext") - expect(body[i]).to.have.property('architecture', Features[i].architecture.trim() + " _addtext") - } - }); -======= expect(resp.status).to.eq(200) resp.body.forEach((feature) => { const expectedFeature = Features.find(f => f.uuid === feature.uuid); @@ -144,7 +133,6 @@ describe('Get Features for Workspace', () => { expect(feature).to.have.property('architecture', expectedFeature.architecture.trim() + " _addtext"); }); }) ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) }) }) }) @@ -155,13 +143,8 @@ describe('Get Feature by uuid', () => { for (let i = 0; i <= 2; i++) { cy.request({ method: 'GET', - url: `${HostName}/features/` + Features[i].uuid, -<<<<<<< HEAD - headers: { 'x-jwt': `${value}` }, -======= url: `${HostName}/features/`+ Features[i].uuid, headers: { 'x-jwt': `${ value }` }, ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) body: {} }).then((resp) => { expect(resp.status).to.eq(200) @@ -180,7 +163,7 @@ describe('Delete Feature by uuid', () => { cy.upsertlogin(User).then(value => { cy.request({ method: 'DELETE', - url: `${HostName}/features/${Features[2].uuid}`, + url: `${HostName}/features/${Features[0].uuid}`, headers: { 'x-jwt': `${ value }` }, body: {} }).then((resp) => { @@ -195,14 +178,10 @@ describe('Check delete by uuid', () => { cy.upsertlogin(User).then(value => { cy.request({ method: 'DELETE', - url: `${HostName}/features/${Features[2].uuid}`, + url: `${HostName}/features/${Features[0].uuid}`, headers: { 'x-jwt': `${ value }` }, -<<<<<<< HEAD - body: {} -======= body: {}, failOnStatusCode: false ->>>>>>> e1f721d5 (Modify Features endpoint and add delete feature) }).then((resp) => { expect(resp.status).to.eq(404); }) From 7899419ffb045104fa85dc208445801795994171 Mon Sep 17 00:00:00 2001 From: MahtabBukhari Date: Sat, 18 May 2024 01:05:44 +0500 Subject: [PATCH 43/44] resolve conflicts again --- handlers/features.go | 16 ++++++++++++++++ handlers/workspaces.go | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/handlers/features.go b/handlers/features.go index 0ae539184..52e7d0d99 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -59,6 +59,14 @@ func (oh *featureHandler) CreateOrEditFeatures(w http.ResponseWriter, r *http.Re return } + // Check if workspace exists + workpace := oh.db.GetWorkspaceByUuid(features.WorkspaceUuid) + if workpace.Uuid != features.WorkspaceUuid { + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode("Workspace does not exists") + return + } + p, err := oh.db.CreateOrEditFeature(features) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -152,6 +160,14 @@ func (oh *featureHandler) CreateOrEditFeaturePhase(w http.ResponseWriter, r *htt newPhase.UpdatedBy = pubKeyFromAuth + // Check if feature exists + feature := oh.db.GetFeatureByUuid(newPhase.FeatureUuid) + if feature.Uuid != newPhase.FeatureUuid { + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode("Feature does not exists") + return + } + phase, err := oh.db.CreateOrEditFeaturePhase(newPhase) if err != nil { w.WriteHeader(http.StatusInternalServerError) diff --git a/handlers/workspaces.go b/handlers/workspaces.go index f56a45b14..051d30bc0 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -820,6 +820,14 @@ func (oh *workspaceHandler) CreateWorkspaceRepository(w http.ResponseWriter, r * return } + // Check if workspace exists + workpace := oh.db.GetWorkspaceByUuid(workspaceRepo.WorkspaceUuid) + if workpace.Uuid != workspaceRepo.WorkspaceUuid { + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode("Workspace does not exists") + return + } + p, err := oh.db.CreateWorkspaceRepository(workspaceRepo) if err != nil { w.WriteHeader(http.StatusBadRequest) From c14676caa4c2110665c9093173540e92c5451f6e Mon Sep 17 00:00:00 2001 From: MahtabBukhari Date: Sun, 19 May 2024 22:27:12 +0500 Subject: [PATCH 44/44] Modify Features endpoint and add delete feature --- cypress/e2e/03_features.cy.ts | 24 ++++++++++++++++++++++++ handlers/features.go | 18 ++++++++++++++++++ handlers/workspaces.go | 2 ++ routes/features.go | 2 ++ routes/workspaces.go | 2 +- 5 files changed, 47 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/03_features.cy.ts b/cypress/e2e/03_features.cy.ts index 55e9f2df8..0e268c6a2 100644 --- a/cypress/e2e/03_features.cy.ts +++ b/cypress/e2e/03_features.cy.ts @@ -137,6 +137,30 @@ describe('Get Features for Workspace', () => { }) }) +describe('Old 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); + if (resp.status === 200) { + resp.body.forEach((feature) => { + const expectedFeature = Features.find(f => f.uuid === feature.uuid); + expect(feature).to.have.property('name', expectedFeature.name.trim() + " _addtext"); + expect(feature).to.have.property('brief', expectedFeature.brief.trim() + " _addtext"); + expect(feature).to.have.property('requirements', expectedFeature.requirements.trim() + " _addtext"); + expect(feature).to.have.property('architecture', expectedFeature.architecture.trim() + " _addtext"); + }); + } + }); + }) + }) +}) + describe('Get Feature by uuid', () => { it('passes', () => { cy.upsertlogin(User).then(value => { diff --git a/handlers/features.go b/handlers/features.go index 52e7d0d99..1e77f2483 100644 --- a/handlers/features.go +++ b/handlers/features.go @@ -98,6 +98,24 @@ func (oh *featureHandler) DeleteFeature(w http.ResponseWriter, r *http.Request) fmt.Fprint(w, "Feature deleted successfully") } +// Old Method for getting features for workspace uuid + +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, "workspace_uuid") + workspaceFeatures := oh.db.GetFeaturesByWorkspaceUuid(uuid, r) + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(workspaceFeatures) +} + func (oh *featureHandler) GetWorkspaceFeaturesCount(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) diff --git a/handlers/workspaces.go b/handlers/workspaces.go index 051d30bc0..9e31737e9 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -854,6 +854,8 @@ func (oh *workspaceHandler) GetWorkspaceRepositorByWorkspaceUuid(w http.Response json.NewEncoder(w).Encode(workspaceFeatures) } +// New method for getting features by workspace uuid + func (oh *workspaceHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) diff --git a/routes/features.go b/routes/features.go index 13c5f38c5..b1343b382 100644 --- a/routes/features.go +++ b/routes/features.go @@ -15,6 +15,8 @@ func FeatureRoutes() chi.Router { r.Post("/", featureHandlers.CreateOrEditFeatures) r.Get("/{uuid}", featureHandlers.GetFeatureByUuid) + // Old route for to getting features for workspace uuid + r.Get("/forworkspace/{workspace_uuid}", featureHandlers.GetFeaturesByWorkspaceUuid) r.Get("/workspace/count/{uuid}", featureHandlers.GetWorkspaceFeaturesCount) r.Delete("/{uuid}", featureHandlers.DeleteFeature) diff --git a/routes/workspaces.go b/routes/workspaces.go index 878d9a848..81f7160e0 100644 --- a/routes/workspaces.go +++ b/routes/workspaces.go @@ -45,7 +45,7 @@ func WorkspaceRoutes() chi.Router { r.Post("/repositories", workspaceHandlers.CreateWorkspaceRepository) r.Get("/repositories/{uuid}", workspaceHandlers.GetWorkspaceRepositorByWorkspaceUuid) - + // New route for to getting features for workspace uuid r.Get("/{workspace_uuid}/features", workspaceHandlers.GetFeaturesByWorkspaceUuid) }) return r