From 709d6011ff882dc26140c4e8d1cb9fe8c2f2078b Mon Sep 17 00:00:00 2001 From: elraphty Date: Fri, 26 Apr 2024 13:30:54 +0100 Subject: [PATCH 01/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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/22] 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 7c11757a3d9d0ebfe79acd0e3b3621f20d9988c2 Mon Sep 17 00:00:00 2001 From: elraphty Date: Tue, 14 May 2024 18:37:06 +0100 Subject: [PATCH 21/22] 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 1c54cf068352fffe9146aacbce1a02819a5a0a17 Mon Sep 17 00:00:00 2001 From: MahtabBukhari Date: Thu, 16 May 2024 15:40:54 +0500 Subject: [PATCH 22/22] cypress fixed --- cypress/e2e/03_features.cy.ts | 50 ++++++++++++++++++++++++++++------- db/features.go | 24 ++++++++++++++--- db/interface.go | 1 + handlers/features.go | 26 ++++++++++-------- handlers/workspaces.go | 16 +++++++++++ mocks/Database.go | 37 ++++++++++++++++++++++++++ routes/features.go | 2 +- routes/workspaces.go | 1 + 8 files changed, 132 insertions(+), 25 deletions(-) diff --git a/cypress/e2e/03_features.cy.ts b/cypress/e2e/03_features.cy.ts index dd2c66424..7f20bbe61 100644 --- a/cypress/e2e/03_features.cy.ts +++ b/cypress/e2e/03_features.cy.ts @@ -120,17 +120,18 @@ describe('Get Features for Workspace', () => { cy.upsertlogin(User).then(value => { cy.request({ method: 'GET', - url: `${HostName}/features/forworkspace/` + Features[0].workspace_uuid, + 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) => { 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") - } + 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"); + }); }) }) }) @@ -144,7 +145,7 @@ describe('Get Feature by uuid', () => { method: 'GET', url: `${HostName}/features/` + Features[i].uuid, headers: { 'x-jwt': `${ value }` }, - body: {} + body: {} }).then((resp) => { expect(resp.status).to.eq(200) expect(resp.body).to.have.property('name', Features[i].name.trim() + " _addtext") @@ -156,3 +157,34 @@ 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[2].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: 'DELETE', + url: `${HostName}/features/${Features[2].uuid}`, + headers: { 'x-jwt': `${ value }` }, + body: {}, + failOnStatusCode: false + }).then((resp) => { + expect(resp.status).to.eq(404); + }) + }) + }) +}) \ No newline at end of file diff --git a/db/features.go b/db/features.go index a4f0aad45..c0374ee54 100644 --- a/db/features.go +++ b/db/features.go @@ -1,12 +1,12 @@ package db import ( + "errors" "fmt" + "github.com/stakwork/sphinx-tribes/utils" "net/http" "strings" "time" - - "github.com/stakwork/sphinx-tribes/utils" ) func (db database) GetFeaturesByWorkspaceUuid(uuid string, r *http.Request) []WorkspaceFeatures { @@ -57,14 +57,30 @@ 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) - 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).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 + +} diff --git a/db/interface.go b/db/interface.go index 0124592ad..4bfbacb99 100644 --- a/db/interface.go +++ b/db/interface.go @@ -146,4 +146,5 @@ type Database interface { GetFeaturesByWorkspaceUuid(uuid string, r *http.Request) []WorkspaceFeatures GetWorkspaceFeaturesCount(uuid string) int64 GetFeatureByUuid(uuid string) WorkspaceFeatures + DeleteFeatureByUuid(uuid string) error } diff --git a/handlers/features.go b/handlers/features.go index ea634cfb0..decda28c7 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 { @@ -69,7 +68,7 @@ func (oh *featureHandler) CreateOrEditFeatures(w http.ResponseWriter, r *http.Re json.NewEncoder(w).Encode(p) } -func (oh *featureHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r *http.Request) { +func (oh *featureHandler) GetWorkspaceFeaturesCount(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) if pubKeyFromAuth == "" { @@ -79,13 +78,13 @@ func (oh *featureHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r *h } uuid := chi.URLParam(r, "uuid") - workspaceFeatures := oh.db.GetFeaturesByWorkspaceUuid(uuid, r) + workspaceFeatures := oh.db.GetWorkspaceFeaturesCount(uuid) w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(workspaceFeatures) } -func (oh *featureHandler) GetWorkspaceFeaturesCount(w http.ResponseWriter, r *http.Request) { +func (oh *featureHandler) GetFeatureByUuid(w http.ResponseWriter, r *http.Request) { ctx := r.Context() pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string) if pubKeyFromAuth == "" { @@ -95,13 +94,13 @@ func (oh *featureHandler) GetWorkspaceFeaturesCount(w http.ResponseWriter, r *ht } uuid := chi.URLParam(r, "uuid") - workspaceFeatures := oh.db.GetWorkspaceFeaturesCount(uuid) + workspaceFeature := oh.db.GetFeatureByUuid(uuid) w.WriteHeader(http.StatusOK) - json.NewEncoder(w).Encode(workspaceFeatures) + json.NewEncoder(w).Encode(workspaceFeature) } -func (oh *featureHandler) GetFeatureByUuid(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 == "" { @@ -111,8 +110,13 @@ func (oh *featureHandler) GetFeatureByUuid(w http.ResponseWriter, r *http.Reques } uuid := chi.URLParam(r, "uuid") - workspaceFeature := oh.db.GetFeatureByUuid(uuid) + 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(workspaceFeature) + fmt.Fprint(w, "Feature deleted successfully") } diff --git a/handlers/workspaces.go b/handlers/workspaces.go index 5ed2f09e6..f56a45b14 100644 --- a/handlers/workspaces.go +++ b/handlers/workspaces.go @@ -845,3 +845,19 @@ func (oh *workspaceHandler) GetWorkspaceRepositorByWorkspaceUuid(w http.Response 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) + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(workspaceFeatures) +} diff --git a/mocks/Database.go b/mocks/Database.go index 6f68771bc..2e590cae8 100644 --- a/mocks/Database.go +++ b/mocks/Database.go @@ -6622,6 +6622,43 @@ func (_c *Database_UpdateWorkspaceBudget_Call) RunAndReturn(run func(db.NewBount 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 +} + // UpdateWorkspaceForDeletion provides a mock function with given fields: uuid func (_m *Database) UpdateWorkspaceForDeletion(uuid string) error { ret := _m.Called(uuid) diff --git a/routes/features.go b/routes/features.go index a6189793a..dcda4f30b 100644 --- a/routes/features.go +++ b/routes/features.go @@ -14,9 +14,9 @@ func FeatureRoutes() chi.Router { r.Use(auth.PubKeyContext) r.Post("/", featureHandlers.CreateOrEditFeatures) - r.Get("/forworkspace/{uuid}", featureHandlers.GetFeaturesByWorkspaceUuid) r.Get("/{uuid}", featureHandlers.GetFeatureByUuid) r.Get("/workspace/count/{uuid}", featureHandlers.GetWorkspaceFeaturesCount) + r.Delete("/{uuid}", featureHandlers.DeleteFeature) }) return r } diff --git a/routes/workspaces.go b/routes/workspaces.go index d02c6e956..f0cde4cd2 100644 --- a/routes/workspaces.go +++ b/routes/workspaces.go @@ -45,6 +45,7 @@ func WorkspaceRoutes() chi.Router { r.Post("/repositories", workspaceHandlers.CreateWorkspaceRepository) r.Get("/repositories/{uuid}", workspaceHandlers.GetWorkspaceRepositorByWorkspaceUuid) + r.Get("/{workspace_uuid}/features", workspaceHandlers.GetFeaturesByWorkspaceUuid) }) return r }