first commit
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
package mysql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"secunda-test/internal/domain"
|
||||
"secunda-test/internal/service"
|
||||
)
|
||||
|
||||
type TaskRepository struct{ db *sql.DB }
|
||||
|
||||
func NewTaskRepository(db *sql.DB) *TaskRepository { return &TaskRepository{db: db} }
|
||||
|
||||
func (r *TaskRepository) Create(ctx context.Context, task domain.Task) (domain.Task, error) {
|
||||
res, err := r.db.ExecContext(ctx, `
|
||||
INSERT INTO tasks(team_id,title,description,status,assignee_id,created_by)
|
||||
VALUES(?,?,?,?,?,?)`,
|
||||
task.TeamID, task.Title, task.Description, valueOrDefault(string(task.Status), string(domain.StatusTodo)), task.AssigneeID, task.CreatedBy)
|
||||
if err != nil {
|
||||
return domain.Task{}, err
|
||||
}
|
||||
id, _ := res.LastInsertId()
|
||||
return r.Get(ctx, id)
|
||||
}
|
||||
|
||||
func (r *TaskRepository) Get(ctx context.Context, id int64) (domain.Task, error) {
|
||||
var t domain.Task
|
||||
err := r.db.QueryRowContext(ctx, `
|
||||
SELECT id,team_id,title,description,status,assignee_id,created_by,created_at,updated_at
|
||||
FROM tasks WHERE id=?`, id).
|
||||
Scan(&t.ID, &t.TeamID, &t.Title, &t.Description, &t.Status, &t.AssigneeID, &t.CreatedBy, &t.CreatedAt, &t.UpdatedAt)
|
||||
if err == sql.ErrNoRows {
|
||||
return domain.Task{}, service.ErrNotFound
|
||||
}
|
||||
return t, err
|
||||
}
|
||||
|
||||
func (r *TaskRepository) Update(ctx context.Context, task domain.Task, changedBy int64) (domain.Task, error) {
|
||||
old, err := r.Get(ctx, task.ID)
|
||||
if err != nil {
|
||||
return domain.Task{}, err
|
||||
}
|
||||
tx, err := r.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return domain.Task{}, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
UPDATE tasks SET title=?,description=?,status=?,assignee_id=? WHERE id=?`,
|
||||
task.Title, task.Description, task.Status, task.AssigneeID, task.ID)
|
||||
if err != nil {
|
||||
return domain.Task{}, err
|
||||
}
|
||||
addHistory := func(field, oldValue, newValue string) error {
|
||||
if oldValue == newValue {
|
||||
return nil
|
||||
}
|
||||
_, err := tx.ExecContext(ctx, `INSERT INTO task_history(task_id,changed_by,field_name,old_value,new_value) VALUES(?,?,?,?,?)`,
|
||||
task.ID, changedBy, field, oldValue, newValue)
|
||||
return err
|
||||
}
|
||||
if err := addHistory("title", old.Title, task.Title); err != nil {
|
||||
return domain.Task{}, err
|
||||
}
|
||||
if err := addHistory("description", old.Description, task.Description); err != nil {
|
||||
return domain.Task{}, err
|
||||
}
|
||||
if err := addHistory("status", string(old.Status), string(task.Status)); err != nil {
|
||||
return domain.Task{}, err
|
||||
}
|
||||
if err := addHistory("assignee_id", ptrString(old.AssigneeID), ptrString(task.AssigneeID)); err != nil {
|
||||
return domain.Task{}, err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return domain.Task{}, err
|
||||
}
|
||||
return r.Get(ctx, task.ID)
|
||||
}
|
||||
|
||||
func (r *TaskRepository) List(ctx context.Context, filter domain.TaskFilter) ([]domain.Task, error) {
|
||||
args := []any{filter.TeamID}
|
||||
where := []string{"team_id=?"}
|
||||
if filter.Status != "" {
|
||||
where = append(where, "status=?")
|
||||
args = append(args, filter.Status)
|
||||
}
|
||||
if filter.AssigneeID != nil {
|
||||
where = append(where, "assignee_id=?")
|
||||
args = append(args, *filter.AssigneeID)
|
||||
}
|
||||
limit, offset := filter.PageSize, (filter.Page-1)*filter.PageSize
|
||||
args = append(args, limit, offset)
|
||||
query := fmt.Sprintf(`
|
||||
SELECT id,team_id,title,description,status,assignee_id,created_by,created_at,updated_at
|
||||
FROM tasks WHERE %s ORDER BY created_at DESC LIMIT ? OFFSET ?`, strings.Join(where, " AND "))
|
||||
rows, err := r.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []domain.Task
|
||||
for rows.Next() {
|
||||
var t domain.Task
|
||||
if err := rows.Scan(&t.ID, &t.TeamID, &t.Title, &t.Description, &t.Status, &t.AssigneeID, &t.CreatedBy, &t.CreatedAt, &t.UpdatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, t)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (r *TaskRepository) History(ctx context.Context, taskID int64) ([]domain.TaskHistory, error) {
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT id,task_id,changed_by,field_name,old_value,new_value,created_at
|
||||
FROM task_history WHERE task_id=? ORDER BY created_at DESC`, taskID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []domain.TaskHistory
|
||||
for rows.Next() {
|
||||
var h domain.TaskHistory
|
||||
if err := rows.Scan(&h.ID, &h.TaskID, &h.ChangedBy, &h.FieldName, &h.OldValue, &h.NewValue, &h.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, h)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (r *TaskRepository) TeamSummary(ctx context.Context) ([]domain.TeamSummary, error) {
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT t.id, t.name, COUNT(DISTINCT tm.user_id) AS members_count,
|
||||
COUNT(DISTINCT CASE WHEN ta.status='done' AND ta.updated_at >= NOW() - INTERVAL 7 DAY THEN ta.id END) AS done_last_7_days
|
||||
FROM teams t
|
||||
LEFT JOIN team_members tm ON tm.team_id=t.id
|
||||
LEFT JOIN tasks ta ON ta.team_id=t.id
|
||||
GROUP BY t.id, t.name
|
||||
ORDER BY t.name`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []domain.TeamSummary
|
||||
for rows.Next() {
|
||||
var s domain.TeamSummary
|
||||
if err := rows.Scan(&s.TeamID, &s.TeamName, &s.MembersCount, &s.DoneLast7Days); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, s)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (r *TaskRepository) TopCreators(ctx context.Context) ([]domain.TopCreator, error) {
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
WITH ranked AS (
|
||||
SELECT t.team_id, te.name AS team_name, t.created_by AS user_id, u.name AS user_name,
|
||||
COUNT(*) AS tasks_created,
|
||||
DENSE_RANK() OVER (PARTITION BY t.team_id ORDER BY COUNT(*) DESC) AS user_rank
|
||||
FROM tasks t
|
||||
JOIN teams te ON te.id=t.team_id
|
||||
JOIN users u ON u.id=t.created_by
|
||||
WHERE t.created_at >= NOW() - INTERVAL 1 MONTH
|
||||
GROUP BY t.team_id, te.name, t.created_by, u.name
|
||||
)
|
||||
SELECT team_id,team_name,user_id,user_name,tasks_created,user_rank
|
||||
FROM ranked WHERE user_rank <= 3 ORDER BY team_id,user_rank`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []domain.TopCreator
|
||||
for rows.Next() {
|
||||
var c domain.TopCreator
|
||||
if err := rows.Scan(&c.TeamID, &c.TeamName, &c.UserID, &c.UserName, &c.TasksCreated, &c.Rank); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, c)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (r *TaskRepository) InvalidAssignees(ctx context.Context) ([]domain.Task, error) {
|
||||
rows, err := r.db.QueryContext(ctx, `
|
||||
SELECT ta.id,ta.team_id,ta.title,ta.description,ta.status,ta.assignee_id,ta.created_by,ta.created_at,ta.updated_at
|
||||
FROM tasks ta
|
||||
LEFT JOIN team_members tm ON tm.team_id=ta.team_id AND tm.user_id=ta.assignee_id
|
||||
WHERE ta.assignee_id IS NOT NULL AND tm.user_id IS NULL`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var out []domain.Task
|
||||
for rows.Next() {
|
||||
var t domain.Task
|
||||
if err := rows.Scan(&t.ID, &t.TeamID, &t.Title, &t.Description, &t.Status, &t.AssigneeID, &t.CreatedBy, &t.CreatedAt, &t.UpdatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, t)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func valueOrDefault(v, fallback string) string {
|
||||
if v == "" {
|
||||
return fallback
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func ptrString(v *int64) string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprint(*v)
|
||||
}
|
||||
Reference in New Issue
Block a user