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/config"
"github.com/hay-kot/content/backend/internal/repo" "github.com/hay-kot/content/backend/internal/repo"
"github.com/hay-kot/content/backend/internal/services" "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/mailer"
"github.com/hay-kot/content/backend/pkgs/server" "github.com/hay-kot/content/backend/pkgs/server"
) )
type app struct { type app struct {
conf *config.Config conf *config.Config
logger *logger.Logger
mailer mailer.Mailer mailer mailer.Mailer
db *ent.Client db *ent.Client
server *server.Server server *server.Server

View file

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

View file

@ -4,12 +4,10 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"github.com/hay-kot/content/backend/internal/mocks"
) )
func GetTestHandler(t *testing.T) *BaseController { func GetTestHandler(t *testing.T) *BaseController {
return NewBaseController(mocks.GetStructLogger(), nil) return NewBaseController(nil)
} }
func TestHandlersv1_HandleBase(t *testing.T) { 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": { "/v1/items": {
"get": { "get": {
"security": [ "security": [
@ -1643,9 +1434,6 @@ const docTemplate = `{
"description": { "description": {
"type": "string" "type": "string"
}, },
"groupId": {
"type": "string"
},
"id": { "id": {
"type": "string" "type": "string"
}, },
@ -1680,9 +1468,6 @@ const docTemplate = `{
"description": { "description": {
"type": "string" "type": "string"
}, },
"groupId": {
"type": "string"
},
"id": { "id": {
"type": "string" "type": "string"
}, },
@ -1709,9 +1494,6 @@ const docTemplate = `{
"description": { "description": {
"type": "string" "type": "string"
}, },
"groupId": {
"type": "string"
},
"id": { "id": {
"type": "string" "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": { "types.UserIn": {
"type": "object", "type": "object",
"properties": { "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": { "/v1/items": {
"get": { "get": {
"security": [ "security": [
@ -1635,9 +1426,6 @@
"description": { "description": {
"type": "string" "type": "string"
}, },
"groupId": {
"type": "string"
},
"id": { "id": {
"type": "string" "type": "string"
}, },
@ -1672,9 +1460,6 @@
"description": { "description": {
"type": "string" "type": "string"
}, },
"groupId": {
"type": "string"
},
"id": { "id": {
"type": "string" "type": "string"
}, },
@ -1701,9 +1486,6 @@
"description": { "description": {
"type": "string" "type": "string"
}, },
"groupId": {
"type": "string"
},
"id": { "id": {
"type": "string" "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": { "types.UserIn": {
"type": "object", "type": "object",
"properties": { "properties": {

View file

@ -493,8 +493,6 @@ definitions:
type: string type: string
description: description:
type: string type: string
groupId:
type: string
id: id:
type: string type: string
itemCount: itemCount:
@ -517,8 +515,6 @@ definitions:
type: string type: string
description: description:
type: string type: string
groupId:
type: string
id: id:
type: string type: string
items: items:
@ -536,8 +532,6 @@ definitions:
type: string type: string
description: description:
type: string type: string
groupId:
type: string
id: id:
type: string type: string
name: name:
@ -552,19 +546,6 @@ definitions:
token: token:
type: string type: string
type: object type: object
types.UserCreate:
properties:
email:
type: string
groupID:
type: string
isSuperuser:
type: boolean
name:
type: string
password:
type: string
type: object
types.UserIn: types.UserIn:
properties: properties:
email: email:
@ -616,124 +597,6 @@ paths:
summary: Retrieves the basic information about the API summary: Retrieves the basic information about the API
tags: tags:
- Base - 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: /v1/items:
get: get:
produces: produces:

View file

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

View file

@ -10,8 +10,8 @@ import (
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
"github.com/hay-kot/content/backend/internal/config" "github.com/hay-kot/content/backend/internal/config"
"github.com/hay-kot/content/backend/internal/services" "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/hay-kot/content/backend/pkgs/server"
"github.com/rs/zerolog/log"
) )
func (a *app) setGlobalMiddleware(r *chi.Mux) { 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 // Use struct logger in production for requests, but use
// pretty console logger in development. // pretty console logger in development.
if a.conf.Mode == config.ModeDevelopment { if a.conf.Mode == config.ModeDevelopment {
r.Use(middleware.Logger) r.Use(a.mwSummaryLogger)
} else { } else {
r.Use(a.mwStructLogger) 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) 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{ log.Info().
"id": middleware.GetReqID(r.Context()), Str("id", middleware.GetReqID(r.Context())).
"method": r.Method, Str("url", url).
"url": url, Str("method", r.Method).
"remote": r.RemoteAddr, 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) 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") 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")) 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) 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/register"), v1Handlers.HandleUserRegistration())
r.Post(v1Base("/users/login"), v1Handlers.HandleAuthLogin()) r.Post(v1Base("/users/login"), v1Handlers.HandleAuthLogin())
r.Group(func(r chi.Router) { 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.Put(v1Base("/items/{id}"), v1Handlers.HandleItemUpdate())
r.Delete(v1Base("/items/{id}"), v1Handlers.HandleItemDelete()) 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 return r

View file

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

View file

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

View file

@ -9,12 +9,10 @@ import (
func Test_NewHandlerV1(t *testing.T) { func Test_NewHandlerV1(t *testing.T) {
v1Base := BaseUrlFunc("/testing/v1") v1Base := BaseUrlFunc("/testing/v1")
ctrl := NewControllerV1(mockHandler.log, mockHandler.svc) ctrl := NewControllerV1(mockHandler.svc)
assert.NotNil(t, ctrl) 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"))
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) { func TestMain(m *testing.M) {
// Set Handler Vars // Set Handler Vars
mockHandler.log = mocks.GetStructLogger()
repos, closeDb := mocks.GetEntRepos() repos, closeDb := mocks.GetEntRepos()
mockHandler.svc = mocks.GetMockServices(repos) mockHandler.svc = mocks.GetMockServices(repos)

View file

@ -7,8 +7,8 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/hay-kot/content/backend/internal/services" "github.com/hay-kot/content/backend/internal/services"
"github.com/hay-kot/content/backend/internal/types" "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/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) { func (ctrl *V1Controller) partialParseIdAndUser(w http.ResponseWriter, r *http.Request) (uuid.UUID, *types.UserOut, error) {
uid, err := uuid.Parse(chi.URLParam(r, "id")) uid, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil { if err != nil {
ctrl.log.Debug(err.Error(), logger.Props{ log.Err(err).Msg("failed to parse id")
"details": "failed to convert id to valid UUID",
})
server.RespondError(w, http.StatusBadRequest, err) server.RespondError(w, http.StatusBadRequest, err)
return uuid.Nil, nil, 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/services"
"github.com/hay-kot/content/backend/internal/types" "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/hay-kot/content/backend/pkgs/server"
"github.com/rs/zerolog/log"
) )
// HandleAuthLogin godoc // HandleAuthLogin godoc
@ -28,7 +28,7 @@ func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
server.Respond(w, http.StatusBadRequest, server.Wrap(err)) 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 return
} }
@ -38,9 +38,7 @@ func (ctrl *V1Controller) HandleAuthLogin() http.HandlerFunc {
err := server.Decode(r, loginForm) err := server.Decode(r, loginForm)
if err != nil { if err != nil {
ctrl.log.Error(errors.New("failed to decode login form (JSON)"), logger.Props{ log.Err(err).Msg("failed to decode login form")
"error": err.Error(),
})
server.Respond(w, http.StatusBadRequest, server.Wrap(err)) server.Respond(w, http.StatusBadRequest, server.Wrap(err))
return return
} }

View file

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

View file

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

View file

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

View file

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

View file

@ -31,8 +31,11 @@ require (
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/kr/pretty v0.2.0 // indirect github.com/kr/pretty v0.2.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/zerolog v1.28.0 // indirect
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a // indirect github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a // indirect
github.com/tkrajina/go-reflector v0.5.6 // indirect github.com/tkrajina/go-reflector v0.5.6 // indirect
github.com/zclconf/go-cty v1.11.0 // indirect github.com/zclconf/go-cty v1.11.0 // indirect

View file

@ -11,6 +11,7 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/ardanlabs/conf/v2 v2.2.0 h1:ar1+TYIYAh2Tdeg2DQroh7ruR56/vJR8BDfzDIrXgtk= github.com/ardanlabs/conf/v2 v2.2.0 h1:ar1+TYIYAh2Tdeg2DQroh7ruR56/vJR8BDfzDIrXgtk=
github.com/ardanlabs/conf/v2 v2.2.0/go.mod h1:m37ZKdW9jwMUEhGX36jRNt8VzSQ/HVmSziLZH2p33nY= github.com/ardanlabs/conf/v2 v2.2.0/go.mod h1:m37ZKdW9jwMUEhGX36jRNt8VzSQ/HVmSziLZH2p33nY=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -31,6 +32,7 @@ github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@ -53,6 +55,12 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
@ -60,8 +68,12 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@ -95,6 +107,9 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWI
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261 h1:v6hYoSR9T5oet+pMXwUWkbiVqx/63mlHjefrHmxwfeY=
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

View file

@ -1,11 +0,0 @@
package mocks
import (
"os"
"github.com/hay-kot/content/backend/pkgs/logger"
)
func GetStructLogger() *logger.Logger {
return logger.New(os.Stdout, logger.LevelDebug)
}

View file

@ -1,132 +0,0 @@
package logger
import (
"encoding/json"
"io"
"os"
"runtime/debug"
"sync"
"time"
)
type Level int8
var (
IncludeTrace = false
)
const (
LevelDebug Level = iota
LevelInfo
LevelError
LevelFatal
LevelOff
)
func (l Level) String() string {
switch l {
case LevelDebug:
return "DEBUG"
case LevelInfo:
return "INFO"
case LevelError:
return "ERROR"
case LevelFatal:
return "FATAL"
default:
return ""
}
}
type Props map[string]string
type Logger struct {
out io.Writer
minLevel Level
mu sync.Mutex
}
func New(out io.Writer, minLevel Level) *Logger {
return &Logger{
out: out,
minLevel: minLevel,
}
}
func (l *Logger) Debug(message string, properties map[string]string) {
l.print(LevelDebug, message, properties)
}
func (l *Logger) Info(message string, properties map[string]string) {
l.print(LevelInfo, message, properties)
}
func (l *Logger) Error(err error, properties map[string]string) {
l.print(LevelError, err.Error(), properties)
}
func (l *Logger) Fatal(err error, properties map[string]string) {
l.print(LevelFatal, err.Error(), properties)
os.Exit(1) // For entries at the FATAL level, we also terminate the application.
}
func (l *Logger) print(level Level, message string, properties map[string]string) (int, error) {
// If the severity level of the log entry is below the minimum severity for the
// logger, then return with no further action.
if level < l.minLevel {
return 0, nil
}
// Declare an anonymous struct holding the data for the log entry.
aux := struct {
Level string `json:"level"`
Time string `json:"time"`
Message string `json:"message"`
Properties map[string]string `json:"properties,omitempty"`
Trace string `json:"trace,omitempty"`
}{
Level: level.String(),
Time: time.Now().UTC().Format(time.RFC3339),
Message: message,
Properties: properties,
}
// Include a stack trace for entries at the ERROR and FATAL levels.
dumpTrace := false
if level >= LevelError {
dumpTrace = true
if IncludeTrace {
aux.Trace = string(debug.Stack())
}
}
// Declare a line variable for holding the actual log entry text.
var line []byte
// Marshal the anonymous struct to JSON and store it in the line variable. If there
// was a problem creating the JSON, set the contents of the log entry to be that
// plain-text error message instead.”
line, err := json.Marshal(aux)
if err != nil {
line = []byte(LevelError.String() + ": unable to marshal log message:" + err.Error())
}
// Lock the mutex so that no two writes to the output destination cannot happen
// concurrently. If we don't do this, it's possible that the text for two or more
// log entries will be intermingled in the output.
l.mu.Lock()
defer l.mu.Unlock()
n, err := l.out.Write(line)
if dumpTrace {
n, err = l.out.Write(debug.Stack())
}
return n, err
}
// We also implement a Write() method on our Logger type so that it satisfies the
// io.Writer interface. This writes a log entry at the ERROR level with no additional
// properties.
func (l *Logger) Write(message []byte) (n int, err error) {
return l.print(LevelError, string(message), nil)
}

View file

@ -1,124 +0,0 @@
package logger
import (
"encoding/json"
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func init() {
IncludeTrace = true
}
var lastWrite = []byte{}
type testLogRecorder struct {
t *testing.T
}
func (tlr testLogRecorder) Write(p []byte) (n int, err error) {
lastWrite = p
return len(p), nil
}
type logEntry struct {
Level string `json:"level"`
Message string `json:"message"`
Props *Props `json:"properties"`
}
func (lr *logEntry) Unmarshal(t *testing.T, jbytes []byte) {
err := json.Unmarshal(jbytes, lr)
if err != nil {
t.Error(err)
}
}
func Test_LevelString(t *testing.T) {
assert.Equal(t, "DEBUG", LevelDebug.String())
assert.Equal(t, "INFO", LevelInfo.String())
assert.Equal(t, "ERROR", LevelError.String())
assert.Equal(t, "FATAL", LevelFatal.String())
assert.Equal(t, "", LevelOff.String())
}
func Test_NewLogger(t *testing.T) {
logRecorder := testLogRecorder{t: t}
logger := New(logRecorder, LevelInfo)
assert.NotNil(t, logger)
}
func getTestLogger(t *testing.T, level Level) *Logger {
logRecorder := testLogRecorder{t: t}
logger := New(logRecorder, level)
assert.NotNil(t, logger)
return logger
}
func checkLastEntry(t *testing.T, level Level, message string, props *Props) {
t.Helper()
entry := &logEntry{}
entry.Unmarshal(t, lastWrite)
assert.Equal(t, level.String(), entry.Level)
assert.Equal(t, message, entry.Message)
assert.Equal(t, props, entry.Props)
}
func Test_LoggerDebug(t *testing.T) {
lgr := getTestLogger(t, LevelDebug)
lgr.Debug("Test Debug", Props{"Hello": "World"})
checkLastEntry(t, LevelDebug, "Test Debug", &Props{"Hello": "World"})
lastWrite = []byte{}
}
func Test_LoggerInfo(t *testing.T) {
lgr := getTestLogger(t, LevelInfo)
lgr.Info("Test Info", Props{"Hello": "World"})
checkLastEntry(t, LevelInfo, "Test Info", &Props{"Hello": "World"})
lastWrite = []byte{}
}
func Test_LoggerError(t *testing.T) {
lgr := getTestLogger(t, LevelError)
myerror := errors.New("Test Error")
lgr.Error(myerror, Props{"Hello": "World"})
checkLastEntry(t, LevelError, "Test Error", &Props{"Hello": "World"})
lastWrite = []byte{}
}
func Test_LoggerLevelScale(t *testing.T) {
lgr := getTestLogger(t, LevelInfo)
lastWrite = []byte{}
lgr.Debug("Test Debug", Props{"Hello": "World"})
assert.Equal(t, []byte{}, lastWrite)
lgr = getTestLogger(t, LevelError)
lastWrite = []byte{}
lgr.Info("Test Debug", Props{"Hello": "World"})
lgr.Debug("Test Debug", Props{"Hello": "World"})
assert.Equal(t, []byte{}, lastWrite)
lgr = getTestLogger(t, LevelFatal)
lgr.Info("Test Debug", Props{"Hello": "World"})
lgr.Debug("Test Debug", Props{"Hello": "World"})
lgr.Error(errors.New("Test Error"), Props{"Hello": "World"})
assert.Equal(t, []byte{}, lastWrite)
}