diff --git a/cypress/e2e/03_features.cy.ts b/cypress/e2e/03_features.cy.ts
new file mode 100644
index 000000000..dd2c66424
--- /dev/null
+++ b/cypress/e2e/03_features.cy.ts
@@ -0,0 +1,158 @@
+import { User, HostName, Workspaces, Repositories, Features } from '../support/objects/objects';
+
+
+
+describe('Create Features for Workspace', () => {
+ it('passes', () => {
+ cy.upsertlogin(User).then(value => {
+ for(let i = 0; i <= 2; i++) {
+ cy.request({
+ method: 'POST',
+ url: `${HostName}/features`,
+ headers: { 'x-jwt': `${value}` },
+ body: Features[i]
+ }).its('body').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());
+ });
+ }
+ })
+ })
+})
+
+describe('Modify name for Feature', () => {
+ it('passes', () => {
+ cy.upsertlogin(User).then(value => {
+ for(let i = 0; i <= 2; i++) {
+ cy.request({
+ method: 'POST',
+ url: `${HostName}/features`,
+ headers: { 'x-jwt': `${value}` },
+ body: {
+ uuid: Features[i].uuid,
+ name: Features[i].name + "_addtext"
+ }
+ }).its('body').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());
+ });
+ }
+ })
+ })
+})
+
+describe('Modify brief for Feature', () => {
+ it('passes', () => {
+ cy.upsertlogin(User).then(value => {
+ for(let i = 0; i <= 2; i++) {
+ cy.request({
+ method: 'POST',
+ url: `${HostName}/features`,
+ headers: { 'x-jwt': `${value}` },
+ body: {
+ uuid: Features[i].uuid,
+ brief: Features[i].brief + "_addtext"
+ }
+ }).its('body').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());
+ });
+ }
+ })
+ })
+})
+
+describe('Modify requirements for Feature', () => {
+ it('passes', () => {
+ cy.upsertlogin(User).then(value => {
+ for(let i = 0; i <= 2; i++) {
+ cy.request({
+ method: 'POST',
+ url: `${HostName}/features`,
+ headers: { 'x-jwt': `${value}` },
+ body: {
+ uuid: Features[i].uuid,
+ requirements: Features[i].requirements + "_addtext"
+ }
+ }).its('body').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());
+ });
+ }
+ })
+ })
+})
+
+describe('Modify architecture for Feature', () => {
+ it('passes', () => {
+ cy.upsertlogin(User).then(value => {
+ for(let i = 0; i <= 2; i++) {
+ cy.request({
+ method: 'POST',
+ url: `${HostName}/features`,
+ headers: { 'x-jwt': `${value}` },
+ body: {
+ uuid: Features[i].uuid,
+ architecture: Features[i].architecture + "_addtext"
+ }
+ }).its('body').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");
+ });
+ }
+ })
+ })
+})
+
+
+describe('Get Features for Workspace', () => {
+ it('passes', () => {
+ cy.upsertlogin(User).then(value => {
+ cy.request({
+ method: 'GET',
+ url: `${HostName}/features/forworkspace/` + Features[0].workspace_uuid,
+ headers: { 'x-jwt': `${ value }` },
+ body: {}
+ }).then((resp) => {
+ expect(resp.status).to.eq(200)
+ for(let i = 0; i <= 2; i++) {
+ expect(resp.body[i]).to.have.property('name', Features[i].name.trim() + " _addtext")
+ expect(resp.body[i]).to.have.property('brief', Features[i].brief.trim() + " _addtext")
+ expect(resp.body[i]).to.have.property('requirements', Features[i].requirements.trim() + " _addtext")
+ expect(resp.body[i]).to.have.property('architecture', Features[i].architecture.trim() + " _addtext")
+ }
+ })
+ })
+ })
+})
+
+describe('Get Feature by uuid', () => {
+ it('passes', () => {
+ cy.upsertlogin(User).then(value => {
+ for(let i = 0; i <= 2; i++) {
+ cy.request({
+ method: 'GET',
+ url: `${HostName}/features/` + Features[i].uuid,
+ headers: { 'x-jwt': `${ value }` },
+ body: {}
+ }).then((resp) => {
+ expect(resp.status).to.eq(200)
+ expect(resp.body).to.have.property('name', Features[i].name.trim() + " _addtext")
+ expect(resp.body).to.have.property('brief', Features[i].brief.trim() + " _addtext")
+ expect(resp.body).to.have.property('requirements', Features[i].requirements.trim() + " _addtext")
+ expect(resp.body).to.have.property('architecture', Features[i].architecture.trim() + " _addtext")
+ })
+ }
+ })
+ })
+})
diff --git a/cypress/support/objects/objects.ts b/cypress/support/objects/objects.ts
index b62af74d7..bcbe8215d 100644
--- a/cypress/support/objects/objects.ts
+++ b/cypress/support/objects/objects.ts
@@ -57,34 +57,42 @@ export const Workspaces = [
export const Repositories = [
{
- name: 'frontend',
- url: 'https://github.com/stakwork/sphinx-tribes-frontend'
+ uuid: 'com1t3gn1e4a4qu3tnlg',
+ workspace_uuid: 'cohob00n1e4808utqel0',
+ name: ' frontend ',
+ url: ' https://github.com/stakwork/sphinx-tribes-frontend '
},
{
- name: 'backend',
- url: 'https://github.com/stakwork/sphinx-tribes'
+ uuid: 'com1t3gn1e4a4qu3tnlg',
+ workspace_uuid: 'cohob00n1e4808utqel0',
+ name: ' backend ',
+ url: ' https://github.com/stakwork/sphinx-tribes '
}
];
export const Features = [
{
- name: 'Hive Process',
+ uuid: 'com1kson1e49th88dbg0',
+ workspace_uuid: 'cohob00n1e4808utqel0',
+ name: ' Hive Process ',
priority: 1,
- brief: 'To follow a set of best practices in product development.' +
+ brief: ' To follow a set of best practices in product development.' +
'Dividing complex features into small
steps makes it easier to ' +
'track and the timing more certain.
A guided process would help ' +
'a PM new to the hive process get the best results with the least mental ' +
'load.
This feature is for a not se technical Product Manager.
' +
- 'The hive process lets you get features out to production faster and with less risk.',
- requirements: 'Modify workspaces endpoint to accomodate new fields.
' +
- 'Create end points for features, user stories and phases',
- architecture: 'Describe the architecture of the feature with the following sections:' +
+ 'The hive process lets you get features out to production faster and with less risk. ',
+ requirements: ' Modify workspaces endpoint to accomodate new fields.
' +
+ 'Create end points for features, user stories and phases ',
+ architecture: ' Describe the architecture of the feature with the following sections:' +
'
Wireframes
Visual Schematics
Object Definition
' +
'DB Schema Changes
UX
CI/CD
Changes
Endpoints
' +
- 'Front
',
+ 'Front
',
},
{
- name: 'AI Assited text fields',
+ uuid: 'com1l5on1e49tucv350g',
+ workspace_uuid: 'cohob00n1e4808utqel0',
+ name: ' AI Assited text fields ',
priority: 2,
brief: 'An important struggle of a technical product manager is to find ' +
'the right words to describe a business goal. The definition of ' +
@@ -92,43 +100,45 @@ 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',
- requirements: 'Create a new page for a conversation format between the PM and the LLM
' +
+ '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',
- 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: 'com1l5on1e49tucv350h',
+ 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.
' +
- '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
' +
- '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
diff --git a/db/config.go b/db/config.go
index f635fa4ef..5496e4b8f 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 05d328654..ff3a4479e 100644
--- a/db/interface.go
+++ b/db/interface.go
@@ -140,4 +140,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 e01abf72d..8cefb2bbf 100644
--- a/db/structs.go
+++ b/db/structs.go
@@ -551,6 +551,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..9c4f9cd68
--- /dev/null
+++ b/handlers/features.go
@@ -0,0 +1,95 @@
+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
+ }
+
+ features.CreatedBy = pubKeyFromAuth
+
+ // 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 8c727b024..f3a3587d7 100644
--- a/mocks/Database.go
+++ b/mocks/Database.go
@@ -930,6 +930,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)
@@ -2523,6 +2579,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)