refactor: move backend files to backend/ directory
Reorganize project structure: - backend/cmd/openteam/ — entry point - backend/internal/ — core packages - backend/middleware/ — HTTP middleware - backend/router/ — route setup - backend/wire/ — dependency injection - backend/pkg/ — shared utilities - backend/go.mod, go.sum — Go module files Updated Makefile to work from backend/ directory. Removed old lowercase makefile.
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"opencatd-open/internal/dao"
|
||||
"opencatd-open/internal/store"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ApiKeyServiceImpl struct {
|
||||
db *gorm.DB
|
||||
apiKeyRepo *dao.ApiKeyDAO
|
||||
}
|
||||
|
||||
func NewApiKeyService(db *gorm.DB, apiKeyDao *dao.ApiKeyDAO) *ApiKeyServiceImpl {
|
||||
return &ApiKeyServiceImpl{db: db, apiKeyRepo: apiKeyDao}
|
||||
}
|
||||
|
||||
func (s *ApiKeyServiceImpl) CreateApiKey(ctx context.Context, apikey *store.APIKey) error {
|
||||
return s.apiKeyRepo.Create(apikey)
|
||||
}
|
||||
|
||||
func (s *ApiKeyServiceImpl) GetApiKey(ctx context.Context, id uint64) (*store.APIKey, error) {
|
||||
return s.apiKeyRepo.GetByID(id)
|
||||
}
|
||||
|
||||
func (s *ApiKeyServiceImpl) ListApiKey(ctx context.Context, userID uint64, limit, offset int) ([]*store.APIKey, int64, error) {
|
||||
return s.apiKeyRepo.ListByUserID(userID, limit, offset)
|
||||
}
|
||||
|
||||
func (s *ApiKeyServiceImpl) UpdateApiKey(ctx context.Context, apikey *store.APIKey) error {
|
||||
return s.apiKeyRepo.Update(apikey)
|
||||
}
|
||||
|
||||
func (s *ApiKeyServiceImpl) DeleteApiKey(ctx context.Context, id uint64) error {
|
||||
return s.apiKeyRepo.Delete(id)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"opencatd-open/internal/channel"
|
||||
"opencatd-open/internal/dao"
|
||||
"opencatd-open/internal/store"
|
||||
"opencatd-open/internal/pkg/crypto"
|
||||
)
|
||||
|
||||
type ChannelServiceImpl struct {
|
||||
channelDAO *dao.ChannelDAO
|
||||
channelSvc *channel.Service
|
||||
}
|
||||
|
||||
func NewChannelService(channelDAO *dao.ChannelDAO, channelSvc *channel.Service) *ChannelServiceImpl {
|
||||
return &ChannelServiceImpl{
|
||||
channelDAO: channelDAO,
|
||||
channelSvc: channelSvc,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ChannelServiceImpl) Create(ctx context.Context, ch *store.Channel) error {
|
||||
return s.channelDAO.Create(ch)
|
||||
}
|
||||
|
||||
func (s *ChannelServiceImpl) GetByID(ctx context.Context, id uint64) (*store.Channel, error) {
|
||||
return s.channelDAO.GetByID(id)
|
||||
}
|
||||
|
||||
func (s *ChannelServiceImpl) List(ctx context.Context, limit, offset int) ([]*store.Channel, int64, error) {
|
||||
return s.channelDAO.List(limit, offset)
|
||||
}
|
||||
|
||||
func (s *ChannelServiceImpl) ListEnabled(ctx context.Context) ([]*store.Channel, error) {
|
||||
return s.channelDAO.ListEnabled()
|
||||
}
|
||||
|
||||
func (s *ChannelServiceImpl) Update(ctx context.Context, ch *store.Channel) error {
|
||||
return s.channelDAO.Update(ch)
|
||||
}
|
||||
|
||||
func (s *ChannelServiceImpl) Delete(ctx context.Context, id uint64) error {
|
||||
return s.channelDAO.Delete(id)
|
||||
}
|
||||
|
||||
// GetAPIKey decrypts the channel's API key
|
||||
func (s *ChannelServiceImpl) GetAPIKey(ctx context.Context, channelID uint64) (string, error) {
|
||||
ch, err := s.channelDAO.GetByID(channelID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return crypto.Decrypt(ch.APIKeyEnc)
|
||||
}
|
||||
|
||||
// SelectForModel selects the best channel for a model
|
||||
func (s *ChannelServiceImpl) SelectForModel(ctx context.Context, modelName string) (*store.Channel, error) {
|
||||
return s.channelSvc.SelectChannel(ctx, modelName)
|
||||
}
|
||||
|
||||
// BindModels binds models to a channel
|
||||
func (s *ChannelServiceImpl) BindModels(ctx context.Context, channelID uint64, bindings []store.ChannelModelBinding) error {
|
||||
return s.channelDAO.BindModels(channelID, bindings)
|
||||
}
|
||||
|
||||
// GetChannelModels returns models bound to a channel
|
||||
func (s *ChannelServiceImpl) GetChannelModels(ctx context.Context, channelID uint64) ([]store.ChannelModelBinding, error) {
|
||||
return s.channelDAO.GetChannelModels(channelID)
|
||||
}
|
||||
|
||||
// GetModelChannels returns channels for a model
|
||||
func (s *ChannelServiceImpl) GetModelChannels(ctx context.Context, modelName string) ([]*store.Channel, error) {
|
||||
return s.channelDAO.GetEnabledChannelsByModel(modelName)
|
||||
}
|
||||
|
||||
// RecordSuccess records a successful request
|
||||
func (s *ChannelServiceImpl) RecordSuccess(channelID uint64) {
|
||||
s.channelSvc.RecordSuccess(channelID)
|
||||
}
|
||||
|
||||
// RecordFailure records a failed request
|
||||
func (s *ChannelServiceImpl) RecordFailure(channelID uint64) {
|
||||
s.channelSvc.RecordFailure(channelID)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"opencatd-open/internal/dao"
|
||||
"opencatd-open/internal/store"
|
||||
)
|
||||
|
||||
type ModelServiceImpl struct {
|
||||
modelDAO *dao.ModelDAO
|
||||
channelDAO *dao.ChannelDAO
|
||||
}
|
||||
|
||||
func NewModelService(modelDAO *dao.ModelDAO, channelDAO *dao.ChannelDAO) *ModelServiceImpl {
|
||||
return &ModelServiceImpl{
|
||||
modelDAO: modelDAO,
|
||||
channelDAO: channelDAO,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ModelServiceImpl) Create(ctx context.Context, model *store.Model) error {
|
||||
return s.modelDAO.Create(model)
|
||||
}
|
||||
|
||||
func (s *ModelServiceImpl) GetByID(ctx context.Context, id uint64) (*store.Model, error) {
|
||||
return s.modelDAO.GetByID(id)
|
||||
}
|
||||
|
||||
func (s *ModelServiceImpl) GetByName(ctx context.Context, name string) (*store.Model, error) {
|
||||
return s.modelDAO.GetByName(name)
|
||||
}
|
||||
|
||||
func (s *ModelServiceImpl) List(ctx context.Context, limit, offset int) ([]*store.Model, int64, error) {
|
||||
return s.modelDAO.List(limit, offset)
|
||||
}
|
||||
|
||||
func (s *ModelServiceImpl) ListEnabled(ctx context.Context) ([]*store.Model, error) {
|
||||
return s.modelDAO.ListEnabled()
|
||||
}
|
||||
|
||||
func (s *ModelServiceImpl) Update(ctx context.Context, model *store.Model) error {
|
||||
return s.modelDAO.Update(model)
|
||||
}
|
||||
|
||||
func (s *ModelServiceImpl) Delete(ctx context.Context, id uint64) error {
|
||||
return s.modelDAO.Delete(id)
|
||||
}
|
||||
|
||||
func (s *ModelServiceImpl) Upsert(ctx context.Context, model *store.Model) error {
|
||||
return s.modelDAO.Upsert(model)
|
||||
}
|
||||
|
||||
// BindChannel binds a model to a channel
|
||||
func (s *ModelServiceImpl) BindChannel(ctx context.Context, modelID, channelID uint64, upstreamModel string, weight int) error {
|
||||
binding := store.ChannelModelBinding{
|
||||
ModelID: modelID,
|
||||
ChannelID: channelID,
|
||||
UpstreamModel: upstreamModel,
|
||||
Weight: weight,
|
||||
}
|
||||
return s.channelDAO.BindModels(channelID, []store.ChannelModelBinding{binding})
|
||||
}
|
||||
|
||||
// ListChannelModels lists all models bound to a channel
|
||||
func (s *ModelServiceImpl) ListChannelModels(ctx context.Context, channelID uint64) ([]store.ChannelModelBinding, error) {
|
||||
return s.channelDAO.GetChannelModels(channelID)
|
||||
}
|
||||
|
||||
// ListModelChannels lists all channels for a model
|
||||
func (s *ModelServiceImpl) ListModelChannels(ctx context.Context, modelName string) ([]*store.Channel, error) {
|
||||
return s.channelDAO.GetEnabledChannelsByModel(modelName)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"opencatd-open/internal/dao"
|
||||
"opencatd-open/internal/store"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type TokenServiceImpl struct {
|
||||
db *gorm.DB
|
||||
tokenRepo *dao.TokenDAO
|
||||
}
|
||||
|
||||
func NewTokenService(db *gorm.DB, tokenRepo *dao.TokenDAO) *TokenServiceImpl {
|
||||
return &TokenServiceImpl{
|
||||
db: db,
|
||||
tokenRepo: tokenRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TokenServiceImpl) GetByKey(ctx context.Context, key string) (*store.User, error) {
|
||||
return t.tokenRepo.GetByKey(key)
|
||||
}
|
||||
|
||||
func (t *TokenServiceImpl) GetByID(ctx context.Context, id uint64) (*store.User, error) {
|
||||
return t.tokenRepo.GetByID(id)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"opencatd-open/pkg/config"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UsageService struct {
|
||||
Ctx context.Context
|
||||
Cfg *config.Config
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
func NewUsageService(ctx context.Context, cfg *config.Config, db *gorm.DB) *UsageService {
|
||||
return &UsageService{
|
||||
Ctx: ctx,
|
||||
Cfg: cfg,
|
||||
DB: db,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"opencatd-open/internal/dao"
|
||||
"opencatd-open/internal/store"
|
||||
"opencatd-open/pkg/config"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserServiceImpl struct {
|
||||
cfg *config.Config
|
||||
db *gorm.DB
|
||||
userRepo *dao.UserDAO
|
||||
}
|
||||
|
||||
func NewUserService(cfg *config.Config, db *gorm.DB, userRepo *dao.UserDAO) *UserServiceImpl {
|
||||
return &UserServiceImpl{
|
||||
cfg: cfg,
|
||||
db: db,
|
||||
userRepo: userRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UserServiceImpl) GetByID(ctx context.Context, id uint64) (*store.User, error) {
|
||||
return s.userRepo.GetByID(id)
|
||||
}
|
||||
|
||||
func (s *UserServiceImpl) GetByUsername(ctx context.Context, username string) (*store.User, error) {
|
||||
return s.userRepo.GetByUsername(username)
|
||||
}
|
||||
|
||||
func (s *UserServiceImpl) List(ctx context.Context, limit, offset int) ([]*store.User, int64, error) {
|
||||
return s.userRepo.List(limit, offset)
|
||||
}
|
||||
|
||||
func (s *UserServiceImpl) Create(ctx context.Context, user *store.User) error {
|
||||
return s.userRepo.Create(user)
|
||||
}
|
||||
|
||||
func (s *UserServiceImpl) Update(ctx context.Context, user *store.User) error {
|
||||
return s.userRepo.Update(user)
|
||||
}
|
||||
|
||||
func (s *UserServiceImpl) Delete(ctx context.Context, id uint64) error {
|
||||
return s.userRepo.Delete(id)
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"opencatd-open/internal/store"
|
||||
"opencatd-open/pkg/config"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-webauthn/webauthn/protocol"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type WebAuthnUser struct {
|
||||
User *store.User
|
||||
Credentials []webauthn.Credential
|
||||
}
|
||||
|
||||
func (u *WebAuthnUser) WebAuthnID() []byte {
|
||||
return []byte(strconv.FormatUint(u.User.ID, 10))
|
||||
}
|
||||
|
||||
func (u *WebAuthnUser) WebAuthnName() string {
|
||||
return u.User.Username
|
||||
}
|
||||
|
||||
func (u *WebAuthnUser) WebAuthnDisplayName() string {
|
||||
return u.User.Username
|
||||
}
|
||||
|
||||
func (u *WebAuthnUser) WebAuthnCredentials() []webauthn.Credential {
|
||||
return u.Credentials
|
||||
}
|
||||
|
||||
func (u *WebAuthnUser) WebAuthnCredentialDescriptors() (descriptors []protocol.CredentialDescriptor) {
|
||||
credentials := u.WebAuthnCredentials()
|
||||
descriptors = make([]protocol.CredentialDescriptor, len(credentials))
|
||||
for i, credential := range credentials {
|
||||
descriptors[i] = credential.Descriptor()
|
||||
}
|
||||
return descriptors
|
||||
}
|
||||
|
||||
type WebAuthnService struct {
|
||||
cfg *config.Config
|
||||
DB *gorm.DB
|
||||
WebAuthn *webauthn.WebAuthn
|
||||
}
|
||||
|
||||
func NewWebAuthnService(cfg *config.Config, db *gorm.DB) (*WebAuthnService, error) {
|
||||
wconfig := &webauthn.Config{
|
||||
RPDisplayName: cfg.AppName,
|
||||
RPID: cfg.RPID,
|
||||
RPOrigins: cfg.RPOrigins,
|
||||
AuthenticatorSelection: protocol.AuthenticatorSelection{
|
||||
RequireResidentKey: protocol.ResidentKeyRequired(),
|
||||
ResidentKey: protocol.ResidentKeyRequirementRequired,
|
||||
UserVerification: protocol.VerificationPreferred,
|
||||
},
|
||||
}
|
||||
|
||||
wa, err := webauthn.New(wconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &WebAuthnService{
|
||||
cfg: cfg,
|
||||
DB: db,
|
||||
WebAuthn: wa,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *WebAuthnService) GetUserWithCredentials(userID uint64) (*WebAuthnUser, error) {
|
||||
var user store.User
|
||||
if err := s.DB.First(&user, userID).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var passkeys []store.Passkey
|
||||
if err := s.DB.Where("user_id = ?", userID).Find(&passkeys).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credentials := make([]webauthn.Credential, len(passkeys))
|
||||
for i, pk := range passkeys {
|
||||
credentialIDBytes, err := base64.StdEncoding.DecodeString(pk.CredentialID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode CredentialID: %w", err)
|
||||
}
|
||||
publicKeyBytes, err := base64.StdEncoding.DecodeString(pk.PublicKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode PublicKey: %w", err)
|
||||
}
|
||||
aaguidBytes, err := base64.StdEncoding.DecodeString(pk.AAGUID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode AAGUID: %w", err)
|
||||
}
|
||||
|
||||
var transport []protocol.AuthenticatorTransport
|
||||
if pk.Transport != "" {
|
||||
transport = []protocol.AuthenticatorTransport{protocol.AuthenticatorTransport(pk.Transport)}
|
||||
}
|
||||
|
||||
credentials[i] = webauthn.Credential{
|
||||
ID: credentialIDBytes,
|
||||
PublicKey: publicKeyBytes,
|
||||
AttestationType: pk.AttestationType,
|
||||
Transport: transport,
|
||||
Flags: webauthn.CredentialFlags{
|
||||
UserPresent: true,
|
||||
UserVerified: true,
|
||||
BackupEligible: pk.BackupEligible,
|
||||
BackupState: pk.BackupState,
|
||||
},
|
||||
Authenticator: webauthn.Authenticator{
|
||||
AAGUID: aaguidBytes,
|
||||
SignCount: uint32(pk.SignCount),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &WebAuthnUser{
|
||||
User: &user,
|
||||
Credentials: credentials,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *WebAuthnService) BeginRegistration(userID uint64) (*protocol.CredentialCreation, error) {
|
||||
user, err := s.GetUserWithCredentials(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
options, _, err := s.WebAuthn.BeginRegistration(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func (s *WebAuthnService) FinishRegistration(userID uint64, response *http.Request, deviceName string) (*store.Passkey, error) {
|
||||
user, err := s.GetUserWithCredentials(userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
credential, err := s.WebAuthn.FinishRegistration(user, webauthn.SessionData{}, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var transport string
|
||||
if len(credential.Transport) > 0 {
|
||||
transport = string(credential.Transport[0])
|
||||
}
|
||||
|
||||
passkey := &store.Passkey{
|
||||
UserID: userID,
|
||||
CredentialID: base64.StdEncoding.EncodeToString(credential.ID),
|
||||
PublicKey: base64.StdEncoding.EncodeToString(credential.PublicKey),
|
||||
AttestationType: string(credential.AttestationType),
|
||||
AAGUID: base64.StdEncoding.EncodeToString(credential.Authenticator.AAGUID),
|
||||
SignCount: uint64(credential.Authenticator.SignCount),
|
||||
Name: deviceName,
|
||||
DeviceType: strings.TrimSpace(fmt.Sprintf("%s", deviceName)),
|
||||
LastUsedAt: time.Now().Unix(),
|
||||
BackupEligible: credential.Flags.BackupEligible,
|
||||
BackupState: credential.Flags.BackupState,
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
if err := s.DB.Create(passkey).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return passkey, nil
|
||||
}
|
||||
|
||||
func (s *WebAuthnService) BeginLogin() (*protocol.CredentialAssertion, error) {
|
||||
options, _, err := s.WebAuthn.BeginDiscoverableLogin()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func (s *WebAuthnService) ListPasskeys(userID uint64) ([]store.Passkey, error) {
|
||||
var passkeys []store.Passkey
|
||||
if err := s.DB.Where("user_id = ?", userID).Find(&passkeys).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return passkeys, nil
|
||||
}
|
||||
|
||||
func (s *WebAuthnService) DeletePasskey(userID uint64, passkeyID uint64) error {
|
||||
return s.DB.Where("id = ? AND user_id = ?", passkeyID, userID).Delete(&store.Passkey{}).Error
|
||||
}
|
||||
Reference in New Issue
Block a user