initial commit
This commit is contained in:
commit
d40b69f1f9
58 changed files with 7919 additions and 0 deletions
37
backend/routes/categories.go
Normal file
37
backend/routes/categories.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.red-panda.pet/pandaware/house/backend/db"
|
||||
"git.red-panda.pet/pandaware/house/backend/middleware"
|
||||
"git.red-panda.pet/pandaware/house/backend/router"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routes["GET"]["/category"] = new(CategoriesGET)
|
||||
}
|
||||
|
||||
type categoriesResponse struct {
|
||||
Categories []db.Category `json:"categories"`
|
||||
}
|
||||
|
||||
type CategoriesGET struct{}
|
||||
|
||||
// Authorize implements router.AuthorizedRoute.
|
||||
func (c *CategoriesGET) Authorize(ctx *router.Context) error {
|
||||
return middleware.Authenticated(ctx)
|
||||
}
|
||||
|
||||
// Handle implements router.Route.
|
||||
func (c *CategoriesGET) Handle(ctx *router.Context) error {
|
||||
categories, err := ctx.Query.ListCategories(ctx)
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
return ctx.JSON(200, categoriesResponse{
|
||||
Categories: categories,
|
||||
})
|
||||
}
|
||||
|
||||
var _ router.AuthorizedRoute = new(CategoriesGET)
|
||||
76
backend/routes/category-create.go
Normal file
76
backend/routes/category-create.go
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.red-panda.pet/pandaware/house/backend/db"
|
||||
"git.red-panda.pet/pandaware/house/backend/dbutil"
|
||||
"git.red-panda.pet/pandaware/house/backend/middleware"
|
||||
"git.red-panda.pet/pandaware/house/backend/router"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routes["POST"]["/category"] = new(CategoryCreate)
|
||||
}
|
||||
|
||||
type categoryCreateBody struct {
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
}
|
||||
|
||||
type categoryCreateResponse struct {
|
||||
Category db.Category `json:"category"`
|
||||
}
|
||||
|
||||
type CategoryCreate struct{}
|
||||
|
||||
// ValidateBody implements router.ValidatedBodyRoute.
|
||||
func (c *CategoryCreate) ValidateBody(ctx *router.Context) error {
|
||||
return middleware.ParseJSONBodyWithValidator(
|
||||
ctx, c, func(value categoryCreateBody) error {
|
||||
if len(value.Name) < 5 {
|
||||
return ctx.Error(nil, 400, "Name too short")
|
||||
}
|
||||
|
||||
if value.Description != nil {
|
||||
description := *value.Description
|
||||
|
||||
if len(description) > 100 {
|
||||
return ctx.Error(nil, 400, "Description too long")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Authorize implements router.AuthorizedRoute.
|
||||
func (c *CategoryCreate) Authorize(ctx *router.Context) error {
|
||||
return middleware.Authenticated(ctx)
|
||||
}
|
||||
|
||||
// Handle implements router.Route.
|
||||
func (c *CategoryCreate) Handle(ctx *router.Context) error {
|
||||
user := middleware.User(c, ctx)
|
||||
body := middleware.JSONBody[categoryCreateBody](c, ctx)
|
||||
|
||||
category, err := ctx.Query.CreateCategory(ctx, db.CreateCategoryParams{
|
||||
Name: body.Name,
|
||||
Description: dbutil.SerializeMaybeString(body.Description),
|
||||
CreatedBy: user.ID,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return ctx.JSON(200, categoryCreateResponse{
|
||||
Category: category,
|
||||
})
|
||||
}
|
||||
|
||||
var _ router.AuthorizedRoute = new(CategoryCreate)
|
||||
var _ router.ValidatedBodyRoute = new(CategoryCreate)
|
||||
86
backend/routes/login.go
Normal file
86
backend/routes/login.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.red-panda.pet/pandaware/house/backend/db"
|
||||
"git.red-panda.pet/pandaware/house/backend/middleware"
|
||||
"git.red-panda.pet/pandaware/house/backend/router"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routes["POST"]["/login"] = new(Login)
|
||||
}
|
||||
|
||||
type loginBody struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type loginResponse struct {
|
||||
User db.GetUserRow `json:"user"`
|
||||
}
|
||||
|
||||
type Login struct{}
|
||||
|
||||
// ValidateBody implements router.ValidatedBodyRoute.
|
||||
func (l *Login) ValidateBody(ctx *router.Context) error {
|
||||
return middleware.ParseJSONBody[loginBody](ctx)
|
||||
}
|
||||
|
||||
// Handle implements router.Route.
|
||||
func (l *Login) Handle(ctx *router.Context) error {
|
||||
body := middleware.JSONBody[loginBody](l, ctx)
|
||||
|
||||
user, err := ctx.Query.GetUserPassword(ctx, body.Username)
|
||||
if err != nil {
|
||||
return ctx.Error(err, http.StatusBadRequest, "Invalid credentials")
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword(user.Password, []byte(body.Password))
|
||||
if err != nil {
|
||||
return ctx.Error(err, http.StatusBadRequest, "Invalid credentials")
|
||||
}
|
||||
|
||||
tokenBs := make([]byte, 32)
|
||||
_, err = rand.Read(tokenBs)
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
encodedToken := base64.StdEncoding.EncodeToString(tokenBs)
|
||||
hashedToken := sha256.Sum256(tokenBs)
|
||||
|
||||
_, err = ctx.Query.CreateSession(ctx, db.CreateSessionParams{
|
||||
Key: hashedToken[:],
|
||||
UserID: user.ID,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
loggedInUser, err := ctx.Query.GetUser(ctx, user.ID)
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
ctx.SetCookie(&http.Cookie{
|
||||
Name: "session",
|
||||
Value: encodedToken,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
})
|
||||
|
||||
return ctx.JSON(200, loginResponse{
|
||||
User: loggedInUser,
|
||||
})
|
||||
}
|
||||
|
||||
var _ router.ValidatedBodyRoute = new(Login)
|
||||
22
backend/routes/routes.go
Normal file
22
backend/routes/routes.go
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"git.red-panda.pet/pandaware/house/backend/router"
|
||||
)
|
||||
|
||||
var routes = map[string]map[string]router.Route{
|
||||
"GET": map[string]router.Route{},
|
||||
"POST": map[string]router.Route{},
|
||||
"PATCH": map[string]router.Route{},
|
||||
"DELETE": map[string]router.Route{},
|
||||
"PUT": map[string]router.Route{},
|
||||
"OPTIONS": map[string]router.Route{},
|
||||
}
|
||||
|
||||
func Register(r router.IRouter) {
|
||||
for method, patterns := range routes {
|
||||
for pattern, route := range patterns {
|
||||
r.Register(method, pattern, route)
|
||||
}
|
||||
}
|
||||
}
|
||||
89
backend/routes/task-add-categories.go
Normal file
89
backend/routes/task-add-categories.go
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
// "net/http"
|
||||
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"git.red-panda.pet/pandaware/house/backend/db"
|
||||
"git.red-panda.pet/pandaware/house/backend/middleware"
|
||||
"git.red-panda.pet/pandaware/house/backend/router"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routes["POST"]["/task/{id}/categories"] = new(TaskAddCategories)
|
||||
}
|
||||
|
||||
type taskAddCategoriesBody struct {
|
||||
TaskID int64 `json:"task_id"`
|
||||
CategoryIDs []int64 `json:"category_ids"`
|
||||
}
|
||||
|
||||
type taskAddCategoriesResponse struct {
|
||||
TaskCategories []db.TaskCategory `json:"task_categories"`
|
||||
}
|
||||
|
||||
type TaskAddCategories struct{}
|
||||
|
||||
// ValidateBody implements router.ValidatedBodyRoute.
|
||||
func (t *TaskAddCategories) ValidateBody(ctx *router.Context) error {
|
||||
return middleware.ParseJSONBody[taskAddCategoriesBody](ctx)
|
||||
}
|
||||
|
||||
// ValidateParams implements router.ValidatedParamRoute.
|
||||
func (t *TaskAddCategories) ValidateParams(ctx *router.Context) error {
|
||||
return middleware.ValidateUUIDParam(ctx, "id")
|
||||
}
|
||||
|
||||
// Authorize implements router.AuthorizedRoute.
|
||||
func (t *TaskAddCategories) Authorize(ctx *router.Context) error {
|
||||
return middleware.Authenticated(ctx)
|
||||
}
|
||||
|
||||
// Handle implements router.Route.
|
||||
func (t *TaskAddCategories) Handle(ctx *router.Context) error {
|
||||
user := middleware.User(t, ctx)
|
||||
body := middleware.JSONBody[taskAddCategoriesBody](t, ctx)
|
||||
|
||||
tx, err := ctx.DB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
query := ctx.Query.WithTx(tx)
|
||||
|
||||
task, err := query.TaskByID(ctx, body.TaskID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return ctx.GenericError(err, http.StatusNotFound)
|
||||
}
|
||||
|
||||
if user.ID != task.CreatedBy && user.ID != task.AssignedTo {
|
||||
return ctx.GenericError(err, http.StatusForbidden)
|
||||
}
|
||||
|
||||
taskCategories := make([]db.TaskCategory, len(body.CategoryIDs))
|
||||
|
||||
for _, categoryID := range body.CategoryIDs {
|
||||
tc, err := query.AddTaskCategory(ctx, db.AddTaskCategoryParams{
|
||||
TaskID: task.ID,
|
||||
CategoryID: categoryID,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return ctx.Error(err, http.StatusNotFound, fmt.Sprintf("Category ID %d not found", categoryID))
|
||||
}
|
||||
|
||||
taskCategories = append(taskCategories, tc)
|
||||
}
|
||||
|
||||
return ctx.JSON(200, taskAddCategoriesResponse{
|
||||
TaskCategories: taskCategories,
|
||||
})
|
||||
}
|
||||
|
||||
var _ router.AuthorizedRoute = new(TaskAddCategories)
|
||||
var _ router.ValidatedBodyRoute = new(TaskAddCategories)
|
||||
var _ router.ValidatedParamRoute = new(TaskAddCategories)
|
||||
57
backend/routes/task-complete.go
Normal file
57
backend/routes/task-complete.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.red-panda.pet/pandaware/house/backend/db"
|
||||
"git.red-panda.pet/pandaware/house/backend/middleware"
|
||||
"git.red-panda.pet/pandaware/house/backend/router"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routes["POST"]["/task/{id}/complete"] = new(TaskComplete)
|
||||
}
|
||||
|
||||
type taskCompleteResponse struct {
|
||||
Task db.Task `json:"task"`
|
||||
}
|
||||
|
||||
type TaskComplete struct{}
|
||||
|
||||
// ValidateParams implements router.ValidatedParamRoute.
|
||||
func (t *TaskComplete) ValidateParams(ctx *router.Context) error {
|
||||
return middleware.ValidateIDParam(ctx, "id")
|
||||
}
|
||||
|
||||
// Authorize implements router.AuthorizedRoute.
|
||||
func (t *TaskComplete) Authorize(ctx *router.Context) error {
|
||||
return middleware.Authenticated(ctx)
|
||||
}
|
||||
|
||||
// Handle implements router.Route.
|
||||
func (t *TaskComplete) Handle(ctx *router.Context) error {
|
||||
id := middleware.ParamID(t, ctx)
|
||||
|
||||
tx, err := ctx.DB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
query := ctx.Query.WithTx(tx)
|
||||
|
||||
task, err := query.CompleteTask(ctx, id)
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
return ctx.JSON(200, taskCompleteResponse{
|
||||
Task: task,
|
||||
})
|
||||
}
|
||||
|
||||
var _ router.AuthorizedRoute = new(TaskComplete)
|
||||
var _ router.ValidatedParamRoute = new(TaskComplete)
|
||||
93
backend/routes/task-create.go
Normal file
93
backend/routes/task-create.go
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.red-panda.pet/pandaware/house/backend/db"
|
||||
"git.red-panda.pet/pandaware/house/backend/dbutil"
|
||||
"git.red-panda.pet/pandaware/house/backend/middleware"
|
||||
"git.red-panda.pet/pandaware/house/backend/router"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routes["POST"]["/task"] = new(TaskCreate)
|
||||
}
|
||||
|
||||
type taskCreateBody struct {
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Deadline *int64 `json:"deadline,omitempty"`
|
||||
}
|
||||
|
||||
type taskCreateResponse struct {
|
||||
Task db.Task `json:"task"`
|
||||
}
|
||||
|
||||
type TaskCreate struct{}
|
||||
|
||||
// Authorize implements router.AuthorizedRoute.
|
||||
func (t *TaskCreate) Authorize(ctx *router.Context) error {
|
||||
return middleware.Authenticated(ctx)
|
||||
}
|
||||
|
||||
// ValidateBody implements router.ValidatedBodyRoute.
|
||||
func (t *TaskCreate) ValidateBody(ctx *router.Context) error {
|
||||
return middleware.ParseJSONBodyWithValidator[taskCreateBody](
|
||||
ctx, t, func(value taskCreateBody) error {
|
||||
if len(value.Name) > 64 {
|
||||
return ctx.Error(nil, 400, "name too long")
|
||||
}
|
||||
|
||||
if len(value.Name) < 3 {
|
||||
return ctx.Error(nil, 400, "name too short")
|
||||
}
|
||||
|
||||
if value.Description != nil {
|
||||
description := *value.Description
|
||||
|
||||
if len(description) > 200 {
|
||||
return ctx.Error(nil, 400, "description too long")
|
||||
}
|
||||
}
|
||||
|
||||
if value.Deadline != nil {
|
||||
deadline := *value.Deadline
|
||||
|
||||
if deadline < 0 {
|
||||
return ctx.Error(nil, 400, "deadline cannot be below 0")
|
||||
}
|
||||
|
||||
if deadline < time.Now().Unix() {
|
||||
return ctx.Error(nil, 400, "deadline cannot be in the past")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Handle implements router.Route.
|
||||
func (t *TaskCreate) Handle(ctx *router.Context) error {
|
||||
user := middleware.User(t, ctx)
|
||||
body := middleware.JSONBody[taskCreateBody](t, ctx)
|
||||
|
||||
task, err := ctx.Query.CreateTask(ctx, db.CreateTaskParams{
|
||||
Name: body.Name,
|
||||
Description: dbutil.SerializeMaybeString(body.Description),
|
||||
CreatedBy: user.ID,
|
||||
AssignedTo: user.ID,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
Deadline: dbutil.SerializeMaybeInt(body.Deadline),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return ctx.JSON(200, task)
|
||||
}
|
||||
|
||||
var _ router.AuthorizedRoute = new(TaskCreate)
|
||||
var _ router.ValidatedBodyRoute = new(TaskCreate)
|
||||
57
backend/routes/task-get.go
Normal file
57
backend/routes/task-get.go
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.red-panda.pet/pandaware/house/backend/db"
|
||||
"git.red-panda.pet/pandaware/house/backend/middleware"
|
||||
"git.red-panda.pet/pandaware/house/backend/router"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routes["GET"]["/task"] = new(TasksGET)
|
||||
}
|
||||
|
||||
type tasksResponse struct {
|
||||
Tasks []db.Task `json:"tasks"`
|
||||
Categories []db.Category `json:"categories"`
|
||||
}
|
||||
|
||||
type TasksGET struct{}
|
||||
|
||||
// Authorize implements router.AuthorizedRoute.
|
||||
func (t *TasksGET) Authorize(ctx *router.Context) error {
|
||||
return middleware.Authenticated(ctx)
|
||||
}
|
||||
|
||||
// Handle implements router.Route.
|
||||
func (t *TasksGET) Handle(ctx *router.Context) error {
|
||||
tx, err := ctx.DB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
query := ctx.Query.WithTx(tx)
|
||||
|
||||
tasks, err := query.AllTasks(ctx)
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
taskIDs := make([]int64, len(tasks))
|
||||
for i, t := range tasks {
|
||||
taskIDs[i] = t.ID
|
||||
}
|
||||
|
||||
categories, err := query.CategoriesOf(ctx, taskIDs)
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return ctx.JSON(200, tasksResponse{
|
||||
Tasks: tasks,
|
||||
Categories: categories,
|
||||
})
|
||||
}
|
||||
|
||||
var _ router.AuthorizedRoute = new(TasksGET)
|
||||
47
backend/routes/user-get.go
Normal file
47
backend/routes/user-get.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.red-panda.pet/pandaware/house/backend/middleware"
|
||||
"git.red-panda.pet/pandaware/house/backend/router"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routes["GET"]["/user/{id}"] = new(UserGET)
|
||||
}
|
||||
|
||||
type UserGET struct{}
|
||||
|
||||
// ValidateParams implements router.ValidatedParamRoute.
|
||||
func (u *UserGET) ValidateParams(ctx *router.Context) error {
|
||||
if ctx.Parameter("id") == "me" {
|
||||
return nil
|
||||
}
|
||||
return middleware.ValidateUUIDParam(ctx, "id")
|
||||
}
|
||||
|
||||
// Authorize implements router.AuthorizedRoute.
|
||||
func (u *UserGET) Authorize(ctx *router.Context) error {
|
||||
return middleware.Authenticated(ctx)
|
||||
}
|
||||
|
||||
// Handle implements router.Route.
|
||||
func (u *UserGET) Handle(ctx *router.Context) error {
|
||||
currentUser := middleware.User(u, ctx)
|
||||
id := ctx.Parameter("id")
|
||||
|
||||
if id == "me" {
|
||||
id = currentUser.ID
|
||||
}
|
||||
|
||||
user, err := ctx.Query.GetUser(ctx, id)
|
||||
if err != nil {
|
||||
return ctx.Error(err, http.StatusNotFound, "user not found")
|
||||
}
|
||||
|
||||
return ctx.JSON(200, user)
|
||||
}
|
||||
|
||||
var _ router.AuthorizedRoute = new(UserGET)
|
||||
var _ router.ValidatedParamRoute = new(UserGET)
|
||||
91
backend/routes/user-tasks.go
Normal file
91
backend/routes/user-tasks.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"git.red-panda.pet/pandaware/house/backend/db"
|
||||
"git.red-panda.pet/pandaware/house/backend/middleware"
|
||||
"git.red-panda.pet/pandaware/house/backend/router"
|
||||
)
|
||||
|
||||
func init() {
|
||||
routes["GET"]["/user/{id}/tasks"] = new(UserTasks)
|
||||
}
|
||||
|
||||
type userTasksResponse struct {
|
||||
Tasks []db.Task `json:"tasks"`
|
||||
TaskCategories []db.TaskCategory `json:"task_categories"`
|
||||
Categories []db.Category `json:"categories"`
|
||||
}
|
||||
|
||||
type UserTasks struct{}
|
||||
|
||||
// Authorize implements router.AuthorizedRoute.
|
||||
func (u *UserTasks) Authorize(ctx *router.Context) error {
|
||||
return middleware.Authenticated(ctx)
|
||||
}
|
||||
|
||||
// ValidateParams implements router.ValidatedParamRoute.
|
||||
func (u *UserTasks) ValidateParams(ctx *router.Context) error {
|
||||
if ctx.Parameter("id") == "me" {
|
||||
return nil
|
||||
}
|
||||
return middleware.ValidateUUIDParam(ctx, "id")
|
||||
}
|
||||
|
||||
// Handle implements router.Route.
|
||||
func (u *UserTasks) Handle(ctx *router.Context) error {
|
||||
user := middleware.User(u, ctx)
|
||||
id := ctx.Parameter("id")
|
||||
|
||||
if id == "me" {
|
||||
id = user.ID
|
||||
}
|
||||
|
||||
tx, err := ctx.DB.BeginTx(ctx, &sql.TxOptions{
|
||||
ReadOnly: true,
|
||||
})
|
||||
|
||||
query := ctx.Query.WithTx(tx)
|
||||
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
tasks, err := query.TasksForUser(ctx, id)
|
||||
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
taskIDs := make([]int64, len(tasks))
|
||||
for i, task := range tasks {
|
||||
taskIDs[i] = task.ID
|
||||
}
|
||||
|
||||
taskCategories, err := query.TaskCategoriesOf(ctx, taskIDs)
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
categories, err := query.CategoriesOf(ctx, taskIDs)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return ctx.GenericError(err, http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
return ctx.JSON(200, userTasksResponse{
|
||||
Tasks: append([]db.Task{}, tasks...),
|
||||
TaskCategories: append([]db.TaskCategory{}, taskCategories...),
|
||||
Categories: append([]db.Category{}, categories...),
|
||||
})
|
||||
}
|
||||
|
||||
var _ router.AuthorizedRoute = new(UserTasks)
|
||||
var _ router.ValidatedParamRoute = new(UserTasks)
|
||||
Loading…
Add table
Add a link
Reference in a new issue