feat(backend): implement task creation and admin web
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
package webui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"cmroubao/backend-api/internal/domain"
|
||||
"cmroubao/backend-api/internal/usecase"
|
||||
)
|
||||
|
||||
const localAdminSubject = "local-admin"
|
||||
|
||||
type UsecaseAdapter struct {
|
||||
tasks *usecase.TaskService
|
||||
assets *usecase.AssetService
|
||||
}
|
||||
|
||||
func NewUsecaseAdapter(
|
||||
tasks *usecase.TaskService,
|
||||
assets *usecase.AssetService,
|
||||
) (*UsecaseAdapter, error) {
|
||||
if tasks == nil || assets == nil {
|
||||
return nil, errors.New("admin web use cases are required")
|
||||
}
|
||||
return &UsecaseAdapter{
|
||||
tasks: tasks,
|
||||
assets: assets,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (adapter *UsecaseAdapter) ListTasks(
|
||||
ctx context.Context,
|
||||
input ListTasksInput,
|
||||
) (TaskList, error) {
|
||||
var status *string
|
||||
if input.Status != "" {
|
||||
value := input.Status
|
||||
status = &value
|
||||
}
|
||||
page, err := adapter.tasks.List(ctx, usecase.ListTasksQuery{
|
||||
CreatorSubject: localAdminSubject,
|
||||
Status: status,
|
||||
Query: input.Query,
|
||||
Limit: input.Limit,
|
||||
Cursor: input.Cursor,
|
||||
})
|
||||
if err != nil {
|
||||
return TaskList{}, mapUsecaseError(err)
|
||||
}
|
||||
items := make([]TaskSummary, 0, len(page.Items))
|
||||
for _, item := range page.Items {
|
||||
items = append(items, taskSummaryFrom(item))
|
||||
}
|
||||
return TaskList{
|
||||
Items: items,
|
||||
NextCursor: page.NextCursor,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (adapter *UsecaseAdapter) GetTask(
|
||||
ctx context.Context,
|
||||
taskID string,
|
||||
) (Task, error) {
|
||||
detail, err := adapter.tasks.Get(ctx, localAdminSubject, taskID)
|
||||
if err != nil {
|
||||
return Task{}, mapUsecaseError(err)
|
||||
}
|
||||
return taskFromDetail(detail), nil
|
||||
}
|
||||
|
||||
func (adapter *UsecaseAdapter) UploadReference(
|
||||
ctx context.Context,
|
||||
input UploadReferenceInput,
|
||||
) (UploadedAsset, error) {
|
||||
result, err := adapter.assets.UploadTaskReference(
|
||||
ctx,
|
||||
usecase.UploadTaskReferenceCommand{
|
||||
CreatorSubject: localAdminSubject,
|
||||
IdempotencyKey: input.IdempotencyKey,
|
||||
DeclaredMediaType: input.DeclaredType,
|
||||
Content: input.Content,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return UploadedAsset{}, mapUsecaseError(err)
|
||||
}
|
||||
return UploadedAsset{
|
||||
ID: result.Asset.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (adapter *UsecaseAdapter) CreateTask(
|
||||
ctx context.Context,
|
||||
input CreateTaskInput,
|
||||
) (Task, error) {
|
||||
maxInt := int64(^uint(0) >> 1)
|
||||
if input.Quantity > maxInt {
|
||||
return Task{}, ErrValidation
|
||||
}
|
||||
var budget *string
|
||||
if input.MaxBudget != "" {
|
||||
value := input.MaxBudget
|
||||
budget = &value
|
||||
}
|
||||
result, err := adapter.tasks.Create(ctx, usecase.CreateTaskCommand{
|
||||
CreatorSubject: localAdminSubject,
|
||||
IdempotencyKey: input.IdempotencyKey,
|
||||
Title: input.Title,
|
||||
Description: input.Description,
|
||||
SKU: input.SKU,
|
||||
ImageAssetID: input.ImageAssetID,
|
||||
Quantity: int(input.Quantity),
|
||||
MaxBudget: budget,
|
||||
})
|
||||
if err != nil {
|
||||
return Task{}, mapUsecaseError(err)
|
||||
}
|
||||
return taskFromPurchase(result.Task), nil
|
||||
}
|
||||
|
||||
func (adapter *UsecaseAdapter) CancelPending(
|
||||
ctx context.Context,
|
||||
input CancelPendingInput,
|
||||
) (Task, error) {
|
||||
task, err := adapter.tasks.Cancel(ctx, usecase.CancelTaskCommand{
|
||||
CreatorSubject: localAdminSubject,
|
||||
TaskID: input.TaskID,
|
||||
Reason: "管理员取消",
|
||||
})
|
||||
if err != nil {
|
||||
return Task{}, mapUsecaseError(err)
|
||||
}
|
||||
return taskFromPurchase(task), nil
|
||||
}
|
||||
|
||||
func taskSummaryFrom(task domain.PurchaseTask) TaskSummary {
|
||||
return TaskSummary{
|
||||
ID: task.ID,
|
||||
Title: task.Title,
|
||||
SKU: task.SKU,
|
||||
Status: string(task.Status),
|
||||
UpdatedAt: task.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func taskFromPurchase(task domain.PurchaseTask) Task {
|
||||
budget := domain.FormatOptionalCNY(task.MaxBudgetCents)
|
||||
result := Task{
|
||||
ID: task.ID,
|
||||
Title: task.Title,
|
||||
SKU: task.SKU,
|
||||
Description: task.Description,
|
||||
Quantity: int64(task.Quantity),
|
||||
Status: string(task.Status),
|
||||
ReferenceAssetID: task.ImageAssetID,
|
||||
CreatedAt: task.CreatedAt,
|
||||
UpdatedAt: task.UpdatedAt,
|
||||
}
|
||||
if budget != nil {
|
||||
result.MaxBudget = *budget
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func taskFromDetail(detail domain.TaskDetail) Task {
|
||||
task := taskFromPurchase(detail.Task)
|
||||
task.ReferenceAssetID = detail.Asset.ID
|
||||
return task
|
||||
}
|
||||
|
||||
func mapUsecaseError(err error) error {
|
||||
var typed *usecase.Error
|
||||
if !errors.As(err, &typed) {
|
||||
return err
|
||||
}
|
||||
var public error
|
||||
switch typed.Kind {
|
||||
case usecase.ErrorKindInvalid:
|
||||
if typed.Code == "TASK_CANCEL_INVALID" {
|
||||
public = ErrNotFound
|
||||
} else if typed.Code == "ASSET_TOO_LARGE" ||
|
||||
typed.Code == "ASSET_MEDIA_TYPE_UNSUPPORTED" ||
|
||||
typed.Code == "ASSET_IMAGE_INVALID" ||
|
||||
typed.Code == "ASSET_FILE_REQUIRED" {
|
||||
public = ErrInvalidFile
|
||||
} else {
|
||||
public = ErrValidation
|
||||
}
|
||||
case usecase.ErrorKindNotFound:
|
||||
public = ErrNotFound
|
||||
case usecase.ErrorKindConflict:
|
||||
public = ErrConflict
|
||||
case usecase.ErrorKindUnavailable:
|
||||
public = ErrUnavailable
|
||||
default:
|
||||
return err
|
||||
}
|
||||
return &adapterError{
|
||||
public: public,
|
||||
cause: err,
|
||||
}
|
||||
}
|
||||
|
||||
type adapterError struct {
|
||||
public error
|
||||
cause error
|
||||
}
|
||||
|
||||
func (err *adapterError) Error() string {
|
||||
return err.public.Error()
|
||||
}
|
||||
|
||||
func (err *adapterError) Unwrap() []error {
|
||||
return []error{err.public, err.cause}
|
||||
}
|
||||
|
||||
var _ Service = (*UsecaseAdapter)(nil)
|
||||
Reference in New Issue
Block a user