feat(core): bind request services and implement registered tasks

Wire plugin services through Bind/InjectFrom and AppContext so HTTP and
workers resolve dependencies after Apply. Register TaskHandler objects
with persisted results, and implement send_email_code, mail:send,
cleanup_inactive_users, and dispatch_bot_msg.
This commit is contained in:
ryan
2026-09-02 16:59:00 +08:00
parent 30bbe965bf
commit 4f50f6a8f9
49 changed files with 1406 additions and 499 deletions
@@ -8,6 +8,7 @@ import (
"Wavelet/core"
"Wavelet/core/contracts"
"context"
"encoding/json"
"errors"
"fmt"
"sync"
@@ -144,14 +145,7 @@ func (p *Plugin) Apply(ctx *core.Context) error {
ResetAsynqClient()
p.mu.Unlock()
// 0. Bind DBService
if db, err := core.Inject[contracts.DBService](ctx); err == nil && db != nil {
setDBService(db)
} else {
core.When[contracts.DBService](ctx, func(db contracts.DBService) {
setDBService(db)
})
}
core.Bind[contracts.DBService](ctx, setDBService)
ctx.OnDispose(func() error {
setDBService(nil)
return nil
@@ -191,12 +185,15 @@ func (p *Plugin) Start(_ context.Context) error {
mux := asynq.NewServeMux()
if p.coreCtx != nil && p.coreCtx.Tasks() != nil {
appCtx := p.coreCtx.Root()
for _, td := range p.coreCtx.Tasks().Tasks() {
handler, err := toAsynqHandler(td.Pattern, td.Handler)
if err != nil {
return fmt.Errorf("driver_asynq_worker: invalid handler for task pattern %q: %w", td.Pattern, err)
}
mux.Handle(td.Pattern, handler)
mux.Handle(td.Pattern, asynq.HandlerFunc(func(c context.Context, t *asynq.Task) error {
return handler.ProcessTask(core.WithAppContext(c, appCtx), t)
}))
}
}
@@ -285,6 +282,14 @@ func toAsynqHandler(pattern string, h any) (asynq.Handler, error) {
RegisterHandler(pattern, th)
return asynq.HandlerFunc(ProcessTask), nil
}
if th, ok := h.(contracts.TaskHandler); ok {
RegisterHandler(pattern, contractTaskAdapter{inner: th})
return asynq.HandlerFunc(ProcessTask), nil
}
if fn, ok := h.(func(context.Context, []byte) (*contracts.TaskResultDTO, error)); ok {
RegisterHandler(pattern, contractFuncAdapter{fn: fn})
return asynq.HandlerFunc(ProcessTask), nil
}
inner, err := toRawAsynqHandler(h)
if err != nil {
@@ -294,6 +299,58 @@ func toAsynqHandler(pattern string, h any) (asynq.Handler, error) {
return asynq.HandlerFunc(ProcessTask), nil
}
type contractTaskAdapter struct {
inner contracts.TaskHandler
}
func (a contractTaskAdapter) Execute(ctx context.Context, payload []byte) (*TaskResult, error) {
res, err := a.inner.Execute(ctx, payload)
if err != nil {
return nil, err
}
return dtoToTaskResult(res), nil
}
func (a contractTaskAdapter) ValidatePayload(payload []byte) ([]byte, error) {
if v, ok := a.inner.(PayloadValidator); ok {
return v.ValidatePayload(payload)
}
return payload, nil
}
type contractFuncAdapter struct {
fn func(context.Context, []byte) (*contracts.TaskResultDTO, error)
}
func (a contractFuncAdapter) Execute(ctx context.Context, payload []byte) (*TaskResult, error) {
res, err := a.fn(ctx, payload)
if err != nil {
return nil, err
}
return dtoToTaskResult(res), nil
}
func dtoToTaskResult(res *contracts.TaskResultDTO) *TaskResult {
if res == nil {
return &TaskResult{Message: "ok"}
}
out := &TaskResult{Message: res.Message}
if res.Detail == nil {
return out
}
if s, ok := res.Detail.(string); ok {
out.Detail = s
return out
}
b, err := json.Marshal(res.Detail)
if err != nil {
out.Detail = fmt.Sprint(res.Detail)
return out
}
out.Detail = string(b)
return out
}
func toRawAsynqHandler(h any) (asynq.Handler, error) {
switch fn := h.(type) {
case asynq.HandlerFunc: