first commit

This commit is contained in:
2020-12-31 17:49:54 +01:00
commit 2240985aa1
120 changed files with 11574 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/gissleh/stufflog/internal/slerrors"
)
func handler(key string, callback func(c *gin.Context) (interface{}, error)) gin.HandlerFunc {
return func(c *gin.Context) {
res, err := callback(c)
if err != nil {
slerrors.Respond(c, err)
return
}
resJson := make(map[string]interface{}, 1)
resJson[key] = res
c.JSON(200, resJson)
}
}
+141
View File
@@ -0,0 +1,141 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/gissleh/stufflog/database"
"github.com/gissleh/stufflog/internal/auth"
"github.com/gissleh/stufflog/internal/generate"
"github.com/gissleh/stufflog/internal/slerrors"
"github.com/gissleh/stufflog/models"
"github.com/gissleh/stufflog/services"
"time"
)
func Goal(g *gin.RouterGroup, db database.Database) {
l := services.Loader{DB: db}
g.GET("/", handler("goals", func(c *gin.Context) (interface{}, error) {
filter := models.GoalFilter{}
if value := c.Query("minTime"); value != "" {
minTime, err := time.Parse(time.RFC3339Nano, value)
if err != nil {
return nil, slerrors.BadRequest("Invalid minTime")
}
minTime = minTime.UTC()
filter.MinTime = &minTime
} else {
lastMonth := time.Now().Add(-30 * 24 * time.Hour).UTC()
filter.MinTime = &lastMonth
}
if value := c.Query("maxTime"); value != "" {
maxTime, err := time.Parse(time.RFC3339Nano, value)
if err != nil {
return nil, slerrors.BadRequest("Invalid maxTime")
}
maxTime = maxTime.UTC()
filter.MaxTime = &maxTime
}
if value := c.Query("includesTime"); value != "" {
includesTime, err := time.Parse(time.RFC3339Nano, value)
if err != nil {
return nil, slerrors.BadRequest("Invalid includesTime")
}
includesTime = includesTime.UTC()
filter.IncludesTime = &includesTime
}
return l.ListGoals(c.Request.Context(), filter)
}))
g.GET("/:id", handler("goal", func(c *gin.Context) (interface{}, error) {
return l.FindGoal(c.Request.Context(), c.Param("id"))
}))
g.POST("/", handler("goal", func(c *gin.Context) (interface{}, error) {
goal := models.Goal{}
err := c.BindJSON(&goal)
if err != nil {
return nil, slerrors.BadRequest("Invalid JSON")
}
goal.ID = generate.GoalID()
goal.UserID = auth.UserID(c)
if goal.Amount <= 0 {
return nil, slerrors.BadRequest("Goal amount must be more than 0.")
}
if goal.StartTime.IsZero() {
return nil, slerrors.BadRequest("Start time must be set.")
}
if goal.EndTime.IsZero() {
return nil, slerrors.BadRequest("End time must be set.")
}
if !goal.EndTime.After(goal.StartTime) {
return nil, slerrors.BadRequest("Start time must be before end time.")
}
goal.StartTime = goal.StartTime.UTC()
goal.EndTime = goal.EndTime.UTC()
group, err := db.Groups().Find(c.Request.Context(), goal.GroupID)
if err != nil {
return nil, err
}
goal.GroupID = group.ID
err = db.Goals().Insert(c.Request.Context(), goal)
if err != nil {
return nil, err
}
return l.FindGoal(c.Request.Context(), goal.ID)
}))
g.PUT("/:id", handler("goal", func(c *gin.Context) (interface{}, error) {
update := models.GoalUpdate{}
err := c.BindJSON(&update)
if err != nil {
return nil, slerrors.BadRequest("Invalid JSON")
}
goal, err := l.FindGoal(c.Request.Context(), c.Param("id"))
if err != nil {
return nil, err
}
goal.StartTime = goal.StartTime.UTC()
goal.EndTime = goal.EndTime.UTC()
goal.Update(update)
if goal.Amount <= 0 {
return nil, slerrors.BadRequest("Goal amount must be more than 0.")
}
if !goal.EndTime.After(goal.StartTime) {
return nil, slerrors.BadRequest("Start time must be before end time.")
}
err = db.Goals().Update(c.Request.Context(), goal.Goal)
if err != nil {
return nil, err
}
return goal, nil
}))
g.DELETE("/:id", handler("goal", func(c *gin.Context) (interface{}, error) {
goal, err := l.FindGoal(c.Request.Context(), c.Param("id"))
if err != nil {
return nil, err
}
err = db.Goals().Delete(c.Request.Context(), goal.Goal)
if err != nil {
return nil, err
}
return goal, nil
}))
}
+90
View File
@@ -0,0 +1,90 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/gissleh/stufflog/database"
"github.com/gissleh/stufflog/internal/auth"
"github.com/gissleh/stufflog/internal/generate"
"github.com/gissleh/stufflog/internal/slerrors"
"github.com/gissleh/stufflog/models"
"github.com/gissleh/stufflog/services"
)
func Group(g *gin.RouterGroup, db database.Database) {
l := services.Loader{DB: db}
g.GET("/", handler("groups", func(c *gin.Context) (interface{}, error) {
return l.ListGroups(c, models.GroupFilter{})
}))
g.GET("/:id", handler("group", func(c *gin.Context) (interface{}, error) {
return l.FindGroup(c, c.Param("id"))
}))
g.POST("/", handler("group", func(c *gin.Context) (interface{}, error) {
group := models.Group{}
err := c.BindJSON(&group)
if err != nil {
return nil, slerrors.BadRequest("Invalid JSON")
}
group.ID = generate.GroupID()
group.UserID = auth.UserID(c)
err = db.Groups().Insert(c.Request.Context(), group)
if err != nil {
return nil, err
}
return &models.GroupResult{
Group: group,
Items: []*models.Item{},
}, nil
}))
g.PUT("/:id", handler("group", func(c *gin.Context) (interface{}, error) {
update := models.GroupUpdate{}
err := c.BindJSON(&update)
if err != nil {
return nil, slerrors.BadRequest("Invalid JSON")
}
group, err := db.Groups().Find(c.Request.Context(), c.Param("id"))
if err != nil {
return nil, err
} else if auth.UserID(c) != group.UserID {
return nil, slerrors.NotFound("Item")
}
group.Update(update)
err = db.Groups().Update(c.Request.Context(), *group)
if err != nil {
return nil, err
}
return l.FindGroup(c, group.ID)
}))
g.DELETE("/:id", handler("group", func(c *gin.Context) (interface{}, error) {
group, err := l.FindGroup(c, c.Param("id"))
if err != nil {
return nil, err
}
if len(group.Items) > 0 {
return nil, slerrors.Forbidden("cannot delete non-empty group.")
}
goals, err := db.Goals().List(c.Request.Context(), models.GoalFilter{GroupIDs: []string{group.ID}})
if err != nil {
return nil, err
}
if len(goals) > 0 {
return nil, slerrors.Forbidden("cannot delete group with goals.")
}
err = db.Groups().Delete(c.Request.Context(), group.Group)
if err != nil {
return nil, err
}
return group, nil
}))
}
+98
View File
@@ -0,0 +1,98 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/gissleh/stufflog/database"
"github.com/gissleh/stufflog/internal/auth"
"github.com/gissleh/stufflog/internal/generate"
"github.com/gissleh/stufflog/internal/slerrors"
"github.com/gissleh/stufflog/models"
"github.com/gissleh/stufflog/services"
)
func Item(g *gin.RouterGroup, db database.Database) {
l := services.Loader{DB: db}
g.GET("/", handler("items", func(c *gin.Context) (interface{}, error) {
return l.ListItems(c, models.ItemFilter{})
}))
g.GET("/:id", handler("item", func(c *gin.Context) (interface{}, error) {
return l.FindItem(c, c.Param("id"))
}))
g.POST("/", handler("item", func(c *gin.Context) (interface{}, error) {
item := models.Item{}
err := c.BindJSON(&item)
if err != nil {
return nil, slerrors.BadRequest("Invalid JSON")
}
group, err := db.Groups().Find(c.Request.Context(), item.GroupID)
if err != nil {
return nil, err
} else if auth.UserID(c) != group.UserID {
return nil, slerrors.NotFound("Group")
}
item.ID = generate.ItemID()
item.UserID = auth.UserID(c)
item.GroupID = group.ID
err = db.Items().Insert(c.Request.Context(), item)
if err != nil {
return nil, err
}
return &models.ItemResult{
Item: item,
Group: group,
}, nil
}))
g.PUT("/:id", handler("item", func(c *gin.Context) (interface{}, error) {
update := models.ItemUpdate{}
err := c.BindJSON(&update)
if err != nil {
return nil, slerrors.BadRequest("Invalid JSON")
}
item, err := l.FindItem(c.Request.Context(), c.Param("id"))
if err != nil {
return nil, err
}
item.Update(update)
err = db.Items().Update(c.Request.Context(), item.Item)
if err != nil {
return nil, err
}
return item, nil
}))
g.DELETE("/:id", handler("item", func(c *gin.Context) (interface{}, error) {
item, err := l.FindItem(c.Request.Context(), c.Param("id"))
if err != nil {
return nil, err
}
tasks, err := db.Tasks().List(c.Request.Context(), models.TaskFilter{
UserID: auth.UserID(c),
ItemIDs: []string{item.ID},
})
if err != nil {
return nil, err
}
if len(tasks) > 0 {
return nil, slerrors.Forbidden("cannot delete item referenced in tasks.")
}
err = db.Items().Delete(c.Request.Context(), item.Item)
if err != nil {
return nil, err
}
return item, nil
}))
}
+115
View File
@@ -0,0 +1,115 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/gissleh/stufflog/database"
"github.com/gissleh/stufflog/internal/auth"
"github.com/gissleh/stufflog/internal/generate"
"github.com/gissleh/stufflog/internal/slerrors"
"github.com/gissleh/stufflog/models"
"github.com/gissleh/stufflog/services"
"time"
)
func Log(g *gin.RouterGroup, db database.Database) {
l := services.Loader{DB: db}
g.GET("/", handler("logs", func(c *gin.Context) (interface{}, error) {
filter := models.LogFilter{}
if value := c.Query("minTime"); value != "" {
minTime, err := time.Parse(time.RFC3339Nano, value)
if err != nil {
return nil, slerrors.BadRequest("Invalid minTime")
}
minTime = minTime.UTC()
filter.MinTime = &minTime
} else {
lastMonth := time.Now().Add(-30 * 24 * time.Hour).UTC()
filter.MinTime = &lastMonth
}
if value := c.Query("maxTime"); value != "" {
maxTime, err := time.Parse(time.RFC3339Nano, value)
if err != nil {
return nil, slerrors.BadRequest("Invalid maxTime")
}
maxTime = maxTime.UTC()
filter.MaxTime = &maxTime
}
return l.ListLogs(c, filter)
}))
g.GET("/:id", handler("log", func(c *gin.Context) (interface{}, error) {
return l.FindLog(c, c.Param("id"))
}))
g.POST("/", handler("log", func(c *gin.Context) (interface{}, error) {
log := models.Log{}
err := c.BindJSON(&log)
if err != nil {
return nil, slerrors.BadRequest("Invalid JSON")
}
task, err := l.FindTask(c.Request.Context(), log.TaskID)
if err != nil {
return nil, err
}
log.ID = generate.LogID()
log.UserID = auth.UserID(c)
log.TaskID = task.ID
log.ItemID = task.ItemID
if log.LoggedTime.IsZero() {
log.LoggedTime = time.Now().UTC()
} else {
log.LoggedTime = log.LoggedTime.UTC()
}
err = db.Logs().Insert(c.Request.Context(), log)
if err != nil {
return nil, err
}
return &models.LogResult{
Log: log,
Task: &task.Task,
}, nil
}))
g.PUT("/:id", handler("log", func(c *gin.Context) (interface{}, error) {
update := models.LogUpdate{}
err := c.BindJSON(&update)
if err != nil {
return nil, slerrors.BadRequest("Invalid JSON")
}
log, err := l.FindLog(c.Request.Context(), c.Param("id"))
if err != nil {
return nil, err
}
log.Update(update)
err = db.Logs().Update(c.Request.Context(), log.Log)
if err != nil {
return nil, err
}
return log, nil
}))
g.DELETE("/:id", handler("log", func(c *gin.Context) (interface{}, error) {
log, err := l.FindLog(c.Request.Context(), c.Param("id"))
if err != nil {
return nil, err
}
err = db.Logs().Delete(c.Request.Context(), log.Log)
if err != nil {
return nil, err
}
return log, nil
}))
}
+101
View File
@@ -0,0 +1,101 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/gissleh/stufflog/database"
"github.com/gissleh/stufflog/internal/auth"
"github.com/gissleh/stufflog/internal/generate"
"github.com/gissleh/stufflog/internal/slerrors"
"github.com/gissleh/stufflog/models"
"github.com/gissleh/stufflog/services"
"time"
)
func Project(g *gin.RouterGroup, db database.Database) {
l := services.Loader{DB: db}
defaultActive := true
g.GET("/", handler("projects", func(c *gin.Context) (interface{}, error) {
filter := models.ProjectFilter{}
if setting := c.Query("active"); setting != "" {
active := setting == "true"
filter.Active = &active
} else {
filter.Active = &defaultActive
}
if setting := c.Query("expiring"); setting != "" {
filter.Expiring = setting == "true"
}
return l.ListProjects(c, filter)
}))
g.GET("/:id", handler("project", func(c *gin.Context) (interface{}, error) {
return l.FindProject(c, c.Param("id"))
}))
g.POST("/", handler("project", func(c *gin.Context) (interface{}, error) {
project := models.Project{}
err := c.BindJSON(&project)
if err != nil {
return nil, slerrors.BadRequest("Invalid JSON")
}
if project.EndTime != nil && project.EndTime.Before(time.Now()) {
return nil, slerrors.BadRequest("Project end time must be later than current time.")
}
project.ID = generate.ProjectID()
project.UserID = auth.UserID(c)
project.CreatedTime = time.Now().UTC()
err = db.Projects().Insert(c.Request.Context(), project)
if err != nil {
return nil, err
}
return &models.ProjectResult{
Project: project,
Tasks: []*models.TaskResult{},
}, nil
}))
g.PUT("/:id", handler("project", func(c *gin.Context) (interface{}, error) {
update := models.ProjectUpdate{}
err := c.BindJSON(&update)
if err != nil {
return nil, slerrors.BadRequest("Invalid JSON")
}
project, err := l.FindProject(c.Request.Context(), c.Param("id"))
if err != nil {
return nil, err
}
project.Update(update)
err = db.Projects().Update(c.Request.Context(), project.Project)
if err != nil {
return nil, err
}
return project, nil
}))
g.DELETE("/:id", handler("project", func(c *gin.Context) (interface{}, error) {
project, err := l.FindProject(c.Request.Context(), c.Param("id"))
if err != nil {
return nil, err
}
if len(project.Tasks) > 0 {
return nil, slerrors.Forbidden("cannot delete non-empty projects.")
}
err = db.Projects().Delete(c.Request.Context(), project.Project)
if err != nil {
return nil, err
}
return project, nil
}))
}
+111
View File
@@ -0,0 +1,111 @@
package api
import (
"github.com/gin-gonic/gin"
"github.com/gissleh/stufflog/database"
"github.com/gissleh/stufflog/internal/auth"
"github.com/gissleh/stufflog/internal/generate"
"github.com/gissleh/stufflog/internal/slerrors"
"github.com/gissleh/stufflog/models"
"github.com/gissleh/stufflog/services"
"time"
)
func Task(g *gin.RouterGroup, db database.Database) {
l := services.Loader{DB: db}
defaultActive := true
g.GET("/", handler("tasks", func(c *gin.Context) (interface{}, error) {
filter := models.TaskFilter{}
if setting := c.Query("active"); setting != "" {
active := setting == "true"
filter.Active = &active
} else {
filter.Active = &defaultActive
}
return l.ListTasks(c, filter)
}))
g.GET("/:id", handler("task", func(c *gin.Context) (interface{}, error) {
return l.FindTask(c, c.Param("id"))
}))
g.POST("/", handler("task", func(c *gin.Context) (interface{}, error) {
task := models.Task{}
err := c.BindJSON(&task)
if err != nil {
return nil, slerrors.BadRequest("Invalid JSON")
}
if task.EndTime != nil && task.EndTime.Before(time.Now()) {
return nil, slerrors.BadRequest("Project end time must be later than current time.")
}
project, err := l.FindProject(c.Request.Context(), task.ProjectID)
if err != nil {
return nil, err
}
item, err := l.FindItem(c.Request.Context(), task.ItemID)
if err != nil {
return nil, err
}
task.ID = generate.TaskID()
task.UserID = auth.UserID(c)
task.CreatedTime = time.Now().UTC()
task.ItemID = item.ID
task.ProjectID = project.ID
err = db.Tasks().Insert(c.Request.Context(), task)
if err != nil {
return nil, err
}
return &models.TaskResult{
Task: task,
Logs: []*models.Log{},
Item: &item.Item,
CompletedAmount: 0,
}, nil
}))
g.PUT("/:id", handler("task", func(c *gin.Context) (interface{}, error) {
update := models.TaskUpdate{}
err := c.BindJSON(&update)
if err != nil {
return nil, slerrors.BadRequest("Invalid JSON")
}
task, err := l.FindTask(c.Request.Context(), c.Param("id"))
if err != nil {
return nil, err
}
task.Update(update)
err = db.Tasks().Update(c.Request.Context(), task.Task)
if err != nil {
return nil, err
}
return task, nil
}))
g.DELETE("/:id", handler("task", func(c *gin.Context) (interface{}, error) {
task, err := l.FindTask(c.Request.Context(), c.Param("id"))
if err != nil {
return nil, err
}
if len(task.Logs) > 0 {
return nil, slerrors.Forbidden("cannot delete tasks with logs.")
}
err = db.Tasks().Delete(c.Request.Context(), task.Task)
if err != nil {
return nil, err
}
return task, nil
}))
}