switch to zero log

This commit is contained in:
Hayden 2022-09-03 10:38:35 -08:00
parent 9351b3fd42
commit 68204a4f22
26 changed files with 122 additions and 1335 deletions

View file

@ -7,14 +7,12 @@ import (
"github.com/hay-kot/content/backend/internal/config"
"github.com/hay-kot/content/backend/internal/repo"
"github.com/hay-kot/content/backend/internal/services"
"github.com/hay-kot/content/backend/pkgs/logger"
"github.com/hay-kot/content/backend/pkgs/mailer"
"github.com/hay-kot/content/backend/pkgs/server"
)
type app struct {
conf *config.Config
logger *logger.Logger
mailer mailer.Mailer
db *ent.Client
server *server.Server

View file

@ -4,20 +4,17 @@ import (
"net/http"
"github.com/hay-kot/content/backend/internal/types"
"github.com/hay-kot/content/backend/pkgs/logger"
"github.com/hay-kot/content/backend/pkgs/server"
)
type ReadyFunc func() bool
type BaseController struct {
log *logger.Logger
svr *server.Server
}
func NewBaseController(log *logger.Logger, svr *server.Server) *BaseController {
func NewBaseController(svr *server.Server) *BaseController {
h := &BaseController{
log: log,
svr: svr,
}
return h

View file

@ -4,12 +4,10 @@ import (
"net/http"
"net/http/httptest"
"testing"
"github.com/hay-kot/content/backend/internal/mocks"
)
func GetTestHandler(t *testing.T) *BaseController {
return NewBaseController(mocks.GetStructLogger(), nil)
return NewBaseController(nil)
}
func TestHandlersv1_HandleBase(t *testing.T) {

View file

@ -52,215 +52,6 @@ const docTemplate = `{
}
}
},
"/v1/admin/users": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Admin: Users"
],
"summary": "Gets all users from the database",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/server.Result"
},
{
"type": "object",
"properties": {
"item": {
"type": "array",
"items": {
"$ref": "#/definitions/ent.User"
}
}
}
}
]
}
}
}
},
"post": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Admin: Users"
],
"summary": "Create a new user",
"parameters": [
{
"description": "User Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.UserCreate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/server.Result"
},
{
"type": "object",
"properties": {
"item": {
"$ref": "#/definitions/ent.User"
}
}
}
]
}
}
}
}
},
"/v1/admin/users/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Admin: Users"
],
"summary": "Get a user from the database",
"parameters": [
{
"type": "string",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/server.Result"
},
{
"type": "object",
"properties": {
"item": {
"$ref": "#/definitions/ent.User"
}
}
}
]
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Admin: Users"
],
"summary": "Update a User",
"parameters": [
{
"type": "string",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "User Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.UserUpdate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/server.Result"
},
{
"type": "object",
"properties": {
"item": {
"$ref": "#/definitions/ent.User"
}
}
}
]
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Admin: Users"
],
"summary": "Delete a User",
"parameters": [
{
"type": "string",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": ""
}
}
}
},
"/v1/items": {
"get": {
"security": [
@ -1643,9 +1434,6 @@ const docTemplate = `{
"description": {
"type": "string"
},
"groupId": {
"type": "string"
},
"id": {
"type": "string"
},
@ -1680,9 +1468,6 @@ const docTemplate = `{
"description": {
"type": "string"
},
"groupId": {
"type": "string"
},
"id": {
"type": "string"
},
@ -1709,9 +1494,6 @@ const docTemplate = `{
"description": {
"type": "string"
},
"groupId": {
"type": "string"
},
"id": {
"type": "string"
},
@ -1734,26 +1516,6 @@ const docTemplate = `{
}
}
},
"types.UserCreate": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"groupID": {
"type": "string"
},
"isSuperuser": {
"type": "boolean"
},
"name": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"types.UserIn": {
"type": "object",
"properties": {

View file

@ -44,215 +44,6 @@
}
}
},
"/v1/admin/users": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Admin: Users"
],
"summary": "Gets all users from the database",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/server.Result"
},
{
"type": "object",
"properties": {
"item": {
"type": "array",
"items": {
"$ref": "#/definitions/ent.User"
}
}
}
}
]
}
}
}
},
"post": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Admin: Users"
],
"summary": "Create a new user",
"parameters": [
{
"description": "User Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.UserCreate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/server.Result"
},
{
"type": "object",
"properties": {
"item": {
"$ref": "#/definitions/ent.User"
}
}
}
]
}
}
}
}
},
"/v1/admin/users/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Admin: Users"
],
"summary": "Get a user from the database",
"parameters": [
{
"type": "string",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/server.Result"
},
{
"type": "object",
"properties": {
"item": {
"$ref": "#/definitions/ent.User"
}
}
}
]
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Admin: Users"
],
"summary": "Update a User",
"parameters": [
{
"type": "string",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "User Data",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/types.UserUpdate"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/server.Result"
},
{
"type": "object",
"properties": {
"item": {
"$ref": "#/definitions/ent.User"
}
}
}
]
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"produces": [
"application/json"
],
"tags": [
"Admin: Users"
],
"summary": "Delete a User",
"parameters": [
{
"type": "string",
"description": "User ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"204": {
"description": ""
}
}
}
},
"/v1/items": {
"get": {
"security": [
@ -1635,9 +1426,6 @@
"description": {
"type": "string"
},
"groupId": {
"type": "string"
},
"id": {
"type": "string"
},
@ -1672,9 +1460,6 @@
"description": {
"type": "string"
},
"groupId": {
"type": "string"
},
"id": {
"type": "string"
},
@ -1701,9 +1486,6 @@
"description": {
"type": "string"
},
"groupId": {
"type": "string"
},
"id": {
"type": "string"
},
@ -1726,26 +1508,6 @@
}
}
},
"types.UserCreate": {
"type": "object",
"properties": {
"email": {
"type": "string"
},
"groupID": {
"type": "string"
},
"isSuperuser": {
"type": "boolean"
},
"name": {
"type": "string"
},
"password": {
"type": "string"
}
}
},
"types.UserIn": {
"type": "object",
"properties": {

View file

@ -493,8 +493,6 @@ definitions:
type: string
description:
type: string
groupId:
type: string
id:
type: string
itemCount:
@ -517,8 +515,6 @@ definitions:
type: string
description:
type: string
groupId:
type: string
id:
type: string
items:
@ -536,8 +532,6 @@ definitions:
type: string
description:
type: string
groupId:
type: string
id:
type: string
name:
@ -552,19 +546,6 @@ definitions:
token:
type: string
type: object
types.UserCreate:
properties:
email:
type: string
groupID:
type: string
isSuperuser:
type: boolean
name:
type: string
password:
type: string
type: object
types.UserIn:
properties:
email:
@ -616,124 +597,6 @@ paths:
summary: Retrieves the basic information about the API
tags:
- Base
/v1/admin/users:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/server.Result'
- properties:
item:
items:
$ref: '#/definitions/ent.User'
type: array
type: object
security:
- Bearer: []
summary: Gets all users from the database
tags:
- 'Admin: Users'
post:
parameters:
- description: User Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/types.UserCreate'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/server.Result'
- properties:
item:
$ref: '#/definitions/ent.User'
type: object
security:
- Bearer: []
summary: Create a new user
tags:
- 'Admin: Users'
/v1/admin/users/{id}:
delete:
parameters:
- description: User ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"204":
description: ""
security:
- Bearer: []
summary: Delete a User
tags:
- 'Admin: Users'
get:
parameters:
- description: User ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/server.Result'
- properties:
item:
$ref: '#/definitions/ent.User'
type: object
security:
- Bearer: []
summary: Get a user from the database
tags:
- 'Admin: Users'
put:
parameters:
- description: User ID
in: path
name: id
required: true
type: string
- description: User Data
in: body
name: payload
required: true
schema:
$ref: '#/definitions/types.UserUpdate'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/server.Result'
- properties:
item:
$ref: '#/definitions/ent.User'
type: object
security:
- Bearer: []
summary: Update a User
tags:
- 'Admin: Users'
/v1/items:
get:
produces:

View file

@ -2,8 +2,6 @@ package main
import (
"context"
"io"
"log"
"os"
"time"
@ -12,9 +10,10 @@ import (
"github.com/hay-kot/content/backend/internal/config"
"github.com/hay-kot/content/backend/internal/repo"
"github.com/hay-kot/content/backend/internal/services"
"github.com/hay-kot/content/backend/pkgs/logger"
"github.com/hay-kot/content/backend/pkgs/server"
_ "github.com/mattn/go-sqlite3"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
// @title Go API Templates
@ -28,6 +27,10 @@ import (
// @name Authorization
// @description "Type 'Bearer TOKEN' to correctly set the API Key"
func main() {
// Logger Init
// zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
cfgFile := "config.yml"
cfg, err := config.NewConfig(cfgFile)
@ -45,42 +48,24 @@ func main() {
func run(cfg *config.Config) error {
app := NewApp(cfg)
// =========================================================================
// Setup Logger
var wrt io.Writer
wrt = os.Stdout
if app.conf.Log.File != "" {
f, err := os.OpenFile(app.conf.Log.File, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
log.Fatalf("error opening file: %v", err)
}
defer func(f *os.File) {
_ = f.Close()
}(f)
wrt = io.MultiWriter(wrt, f)
}
app.logger = logger.New(wrt, logger.LevelDebug)
// =========================================================================
// Initialize Database & Repos
c, err := ent.Open(cfg.Database.GetDriver(), cfg.Database.GetUrl())
if err != nil {
app.logger.Fatal(err, logger.Props{
"details": "failed to connect to database",
"database": cfg.Database.GetDriver(),
"url": cfg.Database.GetUrl(),
})
log.Fatal().
Err(err).
Str("driver", cfg.Database.GetDriver()).
Str("url", cfg.Database.GetUrl()).
Msg("failed opening connection to sqlite")
}
defer func(c *ent.Client) {
_ = c.Close()
}(c)
if err := c.Schema.Create(context.Background()); err != nil {
app.logger.Fatal(err, logger.Props{
"details": "failed to create schema",
})
log.Fatal().
Err(err).
Msg("failed creating schema resources")
}
app.db = c
@ -99,10 +84,7 @@ func run(cfg *config.Config) error {
app.SeedDatabase(app.repos)
app.logger.Info("Starting HTTP Server", logger.Props{
"host": app.server.Host,
"port": app.server.Port,
})
log.Info().Msgf("Starting HTTP Server on %s:%s", app.server.Host, app.server.Port)
// =========================================================================
// Start Reoccurring Tasks
@ -110,9 +92,9 @@ func run(cfg *config.Config) error {
go app.StartReoccurringTasks(time.Duration(24)*time.Hour, func() {
_, err := app.repos.AuthTokens.PurgeExpiredTokens(context.Background())
if err != nil {
app.logger.Error(err, logger.Props{
"details": "failed to purge expired tokens",
})
log.Error().
Err(err).
Msg("failed to purge expired tokens")
}
})

View file

@ -10,8 +10,8 @@ import (
"github.com/go-chi/chi/v5/middleware"
"github.com/hay-kot/content/backend/internal/config"
"github.com/hay-kot/content/backend/internal/services"
"github.com/hay-kot/content/backend/pkgs/logger"
"github.com/hay-kot/content/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
func (a *app) setGlobalMiddleware(r *chi.Mux) {
@ -24,7 +24,7 @@ func (a *app) setGlobalMiddleware(r *chi.Mux) {
// Use struct logger in production for requests, but use
// pretty console logger in development.
if a.conf.Mode == config.ModeDevelopment {
r.Use(middleware.Logger)
r.Use(a.mwSummaryLogger)
} else {
r.Use(a.mwStructLogger)
}
@ -98,13 +98,38 @@ func (a *app) mwStructLogger(next http.Handler) http.Handler {
url := fmt.Sprintf("%s://%s%s %s", scheme, r.Host, r.RequestURI, r.Proto)
a.logger.Info(fmt.Sprintf("[%s] %s", r.Method, url), logger.Props{
"id": middleware.GetReqID(r.Context()),
"method": r.Method,
"url": url,
"remote": r.RemoteAddr,
})
log.Info().
Str("id", middleware.GetReqID(r.Context())).
Str("url", url).
Str("method", r.Method).
Str("remote_addr", r.RemoteAddr).
Msgf("[%s] %s", r.Method, url)
next.ServeHTTP(w, r)
})
}
func (a *app) mwSummaryLogger(next http.Handler) http.Handler {
bold := func(s string) string {
return "\033[1m" + s + "\033[0m"
}
pink := func(s string) string {
return "\033[35m" + s + "\033[0m"
}
aqua := func(s string) string {
return "\033[36m" + s + "\033[0m"
}
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
url := fmt.Sprintf("%s://%s%s %s", scheme, r.Host, r.RequestURI, r.Proto)
log.Info().Msgf("%s %s", bold(pink("["+r.Method+"]")), aqua(url))
next.ServeHTTP(w, r)
})
}

View file

@ -31,7 +31,7 @@ func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux {
http.ServeFile(w, r, "static/favicon.ico")
})
baseHandler := base.NewBaseController(a.logger, a.server)
baseHandler := base.NewBaseController(a.server)
r.Get(prefix+"/status", baseHandler.HandleBase(func() bool { return true }, "v1"))
// =========================================================================
@ -39,7 +39,7 @@ func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux {
v1Base := v1.BaseUrlFunc(prefix)
{
v1Handlers := v1.NewControllerV1(a.logger, a.services)
v1Handlers := v1.NewControllerV1(a.services)
r.Post(v1Base("/users/register"), v1Handlers.HandleUserRegistration())
r.Post(v1Base("/users/login"), v1Handlers.HandleAuthLogin())
r.Group(func(r chi.Router) {
@ -68,15 +68,6 @@ func (a *app) newRouter(repos *repo.AllRepos) *chi.Mux {
r.Put(v1Base("/items/{id}"), v1Handlers.HandleItemUpdate())
r.Delete(v1Base("/items/{id}"), v1Handlers.HandleItemDelete())
})
r.Group(func(r chi.Router) {
r.Use(a.mwAdminOnly)
r.Get(v1Base("/admin/users"), v1Handlers.HandleAdminUserGetAll())
r.Post(v1Base("/admin/users"), v1Handlers.HandleAdminUserCreate())
r.Get(v1Base("/admin/users/{id}"), v1Handlers.HandleAdminUserGet())
r.Put(v1Base("/admin/users/{id}"), v1Handlers.HandleAdminUserUpdate())
r.Delete(v1Base("/admin/users/{id}"), v1Handlers.HandleAdminUserDelete())
})
}
return r

View file

@ -7,7 +7,7 @@ import (
"github.com/hay-kot/content/backend/internal/repo"
"github.com/hay-kot/content/backend/internal/types"
"github.com/hay-kot/content/backend/pkgs/hasher"
"github.com/hay-kot/content/backend/pkgs/logger"
"github.com/rs/zerolog/log"
)
const (
@ -23,7 +23,7 @@ func (a *app) EnsureAdministrator() {
superusers, err := a.repos.Users.GetSuperusers(context.Background())
if err != nil {
a.logger.Fatal(err, nil)
log.Fatal().Err(err).Msg("failed to get superusers")
}
if len(superusers) > 0 {
return
@ -37,15 +37,15 @@ func (a *app) EnsureAdministrator() {
Password: pw,
}
a.logger.Info("creating default superuser", logger.Props{
"name": newSuperUser.Name,
"email": newSuperUser.Email,
})
log.Info().
Str("name", newSuperUser.Name).
Str("email", newSuperUser.Email).
Msg("no superusers found, creating default superuser")
_, err = a.repos.Users.Create(context.Background(), newSuperUser)
if err != nil {
a.logger.Fatal(err, nil)
log.Fatal().Err(err).Msg("failed to create default superuser")
}
}
@ -57,7 +57,7 @@ func (a *app) SeedDatabase(repos *repo.AllRepos) {
group, err := repos.Groups.Create(context.Background(), DefaultGroup)
if err != nil {
a.logger.Fatal(err, nil)
log.Fatal().Err(err).Msg("failed to create default group")
}
for _, user := range a.conf.Seed.Users {
@ -66,19 +66,13 @@ func (a *app) SeedDatabase(repos *repo.AllRepos) {
usr, _ := repos.Users.GetOneEmail(context.Background(), user.Email)
if usr.ID != uuid.Nil {
a.logger.Info("seed user already exists", logger.Props{
"user": user.Name,
})
log.Info().Str("email", user.Email).Msg("user already exists, skipping")
continue
}
hashedPw, err := hasher.HashPassword(user.Password)
if err != nil {
a.logger.Error(err, logger.Props{
"details": "failed to hash password",
"user": user.Name,
})
log.Fatal().Err(err).Msg("failed to hash password")
}
_, err = repos.Users.Create(context.Background(), types.UserCreate{
@ -90,14 +84,9 @@ func (a *app) SeedDatabase(repos *repo.AllRepos) {
})
if err != nil {
a.logger.Error(err, logger.Props{
"details": "failed to create seed user",
"name": user.Name,
})
log.Fatal().Err(err).Msg("failed to create user")
}
a.logger.Info("creating seed user", logger.Props{
"name": user.Name,
})
log.Info().Str("email", user.Email).Msg("created user")
}
}

View file

@ -2,11 +2,9 @@ package v1
import (
"github.com/hay-kot/content/backend/internal/services"
"github.com/hay-kot/content/backend/pkgs/logger"
)
type V1Controller struct {
log *logger.Logger
svc *services.AllServices
}
@ -19,9 +17,8 @@ func BaseUrlFunc(prefix string) func(s string) string {
return prefixFunc
}
func NewControllerV1(log *logger.Logger, svc *services.AllServices) *V1Controller {
func NewControllerV1(svc *services.AllServices) *V1Controller {
ctrl := &V1Controller{
log: log,
svc: svc,
}

View file

@ -9,12 +9,10 @@ import (
func Test_NewHandlerV1(t *testing.T) {
v1Base := BaseUrlFunc("/testing/v1")
ctrl := NewControllerV1(mockHandler.log, mockHandler.svc)
ctrl := NewControllerV1(mockHandler.svc)
assert.NotNil(t, ctrl)
assert.Equal(t, ctrl.log, mockHandler.log)
assert.Equal(t, "/testing/v1/v1/abc123", v1Base("/abc123"))
assert.Equal(t, "/testing/v1/v1/abc123", v1Base("/abc123"))
}

View file

@ -39,7 +39,6 @@ func userPool() func() {
func TestMain(m *testing.M) {
// Set Handler Vars
mockHandler.log = mocks.GetStructLogger()
repos, closeDb := mocks.GetEntRepos()
mockHandler.svc = mocks.GetMockServices(repos)

View file

@ -7,8 +7,8 @@ import (
"github.com/google/uuid"
"github.com/hay-kot/content/backend/internal/services"
"github.com/hay-kot/content/backend/internal/types"
"github.com/hay-kot/content/backend/pkgs/logger"
"github.com/hay-kot/content/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
/*
@ -24,9 +24,7 @@ and makes it a little more consistent when error handling and logging.
func (ctrl *V1Controller) partialParseIdAndUser(w http.ResponseWriter, r *http.Request) (uuid.UUID, *types.UserOut, error) {
uid, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil {
ctrl.log.Debug(err.Error(), logger.Props{
"details": "failed to convert id to valid UUID",
})
log.Err(err).Msg("failed to parse id")
server.RespondError(w, http.StatusBadRequest, err)
return uuid.Nil, nil, err
}

View file

@ -1,207 +0,0 @@
package v1
import (
"errors"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/hay-kot/content/backend/internal/services"
"github.com/hay-kot/content/backend/internal/types"
"github.com/hay-kot/content/backend/pkgs/hasher"
"github.com/hay-kot/content/backend/pkgs/logger"
"github.com/hay-kot/content/backend/pkgs/server"
)
// HandleAdminUserGetAll godoc
// @Summary Gets all users from the database
// @Tags Admin: Users
// @Produce json
// @Success 200 {object} server.Result{item=[]ent.User}
// @Router /v1/admin/users [get]
// @Security Bearer
func (ctrl *V1Controller) HandleAdminUserGetAll() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
users, err := ctrl.svc.Admin.GetAll(r.Context())
if err != nil {
server.RespondError(w, http.StatusInternalServerError, err)
return
}
server.Respond(w, http.StatusOK, server.Wrap(users))
}
}
// HandleAdminUserGet godoc
// @Summary Get a user from the database
// @Tags Admin: Users
// @Produce json
// @Param id path string true "User ID"
// @Success 200 {object} server.Result{item=ent.User}
// @Router /v1/admin/users/{id} [get]
// @Security Bearer
func (ctrl *V1Controller) HandleAdminUserGet() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
uid, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil {
ctrl.log.Debug(err.Error(), logger.Props{
"scope": "admin",
"details": "failed to convert id to valid UUID",
})
server.RespondError(w, http.StatusBadRequest, err)
return
}
user, err := ctrl.svc.Admin.GetByID(r.Context(), uid)
if err != nil {
ctrl.log.Error(err, nil)
server.RespondError(w, http.StatusInternalServerError, err)
return
}
server.Respond(w, http.StatusOK, server.Wrap(user))
}
}
// HandleAdminUserCreate godoc
// @Summary Create a new user
// @Tags Admin: Users
// @Produce json
// @Param payload body types.UserCreate true "User Data"
// @Success 200 {object} server.Result{item=ent.User}
// @Router /v1/admin/users [POST]
// @Security Bearer
func (ctrl *V1Controller) HandleAdminUserCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
createData := types.UserCreate{}
if err := server.Decode(r, &createData); err != nil {
ctrl.log.Error(err, logger.Props{
"scope": "admin",
"details": "failed to decode user create data",
})
server.RespondError(w, http.StatusBadRequest, err)
return
}
err := createData.Validate()
if err != nil {
server.RespondError(w, http.StatusUnprocessableEntity, err)
return
}
hashedPw, err := hasher.HashPassword(createData.Password)
if err != nil {
ctrl.log.Error(err, logger.Props{
"scope": "admin",
"details": "failed to hash password",
})
server.RespondError(w, http.StatusInternalServerError, err)
return
}
createData.Password = hashedPw
userOut, err := ctrl.svc.Admin.Create(r.Context(), createData)
if err != nil {
ctrl.log.Error(err, logger.Props{
"scope": "admin",
"details": "failed to create user",
})
server.RespondError(w, http.StatusInternalServerError, err)
return
}
server.Respond(w, http.StatusCreated, server.Wrap(userOut))
}
}
// HandleAdminUserUpdate godoc
// @Summary Update a User
// @Tags Admin: Users
// @Param id path string true "User ID"
// @Param payload body types.UserUpdate true "User Data"
// @Produce json
// @Success 200 {object} server.Result{item=ent.User}
// @Router /v1/admin/users/{id} [PUT]
// @Security Bearer
func (ctrl *V1Controller) HandleAdminUserUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
uid, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil {
ctrl.log.Debug(err.Error(), logger.Props{
"scope": "admin",
"details": "failed to convert id to valid UUID",
})
}
updateData := types.UserUpdate{}
if err := server.Decode(r, &updateData); err != nil {
ctrl.log.Error(err, logger.Props{
"scope": "admin",
"details": "failed to decode user update data",
})
server.RespondError(w, http.StatusBadRequest, err)
return
}
newData, err := ctrl.svc.Admin.UpdateProperties(r.Context(), uid, updateData)
if err != nil {
ctrl.log.Error(err, logger.Props{
"scope": "admin",
"details": "failed to update user",
})
server.RespondError(w, http.StatusInternalServerError, err)
return
}
server.Respond(w, http.StatusOK, server.Wrap(newData))
}
}
// HandleAdminUserDelete godoc
// @Summary Delete a User
// @Tags Admin: Users
// @Param id path string true "User ID"
// @Produce json
// @Success 204
// @Router /v1/admin/users/{id} [DELETE]
// @Security Bearer
func (ctrl *V1Controller) HandleAdminUserDelete() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
uid, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil {
ctrl.log.Debug(err.Error(), logger.Props{
"scope": "admin",
"details": "failed to convert id to valid UUID",
})
}
actor := services.UseUserCtx(r.Context())
if actor.ID == uid {
server.RespondError(w, http.StatusBadRequest, errors.New("cannot delete yourself"))
return
}
err = ctrl.svc.Admin.Delete(r.Context(), uid)
if err != nil {
ctrl.log.Error(err, logger.Props{
"scope": "admin",
"details": "failed to delete user",
})
server.RespondError(w, http.StatusInternalServerError, err)
return
}
}
}

View file

@ -1,109 +0,0 @@
package v1
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/hay-kot/content/backend/ent"
"github.com/hay-kot/content/backend/internal/mocks/chimocker"
"github.com/hay-kot/content/backend/internal/mocks/factories"
"github.com/hay-kot/content/backend/pkgs/server"
"github.com/stretchr/testify/assert"
)
const (
UrlUser = "/api/v1/admin/users"
UrlUserId = "/api/v1/admin/users/%v"
UrlUserIdChi = "/api/v1/admin/users/{id}"
)
type usersResponse struct {
Users []*ent.User `json:"item"`
}
type userResponse struct {
User *ent.User `json:"item"`
}
func Test_HandleAdminUserGetAll_Success(t *testing.T) {
r := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, UrlUser, nil)
mockHandler.HandleAdminUserGetAll()(r, req)
response := usersResponse{
Users: []*ent.User{},
}
_ = json.Unmarshal(r.Body.Bytes(), &response)
assert.Equal(t, http.StatusOK, r.Code)
assert.Equal(t, len(users), len(response.Users))
knowEmail := []string{
users[0].Email,
users[1].Email,
users[2].Email,
users[3].Email,
}
for _, user := range users {
assert.Contains(t, knowEmail, user.Email)
}
}
func Test_HandleAdminUserGet_Success(t *testing.T) {
targetUser := users[2]
res := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, fmt.Sprintf(UrlUserId, targetUser.ID), nil)
req = chimocker.WithUrlParam(req, "id", fmt.Sprintf("%v", targetUser.ID))
mockHandler.HandleAdminUserGet()(res, req)
assert.Equal(t, http.StatusOK, res.Code)
response := userResponse{
User: &ent.User{},
}
_ = json.Unmarshal(res.Body.Bytes(), &response)
assert.Equal(t, targetUser.ID, response.User.ID)
}
func Test_HandleAdminUserCreate_Success(t *testing.T) {
payload := factories.UserFactory()
r := httptest.NewRecorder()
body, err := json.Marshal(payload)
assert.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, UrlUser, bytes.NewBuffer(body))
req.Header.Set(server.ContentType, server.ContentJSON)
mockHandler.HandleAdminUserCreate()(r, req)
assert.Equal(t, http.StatusCreated, r.Code)
usr, err := mockHandler.svc.Admin.GetByEmail(context.Background(), payload.Email)
assert.NoError(t, err)
assert.Equal(t, payload.Email, usr.Email)
assert.Equal(t, payload.Name, usr.Name)
assert.NotEqual(t, payload.Password, usr.Password) // smoke test - check password is hashed
_ = mockHandler.svc.Admin.Delete(context.Background(), usr.ID)
}
func Test_HandleAdminUserUpdate_Success(t *testing.T) {
t.Skip()
}
func Test_HandleAdminUserUpdate_Delete(t *testing.T) {
t.Skip()
}

View file

@ -6,8 +6,8 @@ import (
"github.com/hay-kot/content/backend/internal/services"
"github.com/hay-kot/content/backend/internal/types"
"github.com/hay-kot/content/backend/pkgs/logger"
"github.com/hay-kot/content/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
// HandleAuthLogin godoc
@ -28,7 +28,7 @@ func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
err := r.ParseForm()
if err != nil {
server.Respond(w, http.StatusBadRequest, server.Wrap(err))
ctrl.log.Error(errors.New("failed to decode login form (FORM)"), logger.Props{"error": err.Error()})
log.Error().Err(err).Msg("failed to parse form")
return
}
@ -38,9 +38,7 @@ func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
err := server.Decode(r, loginForm)
if err != nil {
ctrl.log.Error(errors.New("failed to decode login form (JSON)"), logger.Props{
"error": err.Error(),
})
log.Err(err).Msg("failed to decode login form")
server.Respond(w, http.StatusBadRequest, server.Wrap(err))
return
}

View file

@ -6,6 +6,7 @@ import (
"github.com/hay-kot/content/backend/internal/services"
"github.com/hay-kot/content/backend/internal/types"
"github.com/hay-kot/content/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
// HandleItemsGetAll godoc
@ -20,7 +21,7 @@ func (ctrl *V1Controller) HandleItemsGetAll() http.HandlerFunc {
user := services.UseUserCtx(r.Context())
items, err := ctrl.svc.Items.GetAll(r.Context(), user.GroupID)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to get items")
server.RespondServerError(w)
return
}
@ -40,7 +41,7 @@ func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
createData := types.ItemCreate{}
if err := server.Decode(r, &createData); err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to decode request body")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
@ -48,7 +49,7 @@ func (ctrl *V1Controller) HandleItemsCreate() http.HandlerFunc {
user := services.UseUserCtx(r.Context())
item, err := ctrl.svc.Items.Create(r.Context(), user.GroupID, createData)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to create item")
server.RespondServerError(w)
return
}
@ -75,7 +76,7 @@ func (ctrl *V1Controller) HandleItemDelete() http.HandlerFunc {
err = ctrl.svc.Items.Delete(r.Context(), user.GroupID, uid)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to delete item")
server.RespondServerError(w)
return
}
@ -100,7 +101,7 @@ func (ctrl *V1Controller) HandleItemGet() http.HandlerFunc {
items, err := ctrl.svc.Items.GetOne(r.Context(), user.GroupID, uid)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to get item")
server.RespondServerError(w)
return
}
@ -120,7 +121,7 @@ func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body := types.ItemUpdate{}
if err := server.Decode(r, &body); err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to decode request body")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
@ -132,7 +133,7 @@ func (ctrl *V1Controller) HandleItemUpdate() http.HandlerFunc {
body.ID = uid
result, err := ctrl.svc.Items.Update(r.Context(), user.GroupID, body)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to update item")
server.RespondServerError(w)
return
}

View file

@ -6,6 +6,7 @@ import (
"github.com/hay-kot/content/backend/internal/services"
"github.com/hay-kot/content/backend/internal/types"
"github.com/hay-kot/content/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
// HandleLabelsGetAll godoc
@ -20,7 +21,7 @@ func (ctrl *V1Controller) HandleLabelsGetAll() http.HandlerFunc {
user := services.UseUserCtx(r.Context())
labels, err := ctrl.svc.Labels.GetAll(r.Context(), user.GroupID)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("error getting labels")
server.RespondServerError(w)
return
}
@ -40,7 +41,7 @@ func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
createData := types.LabelCreate{}
if err := server.Decode(r, &createData); err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("error decoding label create data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
@ -48,7 +49,7 @@ func (ctrl *V1Controller) HandleLabelsCreate() http.HandlerFunc {
user := services.UseUserCtx(r.Context())
label, err := ctrl.svc.Labels.Create(r.Context(), user.GroupID, createData)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("error creating label")
server.RespondServerError(w)
return
}
@ -75,7 +76,7 @@ func (ctrl *V1Controller) HandleLabelDelete() http.HandlerFunc {
err = ctrl.svc.Labels.Delete(r.Context(), user.GroupID, uid)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("error deleting label")
server.RespondServerError(w)
return
}
@ -100,7 +101,7 @@ func (ctrl *V1Controller) HandleLabelGet() http.HandlerFunc {
labels, err := ctrl.svc.Labels.Get(r.Context(), user.GroupID, uid)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("error getting label")
server.RespondServerError(w)
return
}
@ -120,7 +121,7 @@ func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body := types.LabelUpdate{}
if err := server.Decode(r, &body); err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("error decoding label update data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
@ -132,7 +133,7 @@ func (ctrl *V1Controller) HandleLabelUpdate() http.HandlerFunc {
body.ID = uid
result, err := ctrl.svc.Labels.Update(r.Context(), user.GroupID, body)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("error updating label")
server.RespondServerError(w)
return
}

View file

@ -6,6 +6,7 @@ import (
"github.com/hay-kot/content/backend/internal/services"
"github.com/hay-kot/content/backend/internal/types"
"github.com/hay-kot/content/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
// HandleLocationGetAll godoc
@ -20,7 +21,7 @@ func (ctrl *V1Controller) HandleLocationGetAll() http.HandlerFunc {
user := services.UseUserCtx(r.Context())
locations, err := ctrl.svc.Location.GetAll(r.Context(), user.GroupID)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to get locations")
server.RespondServerError(w)
return
}
@ -41,7 +42,7 @@ func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
createData := types.LocationCreate{}
if err := server.Decode(r, &createData); err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to decode location create data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
@ -49,7 +50,7 @@ func (ctrl *V1Controller) HandleLocationCreate() http.HandlerFunc {
user := services.UseUserCtx(r.Context())
location, err := ctrl.svc.Location.Create(r.Context(), user.GroupID, createData)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to create location")
server.RespondServerError(w)
return
}
@ -75,7 +76,7 @@ func (ctrl *V1Controller) HandleLocationDelete() http.HandlerFunc {
err = ctrl.svc.Location.Delete(r.Context(), user.GroupID, uid)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to delete location")
server.RespondServerError(w)
return
}
@ -100,7 +101,7 @@ func (ctrl *V1Controller) HandleLocationGet() http.HandlerFunc {
location, err := ctrl.svc.Location.GetOne(r.Context(), user.GroupID, uid)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to get location")
server.RespondServerError(w)
return
}
@ -120,7 +121,7 @@ func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
body := types.LocationUpdate{}
if err := server.Decode(r, &body); err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to decode location update data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
@ -134,7 +135,7 @@ func (ctrl *V1Controller) HandleLocationUpdate() http.HandlerFunc {
result, err := ctrl.svc.Location.Update(r.Context(), user.GroupID, body)
if err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to update location")
server.RespondServerError(w)
return
}

View file

@ -1,14 +1,13 @@
package v1
import (
"errors"
"net/http"
"github.com/google/uuid"
"github.com/hay-kot/content/backend/internal/services"
"github.com/hay-kot/content/backend/internal/types"
"github.com/hay-kot/content/backend/pkgs/logger"
"github.com/hay-kot/content/backend/pkgs/server"
"github.com/rs/zerolog/log"
)
// HandleUserSelf godoc
@ -23,14 +22,13 @@ func (ctrl *V1Controller) HandleUserRegistration() http.HandlerFunc {
regData := types.UserRegistration{}
if err := server.Decode(r, &regData); err != nil {
ctrl.log.Error(err, nil)
log.Err(err).Msg("failed to decode user registration data")
server.RespondError(w, http.StatusInternalServerError, err)
return
}
_, err := ctrl.svc.User.RegisterUser(r.Context(), regData)
if err != nil {
ctrl.log.Error(err, nil)
server.RespondError(w, http.StatusInternalServerError, err)
return
}
@ -51,7 +49,7 @@ func (ctrl *V1Controller) HandleUserSelf() http.HandlerFunc {
token := services.UseTokenCtx(r.Context())
usr, err := ctrl.svc.User.GetSelf(r.Context(), token)
if usr.ID == uuid.Nil || err != nil {
ctrl.log.Error(errors.New("no user within request context"), nil)
log.Err(err).Msg("failed to get user")
server.RespondServerError(w)
return
}
@ -72,10 +70,7 @@ func (ctrl *V1Controller) HandleUserUpdate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
updateData := types.UserUpdate{}
if err := server.Decode(r, &updateData); err != nil {
ctrl.log.Error(err, logger.Props{
"scope": "user",
"details": "failed to decode user update data",
})
log.Err(err).Msg("failed to decode user update data")
server.RespondError(w, http.StatusBadRequest, err)
return
}
@ -84,10 +79,7 @@ func (ctrl *V1Controller) HandleUserUpdate() http.HandlerFunc {
newData, err := ctrl.svc.User.UpdateSelf(r.Context(), actor.ID, updateData)
if err != nil {
ctrl.log.Error(err, logger.Props{
"scope": "user",
"details": "failed to update user",
})
server.RespondError(w, http.StatusInternalServerError, err)
return
}