Restructure the monorepo into clear top-level directories: - backend/: Go module root (cmd, internal, pkg, configs, migrations, docs/swagger, scripts, tests, go.mod, Makefile, .air.toml) - deploy/: Docker (Dockerfile, docker-compose*), quickstart, fluentd - docs/: project documentation + reports/ (moved from repo root) - AGENTS.md: new AI coding-agent guide at repo root Update all references to the new layout: - Dockerfile: COPY backend/go.mod, COPY backend/ (context = repo root) - docker-compose files: context ../.., dockerfile deploy/docker/Dockerfile, env_file ../../.env, volume mounts ../../backend:/app - deploy/quickstart/compose.yaml: dockerfile deploy/docker/Dockerfile - CI: working-directory: backend for go commands, file deploy/docker/Dockerfile, coverage path backend/coverage.out, health_check backend/scripts/ - backend/Makefile: docker target uses -f ../deploy/docker/Dockerfile ../ - README: architecture tree, quickstart, config paths updated Move root stray scripts (rename_models.*, run_m11_tests.sh, verify_build.sh, gorm_bool_main.go) to backend/scripts/legacy/. All moves via git mv to preserve history. Build, vet, SQLite tests, and docker compose config verified.
251 lines
8.6 KiB
Go
251 lines
8.6 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/internal/repository"
|
|
applogger "github.com/gochat/gochat/pkg/logger"
|
|
pkgvalidator "github.com/gochat/gochat/pkg/validator"
|
|
)
|
|
|
|
// AssignmentPolicyV2Service implements business logic for AssignmentPolicy V2 operations.
|
|
// Reference: Chatwoot app/controllers/api/v1/assignment_policies_controller.rb
|
|
type AssignmentPolicyV2Service struct {
|
|
policyRepo *repository.AssignmentPolicyV2Repo
|
|
apInboxRepo *repository.AssignmentPolicyInboxRepo
|
|
}
|
|
|
|
// NewAssignmentPolicyV2Service creates a new AssignmentPolicyV2 service.
|
|
func NewAssignmentPolicyV2Service(
|
|
policyRepo *repository.AssignmentPolicyV2Repo,
|
|
apInboxRepo *repository.AssignmentPolicyInboxRepo,
|
|
) *AssignmentPolicyV2Service {
|
|
return &AssignmentPolicyV2Service{policyRepo: policyRepo, apInboxRepo: apInboxRepo}
|
|
}
|
|
|
|
// CreatePolicyV2Request is the DTO for creating a V2 assignment policy.
|
|
type CreatePolicyV2Request struct {
|
|
Name string `json:"name" validate:"required"`
|
|
Description string `json:"description,omitempty"`
|
|
Type model.AssignmentPolicyV2Type `json:"type" validate:"required,oneof=round_robin fair best_skill_match"`
|
|
}
|
|
|
|
// UpdatePolicyV2Request is the DTO for updating a V2 assignment policy.
|
|
type UpdatePolicyV2Request struct {
|
|
Name string `json:"name,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
Type model.AssignmentPolicyV2Type `json:"type,omitempty" validate:"omitempty,oneof=round_robin fair best_skill_match"`
|
|
}
|
|
|
|
// AddInboxRequestV2 is the DTO for adding an inbox to a V2 policy.
|
|
type AddInboxRequestV2 struct {
|
|
InboxID uint `json:"inbox_id" validate:"required"`
|
|
}
|
|
|
|
// Create creates a new assignment policy V2.
|
|
func (s *AssignmentPolicyV2Service) Create(ctx context.Context, accountID uint, req *CreatePolicyV2Request) (*model.AssignmentPolicyV2, error) {
|
|
if err := pkgvalidator.ValidateStruct(req); err != nil {
|
|
return nil, fmt.Errorf("validation error: %w", err)
|
|
}
|
|
|
|
policy := &model.AssignmentPolicyV2{
|
|
AccountID: accountID,
|
|
Name: req.Name,
|
|
Description: req.Description,
|
|
Type: req.Type,
|
|
}
|
|
|
|
if err := s.policyRepo.Create(ctx, policy); err != nil {
|
|
return nil, fmt.Errorf("create assignment policy v2: %w", err)
|
|
}
|
|
|
|
applogger.L().Infof("Assignment policy V2 %d created for account %d (type=%s)", policy.ID, accountID, policy.Type)
|
|
return policy, nil
|
|
}
|
|
|
|
// Get retrieves an assignment policy V2 by ID.
|
|
func (s *AssignmentPolicyV2Service) Get(ctx context.Context, accountID, policyID uint) (*model.AssignmentPolicyV2, error) {
|
|
policy, err := s.policyRepo.FindByID(ctx, policyID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("assignment policy v2 not found: %w", err)
|
|
}
|
|
if policy.AccountID != accountID {
|
|
return nil, fmt.Errorf("assignment policy v2 %d does not belong to account %d", policyID, accountID)
|
|
}
|
|
return policy, nil
|
|
}
|
|
|
|
// List retrieves all assignment policies V2 for an account.
|
|
func (s *AssignmentPolicyV2Service) List(ctx context.Context, accountID uint) ([]model.AssignmentPolicyV2, error) {
|
|
return s.policyRepo.FindByAccount(ctx, accountID)
|
|
}
|
|
|
|
// Update updates an assignment policy V2.
|
|
func (s *AssignmentPolicyV2Service) Update(ctx context.Context, accountID, policyID uint, req *UpdatePolicyV2Request) (*model.AssignmentPolicyV2, error) {
|
|
policy, err := s.Get(ctx, accountID, policyID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if req.Name != "" {
|
|
policy.Name = req.Name
|
|
}
|
|
if req.Description != "" {
|
|
policy.Description = req.Description
|
|
}
|
|
if req.Type != "" {
|
|
policy.Type = req.Type
|
|
}
|
|
|
|
if err := s.policyRepo.Update(ctx, policy); err != nil {
|
|
return nil, fmt.Errorf("update assignment policy v2: %w", err)
|
|
}
|
|
|
|
applogger.L().Infof("Assignment policy V2 %d updated for account %d", policyID, accountID)
|
|
return policy, nil
|
|
}
|
|
|
|
// Delete deletes an assignment policy V2 (and its inbox associations).
|
|
func (s *AssignmentPolicyV2Service) Delete(ctx context.Context, accountID, policyID uint) error {
|
|
policy, err := s.Get(ctx, accountID, policyID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Remove all inbox associations first
|
|
if err := s.apInboxRepo.DeleteByAssignmentPolicy(ctx, policy.ID); err != nil {
|
|
return fmt.Errorf("delete inbox associations: %w", err)
|
|
}
|
|
|
|
if err := s.policyRepo.Delete(ctx, policy.ID); err != nil {
|
|
return fmt.Errorf("delete assignment policy v2: %w", err)
|
|
}
|
|
|
|
applogger.L().Infof("Assignment policy V2 %d deleted for account %d", policyID, accountID)
|
|
return nil
|
|
}
|
|
|
|
// ListInboxes retrieves all inboxes associated with a policy.
|
|
func (s *AssignmentPolicyV2Service) ListInboxes(ctx context.Context, accountID, policyID uint) ([]model.AssignmentPolicyInbox, error) {
|
|
policy, err := s.Get(ctx, accountID, policyID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return s.apInboxRepo.FindByAssignmentPolicy(ctx, policy.ID)
|
|
}
|
|
|
|
// AddInbox associates an inbox with a policy.
|
|
func (s *AssignmentPolicyV2Service) AddInbox(ctx context.Context, accountID, policyID uint, req *AddInboxRequestV2) (*model.AssignmentPolicyInbox, error) {
|
|
if err := pkgvalidator.ValidateStruct(req); err != nil {
|
|
return nil, fmt.Errorf("validation error: %w", err)
|
|
}
|
|
|
|
policy, err := s.Get(ctx, accountID, policyID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check if inbox already has a policy association
|
|
existing, findErr := s.apInboxRepo.FindByInbox(ctx, req.InboxID)
|
|
if findErr == nil && existing != nil {
|
|
// Inbox already has a policy — update it to the new policy
|
|
existing.AssignmentPolicyID = policy.ID
|
|
if err := s.apInboxRepo.Create(ctx, existing); err != nil {
|
|
return nil, fmt.Errorf("inbox already has a policy association: %w", err)
|
|
}
|
|
return existing, nil
|
|
}
|
|
|
|
apInbox := &model.AssignmentPolicyInbox{
|
|
AssignmentPolicyID: policy.ID,
|
|
InboxID: req.InboxID,
|
|
AccountID: accountID,
|
|
}
|
|
|
|
if err := s.apInboxRepo.Create(ctx, apInbox); err != nil {
|
|
return nil, fmt.Errorf("add inbox to policy: %w", err)
|
|
}
|
|
|
|
applogger.L().Infof("Inbox %d added to assignment policy V2 %d", req.InboxID, policyID)
|
|
return apInbox, nil
|
|
}
|
|
|
|
// RemoveInbox removes an inbox association from a policy.
|
|
func (s *AssignmentPolicyV2Service) RemoveInbox(ctx context.Context, accountID, policyID, inboxID uint) error {
|
|
policy, err := s.Get(ctx, accountID, policyID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
apInboxes, err := s.apInboxRepo.FindByAssignmentPolicy(ctx, policy.ID)
|
|
if err != nil {
|
|
return fmt.Errorf("find inbox associations: %w", err)
|
|
}
|
|
|
|
for _, apInbox := range apInboxes {
|
|
if apInbox.InboxID == inboxID {
|
|
if err := s.apInboxRepo.Delete(ctx, apInbox.ID); err != nil {
|
|
return fmt.Errorf("remove inbox from policy: %w", err)
|
|
}
|
|
applogger.L().Infof("Inbox %d removed from assignment policy V2 %d", inboxID, policyID)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return fmt.Errorf("inbox %d not associated with policy %d", inboxID, policyID)
|
|
}
|
|
|
|
// GetInboxPolicy retrieves the assignment policy for a specific inbox.
|
|
func (s *AssignmentPolicyV2Service) GetInboxPolicy(ctx context.Context, accountID, inboxID uint) (*model.AssignmentPolicyV2, error) {
|
|
apInbox, err := s.apInboxRepo.FindByInbox(ctx, inboxID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("inbox %d has no assignment policy v2: %w", inboxID, err)
|
|
}
|
|
if apInbox.AccountID != accountID {
|
|
return nil, fmt.Errorf("inbox policy does not belong to account %d", accountID)
|
|
}
|
|
|
|
policy, err := s.policyRepo.FindByID(ctx, apInbox.AssignmentPolicyID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("assignment policy v2 not found: %w", err)
|
|
}
|
|
return policy, nil
|
|
}
|
|
|
|
// SetInboxPolicy sets the assignment policy for a specific inbox.
|
|
func (s *AssignmentPolicyV2Service) SetInboxPolicy(ctx context.Context, accountID, inboxID, policyID uint) (*model.AssignmentPolicyInbox, error) {
|
|
policy, err := s.Get(ctx, accountID, policyID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Check if inbox already has a policy — remove it first
|
|
s.apInboxRepo.DeleteByInbox(ctx, inboxID) // ignore error (may not exist)
|
|
|
|
apInbox := &model.AssignmentPolicyInbox{
|
|
AssignmentPolicyID: policy.ID,
|
|
InboxID: inboxID,
|
|
AccountID: accountID,
|
|
}
|
|
|
|
if err := s.apInboxRepo.Create(ctx, apInbox); err != nil {
|
|
return nil, fmt.Errorf("set inbox policy: %w", err)
|
|
}
|
|
|
|
applogger.L().Infof("Inbox %d set to assignment policy V2 %d", inboxID, policyID)
|
|
return apInbox, nil
|
|
}
|
|
|
|
// DeleteInboxPolicy removes the assignment policy association from an inbox.
|
|
func (s *AssignmentPolicyV2Service) DeleteInboxPolicy(ctx context.Context, accountID, inboxID uint) error {
|
|
if err := s.apInboxRepo.DeleteByInbox(ctx, inboxID); err != nil {
|
|
return fmt.Errorf("delete inbox policy: %w", err)
|
|
}
|
|
|
|
applogger.L().Infof("Assignment policy V2 removed from inbox %d", inboxID)
|
|
return nil
|
|
}
|