Add monitoring service and database schema
- Implement monitoring service with heartbeat, lease tasks, resume tasks, and task result handling. - Create monitoring time utilities for business date calculations. - Add unit tests for date window resolution and business day handling. - Define database schema for monitoring-related tables including quotas, daily reports, and task management. - Establish migration scripts for creating and dropping monitoring tables.
This commit is contained in:
@@ -15,12 +15,13 @@ import (
|
||||
)
|
||||
|
||||
type BrandService struct {
|
||||
pool *pgxpool.Pool
|
||||
auditLogs *auditlog.AsyncWriter
|
||||
pool *pgxpool.Pool
|
||||
monitoringPool *pgxpool.Pool
|
||||
auditLogs *auditlog.AsyncWriter
|
||||
}
|
||||
|
||||
func NewBrandService(pool *pgxpool.Pool, auditLogs *auditlog.AsyncWriter) *BrandService {
|
||||
return &BrandService{pool: pool, auditLogs: auditLogs}
|
||||
func NewBrandService(pool, monitoringPool *pgxpool.Pool, auditLogs *auditlog.AsyncWriter) *BrandService {
|
||||
return &BrandService{pool: pool, monitoringPool: monitoringPool, auditLogs: auditLogs}
|
||||
}
|
||||
|
||||
// --- Brand CRUD ---
|
||||
@@ -174,6 +175,10 @@ func (s *BrandService) Delete(ctx context.Context, id int64) error {
|
||||
_, _ = tx.Exec(ctx, `UPDATE brand_questions SET deleted_at = NOW(), updated_at = NOW() WHERE brand_id = $1 AND tenant_id = $2 AND deleted_at IS NULL`, id, actor.TenantID)
|
||||
_, _ = tx.Exec(ctx, `UPDATE competitors SET deleted_at = NOW(), updated_at = NOW() WHERE brand_id = $1 AND tenant_id = $2 AND deleted_at IS NULL`, id, actor.TenantID)
|
||||
|
||||
if err := s.cleanupMonitoringAfterBrandDelete(ctx, actor.TenantID, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
afterJSON, _ := json.Marshal(map[string]interface{}{"id": id, "action": "cascade_soft_delete"})
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return err
|
||||
@@ -265,13 +270,65 @@ func (s *BrandService) UpdateKeyword(ctx context.Context, brandID, keywordID int
|
||||
|
||||
func (s *BrandService) DeleteKeyword(ctx context.Context, brandID, keywordID int64) error {
|
||||
actor := auth.MustActor(ctx)
|
||||
tag, err := s.pool.Exec(ctx, `
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("begin tx: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = tx.Rollback(ctx)
|
||||
}()
|
||||
|
||||
questionRows, err := tx.Query(ctx, `
|
||||
SELECT id
|
||||
FROM brand_questions
|
||||
WHERE brand_id = $1
|
||||
AND keyword_id = $2
|
||||
AND tenant_id = $3
|
||||
AND deleted_at IS NULL
|
||||
`, brandID, keywordID, actor.TenantID)
|
||||
if err != nil {
|
||||
return response.ErrInternal(50010, "query_failed", "failed to load keyword questions")
|
||||
}
|
||||
questionIDs := make([]int64, 0)
|
||||
for questionRows.Next() {
|
||||
var questionID int64
|
||||
if scanErr := questionRows.Scan(&questionID); scanErr != nil {
|
||||
return response.ErrInternal(50010, "scan_failed", scanErr.Error())
|
||||
}
|
||||
questionIDs = append(questionIDs, questionID)
|
||||
}
|
||||
if err := questionRows.Err(); err != nil {
|
||||
return response.ErrInternal(50010, "scan_failed", err.Error())
|
||||
}
|
||||
questionRows.Close()
|
||||
|
||||
tag, err := tx.Exec(ctx, `
|
||||
UPDATE brand_keywords SET deleted_at = NOW(), updated_at = NOW()
|
||||
WHERE id = $1 AND brand_id = $2 AND tenant_id = $3 AND deleted_at IS NULL
|
||||
`, keywordID, brandID, actor.TenantID)
|
||||
if err != nil || tag.RowsAffected() == 0 {
|
||||
return response.ErrNotFound(40421, "keyword_not_found", "keyword not found")
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(ctx, `
|
||||
UPDATE brand_questions
|
||||
SET deleted_at = NOW(), updated_at = NOW()
|
||||
WHERE brand_id = $1
|
||||
AND keyword_id = $2
|
||||
AND tenant_id = $3
|
||||
AND deleted_at IS NULL
|
||||
`, brandID, keywordID, actor.TenantID); err != nil {
|
||||
return response.ErrInternal(50010, "update_failed", "failed to soft delete questions under keyword")
|
||||
}
|
||||
|
||||
if err := s.cleanupMonitoringAfterKeywordDelete(ctx, actor.TenantID, brandID, keywordID, questionIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -378,13 +435,30 @@ func (s *BrandService) UpdateQuestion(ctx context.Context, brandID, questionID i
|
||||
|
||||
func (s *BrandService) DeleteQuestion(ctx context.Context, brandID, questionID int64) error {
|
||||
actor := auth.MustActor(ctx)
|
||||
tag, err := s.pool.Exec(ctx, `
|
||||
tx, err := s.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("begin tx: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = tx.Rollback(ctx)
|
||||
}()
|
||||
|
||||
tag, err := tx.Exec(ctx, `
|
||||
UPDATE brand_questions SET deleted_at = NOW(), updated_at = NOW()
|
||||
WHERE id = $1 AND brand_id = $2 AND tenant_id = $3 AND deleted_at IS NULL
|
||||
`, questionID, brandID, actor.TenantID)
|
||||
if err != nil || tag.RowsAffected() == 0 {
|
||||
return response.ErrNotFound(40422, "question_not_found", "question not found")
|
||||
}
|
||||
|
||||
if err := s.cleanupMonitoringAfterQuestionDelete(ctx, actor.TenantID, brandID, questionID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user