Fix BASE_URL config loading, add tasks/projects; robust .env path resolution

- Config: try ENV_FILE, .env, ../.env for loading; trim trailing slash from BaseURL
- Log BASE_URL at server startup for verification
- .env.example: document BASE_URL
- Tasks, projects, tags, migrations and related API/handlers

Made-with: Cursor
This commit is contained in:
Michilis
2026-03-09 18:57:51 +00:00
parent 75105b8b46
commit bd24545b7b
61 changed files with 6595 additions and 90 deletions

View File

@@ -199,7 +199,7 @@ SELECT c.id, c.owner_id, c.name, c.color, c.is_public, c.count_for_availability,
FROM calendars c
JOIN calendar_members cm ON cm.calendar_id = c.id
WHERE cm.user_id = $1 AND c.deleted_at IS NULL
ORDER BY c.created_at ASC
ORDER BY c.sort_order ASC, c.created_at ASC
`
type ListCalendarsByUserRow struct {

View File

@@ -130,6 +130,25 @@ type EventReminder struct {
MinutesBefore int32 `json:"minutes_before"`
}
type Project struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
Name string `json:"name"`
Color string `json:"color"`
IsShared bool `json:"is_shared"`
Deadline pgtype.Timestamptz `json:"deadline"`
SortOrder int32 `json:"sort_order"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
DeletedAt pgtype.Timestamptz `json:"deleted_at"`
}
type ProjectMember struct {
ProjectID pgtype.UUID `json:"project_id"`
UserID pgtype.UUID `json:"user_id"`
Role string `json:"role"`
}
type RefreshToken struct {
ID pgtype.UUID `json:"id"`
UserID pgtype.UUID `json:"user_id"`
@@ -139,6 +158,60 @@ type RefreshToken struct {
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type Tag struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
Name string `json:"name"`
Color string `json:"color"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type Task struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Status string `json:"status"`
Priority string `json:"priority"`
DueDate pgtype.Timestamptz `json:"due_date"`
CompletedAt pgtype.Timestamptz `json:"completed_at"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
DeletedAt pgtype.Timestamptz `json:"deleted_at"`
ProjectID pgtype.UUID `json:"project_id"`
ParentID pgtype.UUID `json:"parent_id"`
SortOrder int32 `json:"sort_order"`
RecurrenceRule pgtype.Text `json:"recurrence_rule"`
}
type TaskDependency struct {
TaskID pgtype.UUID `json:"task_id"`
BlocksTaskID pgtype.UUID `json:"blocks_task_id"`
}
type TaskReminder struct {
ID pgtype.UUID `json:"id"`
TaskID pgtype.UUID `json:"task_id"`
Type string `json:"type"`
Config []byte `json:"config"`
ScheduledAt pgtype.Timestamptz `json:"scheduled_at"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type TaskTag struct {
TaskID pgtype.UUID `json:"task_id"`
TagID pgtype.UUID `json:"tag_id"`
}
type TaskWebhook struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
Url string `json:"url"`
Events []byte `json:"events"`
Secret pgtype.Text `json:"secret"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
}
type User struct {
ID pgtype.UUID `json:"id"`
Email string `json:"email"`

View File

@@ -0,0 +1,309 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: projects.sql
package repository
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const addProjectMember = `-- name: AddProjectMember :exec
INSERT INTO project_members (project_id, user_id, role)
VALUES ($1, $2, $3)
ON CONFLICT (project_id, user_id) DO UPDATE SET role = EXCLUDED.role
`
type AddProjectMemberParams struct {
ProjectID pgtype.UUID `json:"project_id"`
UserID pgtype.UUID `json:"user_id"`
Role string `json:"role"`
}
func (q *Queries) AddProjectMember(ctx context.Context, arg AddProjectMemberParams) error {
_, err := q.db.Exec(ctx, addProjectMember, arg.ProjectID, arg.UserID, arg.Role)
return err
}
const createProject = `-- name: CreateProject :one
INSERT INTO projects (id, owner_id, name, color, is_shared, deadline, sort_order)
VALUES ($1, $2, $3, $4, COALESCE($5, false), $6, COALESCE($7, 0))
RETURNING id, owner_id, name, color, is_shared, deadline, sort_order, created_at, updated_at, deleted_at
`
type CreateProjectParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
Name string `json:"name"`
Color string `json:"color"`
Column5 interface{} `json:"column_5"`
Deadline pgtype.Timestamptz `json:"deadline"`
Column7 interface{} `json:"column_7"`
}
func (q *Queries) CreateProject(ctx context.Context, arg CreateProjectParams) (Project, error) {
row := q.db.QueryRow(ctx, createProject,
arg.ID,
arg.OwnerID,
arg.Name,
arg.Color,
arg.Column5,
arg.Deadline,
arg.Column7,
)
var i Project
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Name,
&i.Color,
&i.IsShared,
&i.Deadline,
&i.SortOrder,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
)
return i, err
}
const getProjectByID = `-- name: GetProjectByID :one
SELECT id, owner_id, name, color, is_shared, deadline, sort_order, created_at, updated_at, deleted_at FROM projects
WHERE id = $1 AND owner_id = $2 AND deleted_at IS NULL
`
type GetProjectByIDParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) GetProjectByID(ctx context.Context, arg GetProjectByIDParams) (Project, error) {
row := q.db.QueryRow(ctx, getProjectByID, arg.ID, arg.OwnerID)
var i Project
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Name,
&i.Color,
&i.IsShared,
&i.Deadline,
&i.SortOrder,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
)
return i, err
}
const getProjectMember = `-- name: GetProjectMember :one
SELECT project_id, user_id, role FROM project_members
WHERE project_id = $1 AND user_id = $2
`
type GetProjectMemberParams struct {
ProjectID pgtype.UUID `json:"project_id"`
UserID pgtype.UUID `json:"user_id"`
}
func (q *Queries) GetProjectMember(ctx context.Context, arg GetProjectMemberParams) (ProjectMember, error) {
row := q.db.QueryRow(ctx, getProjectMember, arg.ProjectID, arg.UserID)
var i ProjectMember
err := row.Scan(&i.ProjectID, &i.UserID, &i.Role)
return i, err
}
const listProjectMembers = `-- name: ListProjectMembers :many
SELECT pm.project_id, pm.user_id, pm.role, u.email
FROM project_members pm
JOIN users u ON u.id = pm.user_id
WHERE pm.project_id = $1
`
type ListProjectMembersRow struct {
ProjectID pgtype.UUID `json:"project_id"`
UserID pgtype.UUID `json:"user_id"`
Role string `json:"role"`
Email string `json:"email"`
}
func (q *Queries) ListProjectMembers(ctx context.Context, projectID pgtype.UUID) ([]ListProjectMembersRow, error) {
rows, err := q.db.Query(ctx, listProjectMembers, projectID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListProjectMembersRow{}
for rows.Next() {
var i ListProjectMembersRow
if err := rows.Scan(
&i.ProjectID,
&i.UserID,
&i.Role,
&i.Email,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listProjects = `-- name: ListProjects :many
SELECT id, owner_id, name, color, is_shared, deadline, sort_order, created_at, updated_at, deleted_at FROM projects
WHERE owner_id = $1 AND deleted_at IS NULL
ORDER BY sort_order ASC, name ASC
`
func (q *Queries) ListProjects(ctx context.Context, ownerID pgtype.UUID) ([]Project, error) {
rows, err := q.db.Query(ctx, listProjects, ownerID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Project{}
for rows.Next() {
var i Project
if err := rows.Scan(
&i.ID,
&i.OwnerID,
&i.Name,
&i.Color,
&i.IsShared,
&i.Deadline,
&i.SortOrder,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listProjectsForUser = `-- name: ListProjectsForUser :many
SELECT p.id, p.owner_id, p.name, p.color, p.is_shared, p.deadline, p.sort_order, p.created_at, p.updated_at, p.deleted_at FROM projects p
LEFT JOIN project_members pm ON pm.project_id = p.id AND pm.user_id = $1
WHERE (p.owner_id = $1 OR pm.user_id = $1)
AND p.deleted_at IS NULL
ORDER BY p.sort_order ASC, p.name ASC
`
func (q *Queries) ListProjectsForUser(ctx context.Context, userID pgtype.UUID) ([]Project, error) {
rows, err := q.db.Query(ctx, listProjectsForUser, userID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Project{}
for rows.Next() {
var i Project
if err := rows.Scan(
&i.ID,
&i.OwnerID,
&i.Name,
&i.Color,
&i.IsShared,
&i.Deadline,
&i.SortOrder,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const removeProjectMember = `-- name: RemoveProjectMember :exec
DELETE FROM project_members
WHERE project_id = $1 AND user_id = $2
`
type RemoveProjectMemberParams struct {
ProjectID pgtype.UUID `json:"project_id"`
UserID pgtype.UUID `json:"user_id"`
}
func (q *Queries) RemoveProjectMember(ctx context.Context, arg RemoveProjectMemberParams) error {
_, err := q.db.Exec(ctx, removeProjectMember, arg.ProjectID, arg.UserID)
return err
}
const softDeleteProject = `-- name: SoftDeleteProject :exec
UPDATE projects SET deleted_at = now(), updated_at = now()
WHERE id = $1 AND owner_id = $2 AND deleted_at IS NULL
`
type SoftDeleteProjectParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) SoftDeleteProject(ctx context.Context, arg SoftDeleteProjectParams) error {
_, err := q.db.Exec(ctx, softDeleteProject, arg.ID, arg.OwnerID)
return err
}
const updateProject = `-- name: UpdateProject :one
UPDATE projects
SET name = COALESCE($1, name),
color = COALESCE($2, color),
is_shared = COALESCE($3, is_shared),
deadline = $4,
sort_order = COALESCE($5, sort_order),
updated_at = now()
WHERE id = $6 AND owner_id = $7 AND deleted_at IS NULL
RETURNING id, owner_id, name, color, is_shared, deadline, sort_order, created_at, updated_at, deleted_at
`
type UpdateProjectParams struct {
Name pgtype.Text `json:"name"`
Color pgtype.Text `json:"color"`
IsShared pgtype.Bool `json:"is_shared"`
Deadline pgtype.Timestamptz `json:"deadline"`
SortOrder pgtype.Int4 `json:"sort_order"`
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) UpdateProject(ctx context.Context, arg UpdateProjectParams) (Project, error) {
row := q.db.QueryRow(ctx, updateProject,
arg.Name,
arg.Color,
arg.IsShared,
arg.Deadline,
arg.SortOrder,
arg.ID,
arg.OwnerID,
)
var i Project
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Name,
&i.Color,
&i.IsShared,
&i.Deadline,
&i.SortOrder,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
)
return i, err
}

View File

@@ -105,17 +105,6 @@ func (q *Queries) ListSubscriptionsByCalendar(ctx context.Context, calendarID pg
return items, nil
}
const updateSubscriptionLastSynced = `-- name: UpdateSubscriptionLastSynced :exec
UPDATE calendar_subscriptions
SET last_synced_at = now()
WHERE id = $1
`
func (q *Queries) UpdateSubscriptionLastSynced(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, updateSubscriptionLastSynced, id)
return err
}
const listSubscriptionsDueForSync = `-- name: ListSubscriptionsDueForSync :many
SELECT s.id, s.calendar_id, s.source_url, c.owner_id
FROM calendar_subscriptions s
@@ -139,10 +128,15 @@ func (q *Queries) ListSubscriptionsDueForSync(ctx context.Context) ([]ListSubscr
return nil, err
}
defer rows.Close()
var items []ListSubscriptionsDueForSyncRow
items := []ListSubscriptionsDueForSyncRow{}
for rows.Next() {
var i ListSubscriptionsDueForSyncRow
if err := rows.Scan(&i.ID, &i.CalendarID, &i.SourceUrl, &i.OwnerID); err != nil {
if err := rows.Scan(
&i.ID,
&i.CalendarID,
&i.SourceUrl,
&i.OwnerID,
); err != nil {
return nil, err
}
items = append(items, i)
@@ -152,3 +146,14 @@ func (q *Queries) ListSubscriptionsDueForSync(ctx context.Context) ([]ListSubscr
}
return items, nil
}
const updateSubscriptionLastSynced = `-- name: UpdateSubscriptionLastSynced :exec
UPDATE calendar_subscriptions
SET last_synced_at = now()
WHERE id = $1
`
func (q *Queries) UpdateSubscriptionLastSynced(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, updateSubscriptionLastSynced, id)
return err
}

View File

@@ -0,0 +1,232 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: tags.sql
package repository
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const attachTagToTask = `-- name: AttachTagToTask :exec
INSERT INTO task_tags (task_id, tag_id)
VALUES ($1, $2)
ON CONFLICT (task_id, tag_id) DO NOTHING
`
type AttachTagToTaskParams struct {
TaskID pgtype.UUID `json:"task_id"`
TagID pgtype.UUID `json:"tag_id"`
}
func (q *Queries) AttachTagToTask(ctx context.Context, arg AttachTagToTaskParams) error {
_, err := q.db.Exec(ctx, attachTagToTask, arg.TaskID, arg.TagID)
return err
}
const createTag = `-- name: CreateTag :one
INSERT INTO tags (id, owner_id, name, color)
VALUES ($1, $2, $3, COALESCE($4, '#6B7280'))
RETURNING id, owner_id, name, color, created_at
`
type CreateTagParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
Name string `json:"name"`
Column4 interface{} `json:"column_4"`
}
func (q *Queries) CreateTag(ctx context.Context, arg CreateTagParams) (Tag, error) {
row := q.db.QueryRow(ctx, createTag,
arg.ID,
arg.OwnerID,
arg.Name,
arg.Column4,
)
var i Tag
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Name,
&i.Color,
&i.CreatedAt,
)
return i, err
}
const deleteTag = `-- name: DeleteTag :exec
DELETE FROM tags WHERE id = $1 AND owner_id = $2
`
type DeleteTagParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) DeleteTag(ctx context.Context, arg DeleteTagParams) error {
_, err := q.db.Exec(ctx, deleteTag, arg.ID, arg.OwnerID)
return err
}
const detachTagFromTask = `-- name: DetachTagFromTask :exec
DELETE FROM task_tags
WHERE task_id = $1 AND tag_id = $2
`
type DetachTagFromTaskParams struct {
TaskID pgtype.UUID `json:"task_id"`
TagID pgtype.UUID `json:"tag_id"`
}
func (q *Queries) DetachTagFromTask(ctx context.Context, arg DetachTagFromTaskParams) error {
_, err := q.db.Exec(ctx, detachTagFromTask, arg.TaskID, arg.TagID)
return err
}
const getTagByID = `-- name: GetTagByID :one
SELECT id, owner_id, name, color, created_at FROM tags
WHERE id = $1 AND owner_id = $2
`
type GetTagByIDParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) GetTagByID(ctx context.Context, arg GetTagByIDParams) (Tag, error) {
row := q.db.QueryRow(ctx, getTagByID, arg.ID, arg.OwnerID)
var i Tag
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Name,
&i.Color,
&i.CreatedAt,
)
return i, err
}
const listTagsByOwner = `-- name: ListTagsByOwner :many
SELECT id, owner_id, name, color, created_at FROM tags
WHERE owner_id = $1
ORDER BY name ASC
`
func (q *Queries) ListTagsByOwner(ctx context.Context, ownerID pgtype.UUID) ([]Tag, error) {
rows, err := q.db.Query(ctx, listTagsByOwner, ownerID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Tag{}
for rows.Next() {
var i Tag
if err := rows.Scan(
&i.ID,
&i.OwnerID,
&i.Name,
&i.Color,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listTagsByTask = `-- name: ListTagsByTask :many
SELECT t.id, t.owner_id, t.name, t.color, t.created_at FROM tags t
JOIN task_tags tt ON tt.tag_id = t.id
WHERE tt.task_id = $1
`
func (q *Queries) ListTagsByTask(ctx context.Context, taskID pgtype.UUID) ([]Tag, error) {
rows, err := q.db.Query(ctx, listTagsByTask, taskID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Tag{}
for rows.Next() {
var i Tag
if err := rows.Scan(
&i.ID,
&i.OwnerID,
&i.Name,
&i.Color,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listTaskIDsByTag = `-- name: ListTaskIDsByTag :many
SELECT task_id FROM task_tags WHERE tag_id = $1
`
func (q *Queries) ListTaskIDsByTag(ctx context.Context, tagID pgtype.UUID) ([]pgtype.UUID, error) {
rows, err := q.db.Query(ctx, listTaskIDsByTag, tagID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []pgtype.UUID{}
for rows.Next() {
var task_id pgtype.UUID
if err := rows.Scan(&task_id); err != nil {
return nil, err
}
items = append(items, task_id)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateTag = `-- name: UpdateTag :one
UPDATE tags
SET name = COALESCE($1, name),
color = COALESCE($2, color)
WHERE id = $3 AND owner_id = $4
RETURNING id, owner_id, name, color, created_at
`
type UpdateTagParams struct {
Name pgtype.Text `json:"name"`
Color pgtype.Text `json:"color"`
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) UpdateTag(ctx context.Context, arg UpdateTagParams) (Tag, error) {
row := q.db.QueryRow(ctx, updateTag,
arg.Name,
arg.Color,
arg.ID,
arg.OwnerID,
)
var i Tag
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Name,
&i.Color,
&i.CreatedAt,
)
return i, err
}

View File

@@ -0,0 +1,147 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: task_dependencies.sql
package repository
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const addTaskDependency = `-- name: AddTaskDependency :exec
INSERT INTO task_dependencies (task_id, blocks_task_id)
VALUES ($1, $2)
ON CONFLICT (task_id, blocks_task_id) DO NOTHING
`
type AddTaskDependencyParams struct {
TaskID pgtype.UUID `json:"task_id"`
BlocksTaskID pgtype.UUID `json:"blocks_task_id"`
}
func (q *Queries) AddTaskDependency(ctx context.Context, arg AddTaskDependencyParams) error {
_, err := q.db.Exec(ctx, addTaskDependency, arg.TaskID, arg.BlocksTaskID)
return err
}
const checkDirectCircularDependency = `-- name: CheckDirectCircularDependency :one
SELECT EXISTS(
SELECT 1 FROM task_dependencies
WHERE task_id = $2 AND blocks_task_id = $1
) AS has_circle
`
type CheckDirectCircularDependencyParams struct {
BlocksTaskID pgtype.UUID `json:"blocks_task_id"`
TaskID pgtype.UUID `json:"task_id"`
}
// Direct cycle: blocks_task_id is blocked by task_id (would create A->B and B->A)
func (q *Queries) CheckDirectCircularDependency(ctx context.Context, arg CheckDirectCircularDependencyParams) (bool, error) {
row := q.db.QueryRow(ctx, checkDirectCircularDependency, arg.BlocksTaskID, arg.TaskID)
var has_circle bool
err := row.Scan(&has_circle)
return has_circle, err
}
const listTaskBlockers = `-- name: ListTaskBlockers :many
SELECT t.id, t.owner_id, t.title, t.description, t.status, t.priority, t.due_date, t.completed_at, t.created_at, t.updated_at, t.deleted_at, t.project_id, t.parent_id, t.sort_order, t.recurrence_rule FROM tasks t
JOIN task_dependencies td ON td.blocks_task_id = t.id
WHERE td.task_id = $1 AND t.deleted_at IS NULL
`
func (q *Queries) ListTaskBlockers(ctx context.Context, taskID pgtype.UUID) ([]Task, error) {
rows, err := q.db.Query(ctx, listTaskBlockers, taskID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Task{}
for rows.Next() {
var i Task
if err := rows.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listTasksBlockedBy = `-- name: ListTasksBlockedBy :many
SELECT t.id, t.owner_id, t.title, t.description, t.status, t.priority, t.due_date, t.completed_at, t.created_at, t.updated_at, t.deleted_at, t.project_id, t.parent_id, t.sort_order, t.recurrence_rule FROM tasks t
JOIN task_dependencies td ON td.task_id = t.id
WHERE td.blocks_task_id = $1 AND t.deleted_at IS NULL
`
func (q *Queries) ListTasksBlockedBy(ctx context.Context, blocksTaskID pgtype.UUID) ([]Task, error) {
rows, err := q.db.Query(ctx, listTasksBlockedBy, blocksTaskID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Task{}
for rows.Next() {
var i Task
if err := rows.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const removeTaskDependency = `-- name: RemoveTaskDependency :exec
DELETE FROM task_dependencies
WHERE task_id = $1 AND blocks_task_id = $2
`
type RemoveTaskDependencyParams struct {
TaskID pgtype.UUID `json:"task_id"`
BlocksTaskID pgtype.UUID `json:"blocks_task_id"`
}
func (q *Queries) RemoveTaskDependency(ctx context.Context, arg RemoveTaskDependencyParams) error {
_, err := q.db.Exec(ctx, removeTaskDependency, arg.TaskID, arg.BlocksTaskID)
return err
}

View File

@@ -0,0 +1,164 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: task_reminders.sql
package repository
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createTaskReminder = `-- name: CreateTaskReminder :one
INSERT INTO task_reminders (id, task_id, type, config, scheduled_at)
VALUES ($1, $2, $3, COALESCE($4, '{}'), $5)
RETURNING id, task_id, type, config, scheduled_at, created_at
`
type CreateTaskReminderParams struct {
ID pgtype.UUID `json:"id"`
TaskID pgtype.UUID `json:"task_id"`
Type string `json:"type"`
Column4 interface{} `json:"column_4"`
ScheduledAt pgtype.Timestamptz `json:"scheduled_at"`
}
func (q *Queries) CreateTaskReminder(ctx context.Context, arg CreateTaskReminderParams) (TaskReminder, error) {
row := q.db.QueryRow(ctx, createTaskReminder,
arg.ID,
arg.TaskID,
arg.Type,
arg.Column4,
arg.ScheduledAt,
)
var i TaskReminder
err := row.Scan(
&i.ID,
&i.TaskID,
&i.Type,
&i.Config,
&i.ScheduledAt,
&i.CreatedAt,
)
return i, err
}
const deleteTaskReminder = `-- name: DeleteTaskReminder :exec
DELETE FROM task_reminders WHERE id = $1
`
func (q *Queries) DeleteTaskReminder(ctx context.Context, id pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteTaskReminder, id)
return err
}
const deleteTaskRemindersByTask = `-- name: DeleteTaskRemindersByTask :exec
DELETE FROM task_reminders WHERE task_id = $1
`
func (q *Queries) DeleteTaskRemindersByTask(ctx context.Context, taskID pgtype.UUID) error {
_, err := q.db.Exec(ctx, deleteTaskRemindersByTask, taskID)
return err
}
const getTaskReminderByID = `-- name: GetTaskReminderByID :one
SELECT id, task_id, type, config, scheduled_at, created_at FROM task_reminders
WHERE id = $1
`
func (q *Queries) GetTaskReminderByID(ctx context.Context, id pgtype.UUID) (TaskReminder, error) {
row := q.db.QueryRow(ctx, getTaskReminderByID, id)
var i TaskReminder
err := row.Scan(
&i.ID,
&i.TaskID,
&i.Type,
&i.Config,
&i.ScheduledAt,
&i.CreatedAt,
)
return i, err
}
const listTaskReminders = `-- name: ListTaskReminders :many
SELECT id, task_id, type, config, scheduled_at, created_at FROM task_reminders
WHERE task_id = $1
ORDER BY scheduled_at ASC
`
func (q *Queries) ListTaskReminders(ctx context.Context, taskID pgtype.UUID) ([]TaskReminder, error) {
rows, err := q.db.Query(ctx, listTaskReminders, taskID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []TaskReminder{}
for rows.Next() {
var i TaskReminder
if err := rows.Scan(
&i.ID,
&i.TaskID,
&i.Type,
&i.Config,
&i.ScheduledAt,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listTaskRemindersDueBefore = `-- name: ListTaskRemindersDueBefore :many
SELECT tr.id, tr.task_id, tr.type, tr.config, tr.scheduled_at, tr.created_at, t.owner_id, t.title
FROM task_reminders tr
JOIN tasks t ON t.id = tr.task_id
WHERE tr.scheduled_at <= $1
AND t.deleted_at IS NULL
`
type ListTaskRemindersDueBeforeRow struct {
ID pgtype.UUID `json:"id"`
TaskID pgtype.UUID `json:"task_id"`
Type string `json:"type"`
Config []byte `json:"config"`
ScheduledAt pgtype.Timestamptz `json:"scheduled_at"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
OwnerID pgtype.UUID `json:"owner_id"`
Title string `json:"title"`
}
func (q *Queries) ListTaskRemindersDueBefore(ctx context.Context, scheduledAt pgtype.Timestamptz) ([]ListTaskRemindersDueBeforeRow, error) {
rows, err := q.db.Query(ctx, listTaskRemindersDueBefore, scheduledAt)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListTaskRemindersDueBeforeRow{}
for rows.Next() {
var i ListTaskRemindersDueBeforeRow
if err := rows.Scan(
&i.ID,
&i.TaskID,
&i.Type,
&i.Config,
&i.ScheduledAt,
&i.CreatedAt,
&i.OwnerID,
&i.Title,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@@ -0,0 +1,117 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: task_webhooks.sql
package repository
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const createTaskWebhook = `-- name: CreateTaskWebhook :one
INSERT INTO task_webhooks (id, owner_id, url, events, secret)
VALUES ($1, $2, $3, COALESCE($4, '[]'), $5)
RETURNING id, owner_id, url, events, secret, created_at
`
type CreateTaskWebhookParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
Url string `json:"url"`
Column4 interface{} `json:"column_4"`
Secret pgtype.Text `json:"secret"`
}
func (q *Queries) CreateTaskWebhook(ctx context.Context, arg CreateTaskWebhookParams) (TaskWebhook, error) {
row := q.db.QueryRow(ctx, createTaskWebhook,
arg.ID,
arg.OwnerID,
arg.Url,
arg.Column4,
arg.Secret,
)
var i TaskWebhook
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Url,
&i.Events,
&i.Secret,
&i.CreatedAt,
)
return i, err
}
const deleteTaskWebhook = `-- name: DeleteTaskWebhook :exec
DELETE FROM task_webhooks WHERE id = $1 AND owner_id = $2
`
type DeleteTaskWebhookParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) DeleteTaskWebhook(ctx context.Context, arg DeleteTaskWebhookParams) error {
_, err := q.db.Exec(ctx, deleteTaskWebhook, arg.ID, arg.OwnerID)
return err
}
const getTaskWebhookByID = `-- name: GetTaskWebhookByID :one
SELECT id, owner_id, url, events, secret, created_at FROM task_webhooks
WHERE id = $1 AND owner_id = $2
`
type GetTaskWebhookByIDParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) GetTaskWebhookByID(ctx context.Context, arg GetTaskWebhookByIDParams) (TaskWebhook, error) {
row := q.db.QueryRow(ctx, getTaskWebhookByID, arg.ID, arg.OwnerID)
var i TaskWebhook
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Url,
&i.Events,
&i.Secret,
&i.CreatedAt,
)
return i, err
}
const listTaskWebhooksByOwner = `-- name: ListTaskWebhooksByOwner :many
SELECT id, owner_id, url, events, secret, created_at FROM task_webhooks
WHERE owner_id = $1
ORDER BY created_at DESC
`
func (q *Queries) ListTaskWebhooksByOwner(ctx context.Context, ownerID pgtype.UUID) ([]TaskWebhook, error) {
rows, err := q.db.Query(ctx, listTaskWebhooksByOwner, ownerID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []TaskWebhook{}
for rows.Next() {
var i TaskWebhook
if err := rows.Scan(
&i.ID,
&i.OwnerID,
&i.Url,
&i.Events,
&i.Secret,
&i.CreatedAt,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}

View File

@@ -0,0 +1,687 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.30.0
// source: tasks.sql
package repository
import (
"context"
"github.com/jackc/pgx/v5/pgtype"
)
const countSubtasksByStatus = `-- name: CountSubtasksByStatus :one
SELECT
COUNT(*) FILTER (WHERE status = 'done') AS done_count,
COUNT(*) AS total_count
FROM tasks
WHERE parent_id = $1 AND deleted_at IS NULL
`
type CountSubtasksByStatusRow struct {
DoneCount int64 `json:"done_count"`
TotalCount int64 `json:"total_count"`
}
func (q *Queries) CountSubtasksByStatus(ctx context.Context, parentID pgtype.UUID) (CountSubtasksByStatusRow, error) {
row := q.db.QueryRow(ctx, countSubtasksByStatus, parentID)
var i CountSubtasksByStatusRow
err := row.Scan(&i.DoneCount, &i.TotalCount)
return i, err
}
const createTask = `-- name: CreateTask :one
INSERT INTO tasks (id, owner_id, title, description, status, priority, due_date, project_id, parent_id, sort_order, recurrence_rule)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING id, owner_id, title, description, status, priority, due_date, completed_at, created_at, updated_at, deleted_at, project_id, parent_id, sort_order, recurrence_rule
`
type CreateTaskParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
Title string `json:"title"`
Description pgtype.Text `json:"description"`
Status string `json:"status"`
Priority string `json:"priority"`
DueDate pgtype.Timestamptz `json:"due_date"`
ProjectID pgtype.UUID `json:"project_id"`
ParentID pgtype.UUID `json:"parent_id"`
SortOrder int32 `json:"sort_order"`
RecurrenceRule pgtype.Text `json:"recurrence_rule"`
}
func (q *Queries) CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error) {
row := q.db.QueryRow(ctx, createTask,
arg.ID,
arg.OwnerID,
arg.Title,
arg.Description,
arg.Status,
arg.Priority,
arg.DueDate,
arg.ProjectID,
arg.ParentID,
arg.SortOrder,
arg.RecurrenceRule,
)
var i Task
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
)
return i, err
}
const getTaskByID = `-- name: GetTaskByID :one
SELECT id, owner_id, title, description, status, priority, due_date, completed_at, created_at, updated_at, deleted_at, project_id, parent_id, sort_order, recurrence_rule FROM tasks
WHERE id = $1 AND owner_id = $2 AND deleted_at IS NULL
`
type GetTaskByIDParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) GetTaskByID(ctx context.Context, arg GetTaskByIDParams) (Task, error) {
row := q.db.QueryRow(ctx, getTaskByID, arg.ID, arg.OwnerID)
var i Task
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
)
return i, err
}
const getTaskByIDForUpdate = `-- name: GetTaskByIDForUpdate :one
SELECT id, owner_id, title, description, status, priority, due_date, completed_at, created_at, updated_at, deleted_at, project_id, parent_id, sort_order, recurrence_rule FROM tasks
WHERE id = $1 AND owner_id = $2 AND deleted_at IS NULL
FOR UPDATE
`
type GetTaskByIDForUpdateParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) GetTaskByIDForUpdate(ctx context.Context, arg GetTaskByIDForUpdateParams) (Task, error) {
row := q.db.QueryRow(ctx, getTaskByIDForUpdate, arg.ID, arg.OwnerID)
var i Task
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
)
return i, err
}
const hardDeleteTask = `-- name: HardDeleteTask :exec
DELETE FROM tasks WHERE id = $1 AND owner_id = $2
`
type HardDeleteTaskParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) HardDeleteTask(ctx context.Context, arg HardDeleteTaskParams) error {
_, err := q.db.Exec(ctx, hardDeleteTask, arg.ID, arg.OwnerID)
return err
}
const listSubtasks = `-- name: ListSubtasks :many
SELECT id, owner_id, title, description, status, priority, due_date, completed_at, created_at, updated_at, deleted_at, project_id, parent_id, sort_order, recurrence_rule FROM tasks
WHERE parent_id = $1 AND owner_id = $2 AND deleted_at IS NULL
ORDER BY sort_order ASC, created_at ASC
`
type ListSubtasksParams struct {
ParentID pgtype.UUID `json:"parent_id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) ListSubtasks(ctx context.Context, arg ListSubtasksParams) ([]Task, error) {
rows, err := q.db.Query(ctx, listSubtasks, arg.ParentID, arg.OwnerID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Task{}
for rows.Next() {
var i Task
if err := rows.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listTasks = `-- name: ListTasks :many
SELECT t.id, t.owner_id, t.title, t.description, t.status, t.priority, t.due_date, t.completed_at, t.created_at, t.updated_at, t.deleted_at, t.project_id, t.parent_id, t.sort_order, t.recurrence_rule FROM tasks t
WHERE t.owner_id = $1
AND t.deleted_at IS NULL
AND t.parent_id IS NULL
AND ($2::TEXT IS NULL OR t.status = $2::TEXT)
AND ($3::TEXT IS NULL OR t.priority = $3::TEXT)
AND ($4::UUID IS NULL OR t.project_id = $4::UUID)
AND ($5::TIMESTAMPTZ IS NULL OR t.due_date >= $5::TIMESTAMPTZ)
AND ($6::TIMESTAMPTZ IS NULL OR t.due_date <= $6::TIMESTAMPTZ)
AND (
$7::TIMESTAMPTZ IS NULL
OR (t.created_at, t.id) < ($7::TIMESTAMPTZ, $8::UUID)
)
ORDER BY t.created_at DESC, t.id DESC
LIMIT $9
`
type ListTasksParams struct {
OwnerID pgtype.UUID `json:"owner_id"`
Status pgtype.Text `json:"status"`
Priority pgtype.Text `json:"priority"`
ProjectID pgtype.UUID `json:"project_id"`
DueFrom pgtype.Timestamptz `json:"due_from"`
DueTo pgtype.Timestamptz `json:"due_to"`
CursorTime pgtype.Timestamptz `json:"cursor_time"`
CursorID pgtype.UUID `json:"cursor_id"`
Lim int32 `json:"lim"`
}
func (q *Queries) ListTasks(ctx context.Context, arg ListTasksParams) ([]Task, error) {
rows, err := q.db.Query(ctx, listTasks,
arg.OwnerID,
arg.Status,
arg.Priority,
arg.ProjectID,
arg.DueFrom,
arg.DueTo,
arg.CursorTime,
arg.CursorID,
arg.Lim,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Task{}
for rows.Next() {
var i Task
if err := rows.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listTasksByDueDate = `-- name: ListTasksByDueDate :many
SELECT t.id, t.owner_id, t.title, t.description, t.status, t.priority, t.due_date, t.completed_at, t.created_at, t.updated_at, t.deleted_at, t.project_id, t.parent_id, t.sort_order, t.recurrence_rule FROM tasks t
WHERE t.owner_id = $1
AND t.deleted_at IS NULL
AND t.parent_id IS NULL
AND ($2::TEXT IS NULL OR t.status = $2::TEXT)
AND ($3::TEXT IS NULL OR t.priority = $3::TEXT)
AND ($4::UUID IS NULL OR t.project_id = $4::UUID)
AND ($5::TIMESTAMPTZ IS NULL OR t.due_date >= $5::TIMESTAMPTZ)
AND ($6::TIMESTAMPTZ IS NULL OR t.due_date <= $6::TIMESTAMPTZ)
ORDER BY COALESCE(t.due_date, '9999-12-31'::timestamptz) ASC NULLS LAST, t.id ASC
LIMIT $7
`
type ListTasksByDueDateParams struct {
OwnerID pgtype.UUID `json:"owner_id"`
Status pgtype.Text `json:"status"`
Priority pgtype.Text `json:"priority"`
ProjectID pgtype.UUID `json:"project_id"`
DueFrom pgtype.Timestamptz `json:"due_from"`
DueTo pgtype.Timestamptz `json:"due_to"`
Lim int32 `json:"lim"`
}
func (q *Queries) ListTasksByDueDate(ctx context.Context, arg ListTasksByDueDateParams) ([]Task, error) {
rows, err := q.db.Query(ctx, listTasksByDueDate,
arg.OwnerID,
arg.Status,
arg.Priority,
arg.ProjectID,
arg.DueFrom,
arg.DueTo,
arg.Lim,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Task{}
for rows.Next() {
var i Task
if err := rows.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listTasksByPriority = `-- name: ListTasksByPriority :many
SELECT t.id, t.owner_id, t.title, t.description, t.status, t.priority, t.due_date, t.completed_at, t.created_at, t.updated_at, t.deleted_at, t.project_id, t.parent_id, t.sort_order, t.recurrence_rule FROM tasks t
WHERE t.owner_id = $1
AND t.deleted_at IS NULL
AND t.parent_id IS NULL
AND ($2::TEXT IS NULL OR t.status = $2::TEXT)
AND ($3::TEXT IS NULL OR t.priority = $3::TEXT)
AND ($4::UUID IS NULL OR t.project_id = $4::UUID)
AND ($5::TIMESTAMPTZ IS NULL OR t.due_date >= $5::TIMESTAMPTZ)
AND ($6::TIMESTAMPTZ IS NULL OR t.due_date <= $6::TIMESTAMPTZ)
ORDER BY CASE t.priority
WHEN 'critical' THEN 1
WHEN 'high' THEN 2
WHEN 'medium' THEN 3
WHEN 'low' THEN 4
ELSE 5
END ASC, t.created_at DESC, t.id DESC
LIMIT $7
`
type ListTasksByPriorityParams struct {
OwnerID pgtype.UUID `json:"owner_id"`
Status pgtype.Text `json:"status"`
Priority pgtype.Text `json:"priority"`
ProjectID pgtype.UUID `json:"project_id"`
DueFrom pgtype.Timestamptz `json:"due_from"`
DueTo pgtype.Timestamptz `json:"due_to"`
Lim int32 `json:"lim"`
}
func (q *Queries) ListTasksByPriority(ctx context.Context, arg ListTasksByPriorityParams) ([]Task, error) {
rows, err := q.db.Query(ctx, listTasksByPriority,
arg.OwnerID,
arg.Status,
arg.Priority,
arg.ProjectID,
arg.DueFrom,
arg.DueTo,
arg.Lim,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Task{}
for rows.Next() {
var i Task
if err := rows.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listTasksWithRecurrence = `-- name: ListTasksWithRecurrence :many
SELECT id, owner_id, title, description, status, priority, due_date, completed_at, created_at, updated_at, deleted_at, project_id, parent_id, sort_order, recurrence_rule FROM tasks
WHERE owner_id = $1 AND deleted_at IS NULL
AND recurrence_rule IS NOT NULL
AND parent_id IS NULL
`
func (q *Queries) ListTasksWithRecurrence(ctx context.Context, ownerID pgtype.UUID) ([]Task, error) {
rows, err := q.db.Query(ctx, listTasksWithRecurrence, ownerID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Task{}
for rows.Next() {
var i Task
if err := rows.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listTasksWithTag = `-- name: ListTasksWithTag :many
SELECT DISTINCT t.id, t.owner_id, t.title, t.description, t.status, t.priority, t.due_date, t.completed_at, t.created_at, t.updated_at, t.deleted_at, t.project_id, t.parent_id, t.sort_order, t.recurrence_rule FROM tasks t
JOIN task_tags tt ON tt.task_id = t.id
WHERE t.owner_id = $1
AND t.deleted_at IS NULL
AND t.parent_id IS NULL
AND tt.tag_id = ANY($2)
AND ($3::TEXT IS NULL OR t.status = $3::TEXT)
AND ($4::UUID IS NULL OR t.project_id = $4::UUID)
ORDER BY t.created_at DESC, t.id DESC
LIMIT $5
`
type ListTasksWithTagParams struct {
OwnerID pgtype.UUID `json:"owner_id"`
TagIds pgtype.UUID `json:"tag_ids"`
Status pgtype.Text `json:"status"`
ProjectID pgtype.UUID `json:"project_id"`
Lim int32 `json:"lim"`
}
func (q *Queries) ListTasksWithTag(ctx context.Context, arg ListTasksWithTagParams) ([]Task, error) {
rows, err := q.db.Query(ctx, listTasksWithTag,
arg.OwnerID,
arg.TagIds,
arg.Status,
arg.ProjectID,
arg.Lim,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Task{}
for rows.Next() {
var i Task
if err := rows.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const markTaskComplete = `-- name: MarkTaskComplete :one
UPDATE tasks
SET status = 'done', completed_at = now(), updated_at = now()
WHERE id = $1 AND owner_id = $2 AND deleted_at IS NULL
RETURNING id, owner_id, title, description, status, priority, due_date, completed_at, created_at, updated_at, deleted_at, project_id, parent_id, sort_order, recurrence_rule
`
type MarkTaskCompleteParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) MarkTaskComplete(ctx context.Context, arg MarkTaskCompleteParams) (Task, error) {
row := q.db.QueryRow(ctx, markTaskComplete, arg.ID, arg.OwnerID)
var i Task
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
)
return i, err
}
const markTaskUncomplete = `-- name: MarkTaskUncomplete :one
UPDATE tasks
SET status = COALESCE($3, 'todo'), completed_at = NULL, updated_at = now()
WHERE id = $1 AND owner_id = $2 AND deleted_at IS NULL
RETURNING id, owner_id, title, description, status, priority, due_date, completed_at, created_at, updated_at, deleted_at, project_id, parent_id, sort_order, recurrence_rule
`
type MarkTaskUncompleteParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
NewStatus pgtype.Text `json:"new_status"`
}
func (q *Queries) MarkTaskUncomplete(ctx context.Context, arg MarkTaskUncompleteParams) (Task, error) {
row := q.db.QueryRow(ctx, markTaskUncomplete, arg.ID, arg.OwnerID, arg.NewStatus)
var i Task
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
)
return i, err
}
const softDeleteTask = `-- name: SoftDeleteTask :exec
UPDATE tasks SET deleted_at = now(), updated_at = now()
WHERE id = $1 AND owner_id = $2 AND deleted_at IS NULL
`
type SoftDeleteTaskParams struct {
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) SoftDeleteTask(ctx context.Context, arg SoftDeleteTaskParams) error {
_, err := q.db.Exec(ctx, softDeleteTask, arg.ID, arg.OwnerID)
return err
}
const updateTask = `-- name: UpdateTask :one
UPDATE tasks
SET title = COALESCE($1, title),
description = COALESCE($2, description),
status = COALESCE($3, status),
priority = COALESCE($4, priority),
due_date = $5,
project_id = $6,
sort_order = COALESCE($7, sort_order),
recurrence_rule = $8,
updated_at = now()
WHERE id = $9 AND owner_id = $10 AND deleted_at IS NULL
RETURNING id, owner_id, title, description, status, priority, due_date, completed_at, created_at, updated_at, deleted_at, project_id, parent_id, sort_order, recurrence_rule
`
type UpdateTaskParams struct {
Title pgtype.Text `json:"title"`
Description pgtype.Text `json:"description"`
Status pgtype.Text `json:"status"`
Priority pgtype.Text `json:"priority"`
DueDate pgtype.Timestamptz `json:"due_date"`
ProjectID pgtype.UUID `json:"project_id"`
SortOrder pgtype.Int4 `json:"sort_order"`
RecurrenceRule pgtype.Text `json:"recurrence_rule"`
ID pgtype.UUID `json:"id"`
OwnerID pgtype.UUID `json:"owner_id"`
}
func (q *Queries) UpdateTask(ctx context.Context, arg UpdateTaskParams) (Task, error) {
row := q.db.QueryRow(ctx, updateTask,
arg.Title,
arg.Description,
arg.Status,
arg.Priority,
arg.DueDate,
arg.ProjectID,
arg.SortOrder,
arg.RecurrenceRule,
arg.ID,
arg.OwnerID,
)
var i Task
err := row.Scan(
&i.ID,
&i.OwnerID,
&i.Title,
&i.Description,
&i.Status,
&i.Priority,
&i.DueDate,
&i.CompletedAt,
&i.CreatedAt,
&i.UpdatedAt,
&i.DeletedAt,
&i.ProjectID,
&i.ParentID,
&i.SortOrder,
&i.RecurrenceRule,
)
return i, err
}