fx build fix
Some checks failed
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/fx/1 Pipeline failed
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/fx/2 Pipeline failed

This commit is contained in:
Stephan D
2025-11-08 00:30:29 +01:00
parent 590fad0071
commit 49b86efecb
165 changed files with 9466 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
package api
import (
"github.com/tech/sendico/pkg/auth"
"github.com/tech/sendico/pkg/db"
"github.com/tech/sendico/pkg/domainprovider"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mservice"
)
type API interface {
Logger() mlogger.Logger
DomainProvider() domainprovider.DomainProvider
Config() *Config
DBFactory() db.Factory
Permissions() auth.Provider
Register() Register
}
type MicroServiceFactoryT = func(API) (mservice.MicroService, error)

View File

@@ -0,0 +1,11 @@
package api
import (
mwa "github.com/tech/sendico/server/interface/middleware"
fsc "github.com/tech/sendico/server/interface/services/fileservice/config"
)
type Config struct {
Mw *mwa.Config `yaml:"middleware"`
Storage *fsc.Config `yaml:"storage"`
}

View File

@@ -0,0 +1,10 @@
package permissions
import (
"github.com/tech/sendico/pkg/auth"
"github.com/tech/sendico/pkg/model"
)
func Deny(_ *model.Account, _ *auth.Enforcer) (bool, error) {
return true, nil
}

View File

@@ -0,0 +1,10 @@
package permissions
import (
"github.com/tech/sendico/pkg/auth"
"github.com/tech/sendico/pkg/model"
)
func DoNotCheck(_ *model.Account, _ *auth.Enforcer) (bool, error) {
return true, nil
}

View File

@@ -0,0 +1,17 @@
package api
import (
api "github.com/tech/sendico/pkg/api/http"
"github.com/tech/sendico/pkg/messaging"
"github.com/tech/sendico/pkg/mservice"
"github.com/tech/sendico/server/interface/api/sresponse"
"github.com/tech/sendico/server/interface/api/ws"
)
type Register interface {
Handler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.HandlerFunc)
AccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.AccountHandlerFunc)
WSHandler(messageType string, handler ws.HandlerFunc)
Messaging() messaging.Register
}

View File

@@ -0,0 +1,7 @@
package srequest
import "github.com/tech/sendico/pkg/model"
type AcceptInvitation struct {
Account *model.AccountData `json:"account,omitempty"`
}

View File

@@ -0,0 +1,12 @@
package srequest
import (
"github.com/tech/sendico/pkg/model"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ChangePolicies struct {
RoleRef primitive.ObjectID `json:"roleRef"`
Add *[]model.RolePolicy `json:"add,omitempty"`
Remove *[]model.RolePolicy `json:"remove,omitempty"`
}

View File

@@ -0,0 +1,8 @@
package srequest
import "go.mongodb.org/mongo-driver/bson/primitive"
type ChangeRole struct {
AccountRef primitive.ObjectID `json:"accountRef"`
NewRoleDescriptionRef primitive.ObjectID `json:"newRoleDescriptionRef"`
}

View File

@@ -0,0 +1,7 @@
package srequest
import "go.mongodb.org/mongo-driver/bson/primitive"
type FileUpload struct {
ObjRef primitive.ObjectID `json:"objRef"`
}

View File

@@ -0,0 +1,7 @@
package srequest
import (
"github.com/tech/sendico/pkg/model"
)
type CreateInvitation = model.Invitation

View File

@@ -0,0 +1,8 @@
package srequest
import "github.com/tech/sendico/pkg/model"
type Login struct {
model.SessionIdentifier `json:",inline"`
model.LoginData `json:"login"`
}

View File

@@ -0,0 +1,15 @@
package srequest
type ChangePassword struct {
Old string `json:"old"`
New string `json:"new"`
DeviceID string `json:"deviceId"`
}
type ResetPassword struct {
Password string `json:"password"`
}
type ForgotPassword struct {
Login string `json:"login"`
}

View File

@@ -0,0 +1,8 @@
package srequest
import "github.com/tech/sendico/pkg/model"
type CreatePriorityGroup struct {
Description model.Describable `json:"description"`
Priorities []model.Colorable `json:"priorities"`
}

View File

@@ -0,0 +1,31 @@
package srequest
import (
"github.com/tech/sendico/pkg/model"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type CreateProject struct {
Project model.Describable `json:"project"`
LogoURI *string `json:"logoUrl,omitempty"`
PrioriyGroupRef primitive.ObjectID `json:"priorityGroupRef"`
StatusGroupRef primitive.ObjectID `json:"statusGroupRef"`
Mnemonic string `json:"mnemonic"`
}
type ProjectPreview struct {
Projects []primitive.ObjectID `json:"projects"`
}
type TagFilterMode string
const (
TagFilterModeNone TagFilterMode = "none"
TagFilterModePresent TagFilterMode = "present"
TagFilterModeMissing TagFilterMode = "missing"
TagFilterModeIncludeAny TagFilterMode = "includeAny"
TagFilterModeIncludeAll TagFilterMode = "includeAll"
TagFilterModeExcludeAny TagFilterMode = "excludeAny"
)
type ProjectsFilter = model.ProjectFilterBase

View File

@@ -0,0 +1,11 @@
package srequest
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
// DeleteProject represents a request to delete a project
type DeleteProject struct {
OrganizationRef primitive.ObjectID `json:"organizationRef"` // If provided, move tasks to this project. If null, delete all tasks
MoveTasksToProjectRef *primitive.ObjectID `json:"moveTasksToProjectRef,omitempty"` // If provided, move tasks to this project. If null, delete all tasks
}

View File

@@ -0,0 +1,5 @@
package srequest
import "github.com/tech/sendico/pkg/model"
type AccessTokenRefresh = model.ClientRefreshToken

View File

@@ -0,0 +1,19 @@
package srequest
import "go.mongodb.org/mongo-driver/bson/primitive"
type Reorder struct {
ParentRef primitive.ObjectID `json:"parentRef"`
From int `json:"from"`
To int `json:"to"`
}
type ReorderX struct {
ObjectRef primitive.ObjectID `json:"objectRef"`
To int `json:"to"`
}
type ReorderXDefault struct {
ReorderX `json:",inline"`
ParentRef primitive.ObjectID `json:"parentRef"`
}

View File

@@ -0,0 +1,5 @@
package srequest
import "github.com/tech/sendico/pkg/model"
type TokenRefreshRotate = model.ClientRefreshToken

View File

@@ -0,0 +1,13 @@
package srequest
import "go.mongodb.org/mongo-driver/bson/primitive"
type GroupItemChange struct {
GroupRef primitive.ObjectID `json:"groupRef"`
ItemRef primitive.ObjectID `json:"itemRef"`
}
type RemoveItemFromGroup struct {
GroupItemChange `json:",inline"`
TargetItemRef primitive.ObjectID `json:"targetItemRef"`
}

View File

@@ -0,0 +1,14 @@
package srequest
import "github.com/tech/sendico/pkg/model"
type Signup struct {
Account model.AccountData `json:"account"`
OrganizationName string `json:"organizationName"`
OrganizationTimeZone string `json:"organizationTimeZone"`
DefaultPriorityGroup CreatePriorityGroup `json:"defaultPriorityGroup"`
DefaultStatusGroup CreateStatusGroup `json:"defaultStatusGroup"`
AnonymousUser model.Describable `json:"anonymousUser"`
OwnerRole model.Describable `json:"ownerRole"`
AnonymousRole model.Describable `json:"anonymousRole"`
}

View File

@@ -0,0 +1,312 @@
package srequest_test
import (
"encoding/json"
"fmt"
"testing"
"github.com/tech/sendico/pkg/model"
"github.com/tech/sendico/server/interface/api/srequest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Helper function to create string pointers
func stringPtr(s string) *string {
return &s
}
func TestSignupRequest_JSONSerialization(t *testing.T) {
signup := srequest.Signup{
Account: model.AccountData{
LoginData: model.LoginData{
UserDataBase: model.UserDataBase{
Login: "test@example.com",
},
Password: "TestPassword123!",
},
Name: "Test User",
},
OrganizationName: "Test Organization",
OrganizationTimeZone: "UTC",
DefaultPriorityGroup: srequest.CreatePriorityGroup{
Description: model.Describable{
Name: "Default Priority Group",
},
Priorities: []model.Colorable{
{
Describable: model.Describable{Name: "High"},
Color: stringPtr("#FF0000"),
},
{
Describable: model.Describable{Name: "Medium"},
Color: stringPtr("#FFFF00"),
},
{
Describable: model.Describable{Name: "Low"},
Color: stringPtr("#00FF00"),
},
},
},
AnonymousUser: model.Describable{
Name: "Anonymous User",
},
OwnerRole: model.Describable{
Name: "Owner",
},
AnonymousRole: model.Describable{
Name: "Anonymous",
},
}
// Test JSON marshaling
jsonData, err := json.Marshal(signup)
require.NoError(t, err)
assert.NotEmpty(t, jsonData)
// Test JSON unmarshaling
var unmarshaled srequest.Signup
err = json.Unmarshal(jsonData, &unmarshaled)
require.NoError(t, err)
// Verify all fields are properly serialized/deserialized
assert.Equal(t, signup.Account.Name, unmarshaled.Account.Name)
assert.Equal(t, signup.Account.Login, unmarshaled.Account.Login)
assert.Equal(t, signup.Account.Password, unmarshaled.Account.Password)
assert.Equal(t, signup.OrganizationName, unmarshaled.OrganizationName)
assert.Equal(t, signup.OrganizationTimeZone, unmarshaled.OrganizationTimeZone)
assert.Equal(t, signup.DefaultPriorityGroup.Description.Name, unmarshaled.DefaultPriorityGroup.Description.Name)
assert.Equal(t, len(signup.DefaultPriorityGroup.Priorities), len(unmarshaled.DefaultPriorityGroup.Priorities))
assert.Equal(t, signup.AnonymousUser.Name, unmarshaled.AnonymousUser.Name)
assert.Equal(t, signup.OwnerRole.Name, unmarshaled.OwnerRole.Name)
assert.Equal(t, signup.AnonymousRole.Name, unmarshaled.AnonymousRole.Name)
// Verify priorities
for i, priority := range signup.DefaultPriorityGroup.Priorities {
assert.Equal(t, priority.Name, unmarshaled.DefaultPriorityGroup.Priorities[i].Name)
if priority.Color != nil && unmarshaled.DefaultPriorityGroup.Priorities[i].Color != nil {
assert.Equal(t, *priority.Color, *unmarshaled.DefaultPriorityGroup.Priorities[i].Color)
}
}
}
func TestSignupRequest_MinimalValidRequest(t *testing.T) {
signup := srequest.Signup{
Account: model.AccountData{
LoginData: model.LoginData{
UserDataBase: model.UserDataBase{
Login: "test@example.com",
},
Password: "TestPassword123!",
},
Name: "Test User",
},
OrganizationName: "Test Organization",
OrganizationTimeZone: "UTC",
DefaultPriorityGroup: srequest.CreatePriorityGroup{
Description: model.Describable{
Name: "Default",
},
Priorities: []model.Colorable{
{
Describable: model.Describable{Name: "Normal"},
Color: stringPtr("#000000"),
},
},
},
AnonymousUser: model.Describable{
Name: "Anonymous",
},
OwnerRole: model.Describable{
Name: "Owner",
},
AnonymousRole: model.Describable{
Name: "Anonymous",
},
}
// Test JSON marshaling
jsonData, err := json.Marshal(signup)
require.NoError(t, err)
assert.NotEmpty(t, jsonData)
// Test JSON unmarshaling
var unmarshaled srequest.Signup
err = json.Unmarshal(jsonData, &unmarshaled)
require.NoError(t, err)
// Verify minimal request is valid
assert.Equal(t, signup.Account.Name, unmarshaled.Account.Name)
assert.Equal(t, signup.Account.Login, unmarshaled.Account.Login)
assert.Equal(t, signup.OrganizationName, unmarshaled.OrganizationName)
assert.Len(t, unmarshaled.DefaultPriorityGroup.Priorities, 1)
}
func TestSignupRequest_InvalidJSON(t *testing.T) {
invalidJSONs := []string{
`{"account": invalid}`,
`{"organizationName": 123}`,
`{"organizationTimeZone": true}`,
`{"defaultPriorityGroup": "not_an_object"}`,
`{"anonymousUser": []}`,
`{"anonymousRole": 456}`,
`{invalid json}`,
}
for i, invalidJSON := range invalidJSONs {
t.Run(fmt.Sprintf("Invalid JSON %d", i), func(t *testing.T) {
var signup srequest.Signup
err := json.Unmarshal([]byte(invalidJSON), &signup)
require.Error(t, err)
})
}
}
func TestSignupRequest_UnicodeCharacters(t *testing.T) {
signup := srequest.Signup{
Account: model.AccountData{
LoginData: model.LoginData{
UserDataBase: model.UserDataBase{
Login: "测试@example.com",
},
Password: "TestPassword123!",
},
Name: "Test 用户 Üser",
},
OrganizationName: "测试 Organization",
OrganizationTimeZone: "UTC",
DefaultPriorityGroup: srequest.CreatePriorityGroup{
Description: model.Describable{
Name: "默认 Priority Group",
},
Priorities: []model.Colorable{
{
Describable: model.Describable{Name: "高"},
Color: stringPtr("#FF0000"),
},
},
},
AnonymousUser: model.Describable{
Name: "匿名 User",
},
OwnerRole: model.Describable{
Name: "所有者",
},
AnonymousRole: model.Describable{
Name: "匿名",
},
}
// Test JSON marshaling
jsonData, err := json.Marshal(signup)
require.NoError(t, err)
assert.NotEmpty(t, jsonData)
// Test JSON unmarshaling
var unmarshaled srequest.Signup
err = json.Unmarshal(jsonData, &unmarshaled)
require.NoError(t, err)
// Verify unicode characters are properly handled
assert.Equal(t, "测试@example.com", unmarshaled.Account.Login)
assert.Equal(t, "Test 用户 Üser", unmarshaled.Account.Name)
assert.Equal(t, "测试 Organization", unmarshaled.OrganizationName)
assert.Equal(t, "默认 Priority Group", unmarshaled.DefaultPriorityGroup.Description.Name)
assert.Equal(t, "高", unmarshaled.DefaultPriorityGroup.Priorities[0].Name)
assert.Equal(t, "匿名 User", unmarshaled.AnonymousUser.Name)
assert.Equal(t, "所有者", unmarshaled.OwnerRole.Name)
assert.Equal(t, "匿名", unmarshaled.AnonymousRole.Name)
}
func TestCreatePriorityGroup_JSONSerialization(t *testing.T) {
priorityGroup := srequest.CreatePriorityGroup{
Description: model.Describable{
Name: "Test Priority Group",
},
Priorities: []model.Colorable{
{
Describable: model.Describable{Name: "Critical"},
Color: stringPtr("#FF0000"),
},
{
Describable: model.Describable{Name: "High"},
Color: stringPtr("#FF8000"),
},
{
Describable: model.Describable{Name: "Medium"},
Color: stringPtr("#FFFF00"),
},
{
Describable: model.Describable{Name: "Low"},
Color: stringPtr("#00FF00"),
},
},
}
// Test JSON marshaling
jsonData, err := json.Marshal(priorityGroup)
require.NoError(t, err)
assert.NotEmpty(t, jsonData)
// Test JSON unmarshaling
var unmarshaled srequest.CreatePriorityGroup
err = json.Unmarshal(jsonData, &unmarshaled)
require.NoError(t, err)
// Verify all fields are properly serialized/deserialized
assert.Equal(t, priorityGroup.Description.Name, unmarshaled.Description.Name)
assert.Equal(t, len(priorityGroup.Priorities), len(unmarshaled.Priorities))
for i, priority := range priorityGroup.Priorities {
assert.Equal(t, priority.Name, unmarshaled.Priorities[i].Name)
if priority.Color != nil && unmarshaled.Priorities[i].Color != nil {
assert.Equal(t, *priority.Color, *unmarshaled.Priorities[i].Color)
}
}
}
func TestCreatePriorityGroup_EmptyPriorities(t *testing.T) {
priorityGroup := srequest.CreatePriorityGroup{
Description: model.Describable{
Name: "Empty Priority Group",
},
Priorities: []model.Colorable{},
}
// Test JSON marshaling
jsonData, err := json.Marshal(priorityGroup)
require.NoError(t, err)
assert.NotEmpty(t, jsonData)
// Test JSON unmarshaling
var unmarshaled srequest.CreatePriorityGroup
err = json.Unmarshal(jsonData, &unmarshaled)
require.NoError(t, err)
// Verify empty priorities array is handled correctly
assert.Equal(t, priorityGroup.Description.Name, unmarshaled.Description.Name)
assert.Empty(t, unmarshaled.Priorities)
}
func TestCreatePriorityGroup_NilPriorities(t *testing.T) {
priorityGroup := srequest.CreatePriorityGroup{
Description: model.Describable{
Name: "Nil Priority Group",
},
Priorities: nil,
}
// Test JSON marshaling
jsonData, err := json.Marshal(priorityGroup)
require.NoError(t, err)
assert.NotEmpty(t, jsonData)
// Test JSON unmarshaling
var unmarshaled srequest.CreatePriorityGroup
err = json.Unmarshal(jsonData, &unmarshaled)
require.NoError(t, err)
// Verify nil priorities is handled correctly
assert.Equal(t, priorityGroup.Description.Name, unmarshaled.Description.Name)
assert.Nil(t, unmarshaled.Priorities)
}

View File

@@ -0,0 +1,16 @@
package srequest
import (
"github.com/tech/sendico/pkg/model"
)
type CreateStatus struct {
model.Colorable `json:"description"`
Icon string `json:"icon"`
IsFinal bool `json:"isFinal"`
}
type CreateStatusGroup struct {
Description model.Describable `json:"description"`
Statuses []CreateStatus `json:"statuses"`
}

View File

@@ -0,0 +1,20 @@
package srequest
import "go.mongodb.org/mongo-driver/bson/primitive"
// TaggableSingle is used for single tag operations (add/remove tag)
type TaggableSingle struct {
ObjectRef primitive.ObjectID `json:"objectRef"`
TagRef primitive.ObjectID `json:"tagRef"`
}
// TaggableMultiple is used for multiple tag operations (add tags, set tags)
type TaggableMultiple struct {
ObjectRef primitive.ObjectID `json:"objectRef"`
TagRefs []primitive.ObjectID `json:"tagRefs"`
}
// TaggableObject is used for object-only operations (remove all tags, get tags)
type TaggableObject struct {
ObjectRef primitive.ObjectID `json:"objectRef"`
}

View File

@@ -0,0 +1,62 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type accountData struct {
model.AccountPublic `json:",inline"`
IsAnonymous bool `json:"isAnonymous"`
}
type accountResponse struct {
authResponse `json:",inline"`
Account accountData `json:"account"`
}
func _createAccount(account *model.Account, isAnonymous bool) *accountData {
return &accountData{
AccountPublic: account.AccountPublic,
IsAnonymous: isAnonymous,
}
}
func _toAccount(account *model.Account, orgRef primitive.ObjectID) *accountData {
return _createAccount(account, model.AccountIsAnonymous(&account.UserDataBase, orgRef))
}
func Account(logger mlogger.Logger, account *model.Account, accessToken *TokenData) http.HandlerFunc {
return response.Ok(
logger,
&accountResponse{
Account: *_createAccount(account, false),
authResponse: authResponse{AccessToken: *accessToken},
},
)
}
type accountsResponse struct {
authResponse `json:",inline"`
Accounts []accountData `json:"accounts"`
}
func Accounts(logger mlogger.Logger, accounts []model.Account, orgRef primitive.ObjectID, accessToken *TokenData) http.HandlerFunc {
// Convert each account to its public representation.
publicAccounts := make([]accountData, len(accounts))
for i, a := range accounts {
publicAccounts[i] = *_toAccount(&a, orgRef)
}
return response.Ok(
logger,
&accountsResponse{
Accounts: publicAccounts,
authResponse: authResponse{AccessToken: *accessToken},
},
)
}

View File

@@ -0,0 +1,5 @@
package sresponse
type authResponse struct {
AccessToken TokenData `json:"accessToken"`
}

View File

@@ -0,0 +1,15 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mservice"
"go.uber.org/zap"
)
func BadRPassword(logger mlogger.Logger, source mservice.Type, err error) http.HandlerFunc {
logger.Info("Failed password validation check", zap.Error(err))
return response.BadRequest(logger, source, "invalid_request", err.Error())
}

View File

@@ -0,0 +1,24 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
)
type commentPreviewResponse struct {
authResponse `json:",inline"`
Comments []model.CommentPreview `json:"comments"`
}
func CommentPreview(logger mlogger.Logger, accessToken *TokenData, comments []model.CommentPreview) http.HandlerFunc {
return response.Ok(
logger,
&commentPreviewResponse{
Comments: comments,
authResponse: authResponse{AccessToken: *accessToken},
},
)
}

View File

@@ -0,0 +1,24 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
)
type dzoneResponse struct {
authResponse `json:",inline"`
DZone model.DZone `json:"dzone"`
}
func DZone(logger mlogger.Logger, dzone *model.DZone, accessToken *TokenData) http.HandlerFunc {
return response.Ok(
logger,
&dzoneResponse{
DZone: *dzone,
authResponse: authResponse{AccessToken: *accessToken},
},
)
}

View File

@@ -0,0 +1,16 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
)
type fileUpladed struct {
URL string `json:"url"`
}
func FileUploaded(logger mlogger.Logger, url string) http.HandlerFunc {
return response.Ok(logger, &fileUpladed{URL: url})
}

View File

@@ -0,0 +1,21 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
)
type invitationResp struct {
Invitation model.PublicInvitation `json:"invitation"`
}
func Invitation(logger mlogger.Logger, invitation *model.PublicInvitation) http.HandlerFunc {
return response.Ok(logger, &invitationResp{Invitation: *invitation})
}
func Invitations(logger mlogger.Logger, invitations []model.Invitation) http.HandlerFunc {
return response.Ok(logger, invitations)
}

View File

@@ -0,0 +1,27 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
)
type loginResponse struct {
accountResponse
RefreshToken TokenData `json:"refreshToken"`
}
func Login(logger mlogger.Logger, account *model.Account, accessToken, refreshToken *TokenData) http.HandlerFunc {
return response.Ok(
logger,
&loginResponse{
accountResponse: accountResponse{
Account: *_createAccount(account, false),
authResponse: authResponse{AccessToken: *accessToken},
},
RefreshToken: *refreshToken,
},
)
}

View File

@@ -0,0 +1,49 @@
package sresponse
import (
"encoding/json"
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/mservice"
)
type DynamicResponse[T any] struct {
authResponse `json:",inline"`
Items []T
// FieldName is the JSON key to use for the items.
FieldName string
}
func (dr DynamicResponse[T]) MarshalJSON() ([]byte, error) {
// Create a temporary map to hold the keys and values.
m := map[string]any{
dr.FieldName: dr.Items,
"accessToken": dr.AccessToken,
}
return json.Marshal(m)
}
type handler = func(logger mlogger.Logger, data any) http.HandlerFunc
func objectsAuth[T any](logger mlogger.Logger, items []T, accessToken *TokenData, resource mservice.Type, handler handler) http.HandlerFunc {
resp := &DynamicResponse[T]{
Items: items,
authResponse: authResponse{AccessToken: *accessToken},
FieldName: resource,
}
return handler(logger, resp)
}
func ObjectsAuth[T any](logger mlogger.Logger, items []T, accessToken *TokenData, resource mservice.Type) http.HandlerFunc {
return objectsAuth(logger, items, accessToken, resource, response.Ok)
}
func ObjectAuth[T any](logger mlogger.Logger, item *T, accessToken *TokenData, resource mservice.Type) http.HandlerFunc {
return ObjectsAuth(logger, []T{*item}, accessToken, resource)
}
func ObjectAuthCreated[T any](logger mlogger.Logger, item *T, accessToken *TokenData, resource mservice.Type) http.HandlerFunc {
return objectsAuth(logger, []T{*item}, accessToken, resource, response.Created)
}

View File

@@ -0,0 +1,35 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
)
type organizationsResponse struct {
authResponse `json:",inline"`
Organizations []model.Organization `json:"organizations"`
}
func Organization(logger mlogger.Logger, organization *model.Organization, accessToken *TokenData) http.HandlerFunc {
return Organizations(logger, []model.Organization{*organization}, accessToken)
}
func Organizations(logger mlogger.Logger, organizations []model.Organization, accessToken *TokenData) http.HandlerFunc {
return response.Ok(logger, organizationsResponse{
Organizations: organizations,
authResponse: authResponse{AccessToken: *accessToken},
})
}
type organizationPublicResponse struct {
Organizations []model.OrganizationBase `json:"organizations"`
}
func OrganizationPublic(logger mlogger.Logger, organization *model.OrganizationBase) http.HandlerFunc {
return response.Ok(logger, organizationPublicResponse{
[]model.OrganizationBase{*organization},
})
}

View File

@@ -0,0 +1,45 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
)
type permissionsDescription struct {
Roles []model.RoleDescription `json:"roles"`
Policies []model.PolicyDescription `json:"policies"`
}
type permissionsData struct {
Roles []model.Role `json:"roles"`
Policies []model.RolePolicy `json:"policies"`
Permissions []model.Permission `json:"permissions"`
}
type permissionsResponse struct {
authResponse `json:",inline"`
Descriptions permissionsDescription `json:"descriptions"`
Permissions permissionsData `json:"permissions"`
}
func Permisssions(logger mlogger.Logger,
rolesDescs []model.RoleDescription, policiesDescs []model.PolicyDescription,
roles []model.Role, policies []model.RolePolicy, permissions []model.Permission,
accessToken *TokenData,
) http.HandlerFunc {
return response.Ok(logger, permissionsResponse{
Descriptions: permissionsDescription{
Roles: rolesDescs,
Policies: policiesDescs,
},
Permissions: permissionsData{
Roles: roles,
Policies: policies,
Permissions: permissions,
},
authResponse: authResponse{AccessToken: *accessToken},
})
}

View File

@@ -0,0 +1,37 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
)
type projectsResponse struct {
authResponse `json:",inline"`
Projects []model.Project `json:"projects"`
}
func Projects(logger mlogger.Logger, projects []model.Project, accessToken *TokenData) http.HandlerFunc {
return response.Ok(logger, projectsResponse{
Projects: projects,
authResponse: authResponse{AccessToken: *accessToken},
})
}
func Project(logger mlogger.Logger, project *model.Project, accessToken *TokenData) http.HandlerFunc {
return Projects(logger, []model.Project{*project}, accessToken)
}
type projectPreviewsResponse struct {
authResponse `json:",inline"`
Previews []model.ProjectPreview `json:"previews"`
}
func ProjectsPreviews(logger mlogger.Logger, previews []model.ProjectPreview, accessToken *TokenData) http.HandlerFunc {
return response.Ok(logger, &projectPreviewsResponse{
authResponse: authResponse{AccessToken: *accessToken},
Previews: previews,
})
}

View File

@@ -0,0 +1,12 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/model"
)
type (
HandlerFunc = func(r *http.Request) http.HandlerFunc
AccountHandlerFunc = func(r *http.Request, account *model.Account, accessToken *TokenData) http.HandlerFunc
)

View File

@@ -0,0 +1,27 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
)
type resultAuth struct {
authResponse `json:",inline"`
response.Result `json:",inline"`
}
func Success(logger mlogger.Logger, accessToken *TokenData) http.HandlerFunc {
return response.Ok(logger, &resultAuth{
Result: response.Result{Result: true},
authResponse: authResponse{AccessToken: *accessToken},
})
}
func Failed(logger mlogger.Logger, accessToken *TokenData) http.HandlerFunc {
return response.Accepted(logger, &resultAuth{
Result: response.Result{Result: false},
authResponse: authResponse{AccessToken: *accessToken},
})
}

View File

@@ -0,0 +1,16 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
)
func SignUp(logger mlogger.Logger, account *model.Account) http.HandlerFunc {
return response.Ok(
logger,
&account.AccountBase,
)
}

View File

@@ -0,0 +1,25 @@
package sresponse
import (
"net/http"
"github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/pkg/model"
)
type statusesResponse struct {
authResponse `json:",inline"`
Statuses []model.Status `json:"statuses"`
}
func Statuses(logger mlogger.Logger, statuses []model.Status, accessToken *TokenData) http.HandlerFunc {
return response.Ok(logger, statusesResponse{
Statuses: statuses,
authResponse: authResponse{AccessToken: *accessToken},
})
}
func Status(logger mlogger.Logger, status *model.Status, accessToken *TokenData) http.HandlerFunc {
return Statuses(logger, []model.Status{*status}, accessToken)
}

View File

@@ -0,0 +1,8 @@
package sresponse
import "time"
type TokenData struct {
Token string `json:"token"`
Expiration time.Time `json:"expiration"`
}

View File

@@ -0,0 +1,57 @@
package ws
import (
"net/http"
api "github.com/tech/sendico/pkg/api/http"
r "github.com/tech/sendico/pkg/api/http/response"
"github.com/tech/sendico/pkg/mlogger"
"github.com/tech/sendico/server/interface/api/ws"
"go.uber.org/zap"
"golang.org/x/net/websocket"
)
func respond(logger mlogger.Logger, conn *websocket.Conn, messageType, apiStatus, requestID string, data any) {
message := ws.Message{
BaseResponse: r.BaseResponse{
Status: apiStatus,
Data: data,
},
ID: requestID,
MessageType: messageType,
}
if err := websocket.JSON.Send(conn, message); err != nil {
logger.Warn("Failed to send error message", zap.Error(err), zap.Any("message", message))
}
}
func errorf(logger mlogger.Logger, messageType, requestID string, conn *websocket.Conn, resp r.ErrorResponse) {
logger.Debug(
"Writing error sresponse",
zap.String("error", resp.Error),
zap.String("details", resp.Details),
zap.Int("code", resp.Code),
)
respond(logger, conn, messageType, api.MSError, requestID, &resp)
}
func Ok(logger mlogger.Logger, requestID string, data any) ws.ResponseHandler {
res := func(messageType string, conn *websocket.Conn) {
logger.Debug("Successfully executed request", zap.Any("sresponse", data))
respond(logger, conn, messageType, api.MSSuccess, requestID, data)
}
return res
}
func Internal(logger mlogger.Logger, requestID string, err error) ws.ResponseHandler {
res := func(messageType string, conn *websocket.Conn) {
errorf(logger, messageType, requestID, conn,
r.ErrorResponse{
Error: "internal_error",
Details: err.Error(),
Code: http.StatusInternalServerError,
})
}
return res
}

View File

@@ -0,0 +1,9 @@
package ws
import (
ac "github.com/tech/sendico/server/internal/api/config"
)
type (
Config = ac.WebSocketConfig
)

View File

@@ -0,0 +1,12 @@
package ws
import (
"context"
"golang.org/x/net/websocket"
)
type (
ResponseHandler func(messageType string, conn *websocket.Conn)
HandlerFunc func(ctx context.Context, msg Message) ResponseHandler
)

View File

@@ -0,0 +1,9 @@
package ws
import "github.com/tech/sendico/pkg/api/http/response"
type Message struct {
response.BaseResponse
ID string `json:"id"`
MessageType string `json:"messageType"`
}