diff --git a/backend/app/api/middleware.go b/backend/app/api/middleware.go index c062ec2..aec4917 100644 --- a/backend/app/api/middleware.go +++ b/backend/app/api/middleware.go @@ -87,8 +87,21 @@ func mwStripTrailingSlash(next http.Handler) http.Handler { }) } +type StatusRecorder struct { + http.ResponseWriter + Status int +} + +func (r *StatusRecorder) WriteHeader(status int) { + r.Status = status + r.ResponseWriter.WriteHeader(status) +} + func (a *app) mwStructLogger(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + record := &StatusRecorder{ResponseWriter: w, Status: http.StatusOK} + next.ServeHTTP(record, r) + scheme := "http" if r.TLS != nil { scheme = "https" @@ -101,25 +114,35 @@ func (a *app) mwStructLogger(next http.Handler) http.Handler { Str("url", url). Str("method", r.Method). Str("remote_addr", r.RemoteAddr). - Msgf("[%s] %s", r.Method, url) - next.ServeHTTP(w, r) + Int("status", record.Status). + Msg(url) }) } func (a *app) mwSummaryLogger(next http.Handler) http.Handler { - bold := func(s string) string { - return "\033[1m" + s + "\033[0m" - } + bold := func(s string) string { return "\033[1m" + s + "\033[0m" } + orange := func(s string) string { return "\033[33m" + s + "\033[0m" } + aqua := func(s string) string { return "\033[36m" + s + "\033[0m" } + red := func(s string) string { return "\033[31m" + s + "\033[0m" } + green := func(s string) string { return "\033[32m" + 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" + fmtCode := func(code int) string { + switch { + case code >= 500: + return red(fmt.Sprintf("%d", code)) + case code >= 400: + return orange(fmt.Sprintf("%d", code)) + case code >= 300: + return aqua(fmt.Sprintf("%d", code)) + default: + return green(fmt.Sprintf("%d", code)) + } } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + record := &StatusRecorder{ResponseWriter: w, Status: http.StatusOK} + next.ServeHTTP(record, r) // Blocks until the next handler returns. + scheme := "http" if r.TLS != nil { scheme = "https" @@ -127,7 +150,11 @@ func (a *app) mwSummaryLogger(next http.Handler) http.Handler { 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) + log.Info(). + Msgf("%s %s %s", + bold(orange(""+r.Method+"")), + aqua(url), + bold(fmtCode(record.Status)), + ) }) } diff --git a/backend/internal/services/service_user.go b/backend/internal/services/service_user.go index 80c7d10..b5ced21 100644 --- a/backend/internal/services/service_user.go +++ b/backend/internal/services/service_user.go @@ -23,6 +23,8 @@ type UserService struct { repos *repo.AllRepos } +// RegisterUser creates a new user and group in the data with the provided data. It also bootstraps the user's group +// with default Labels and Locations. func (svc *UserService) RegisterUser(ctx context.Context, data types.UserRegistration) (*types.UserOut, error) { group, err := svc.repos.Groups.Create(ctx, data.GroupName) if err != nil { @@ -38,7 +40,26 @@ func (svc *UserService) RegisterUser(ctx context.Context, data types.UserRegistr GroupID: group.ID, } - return mappers.ToOutUser(svc.repos.Users.Create(ctx, usrCreate)) + usr, err := svc.repos.Users.Create(ctx, usrCreate) + if err != nil { + return &types.UserOut{}, err + } + + for _, label := range defaultLabels() { + _, err := svc.repos.Labels.Create(ctx, group.ID, label) + if err != nil { + return &types.UserOut{}, err + } + } + + for _, location := range defaultLocations() { + _, err := svc.repos.Locations.Create(ctx, group.ID, location) + if err != nil { + return &types.UserOut{}, err + } + } + + return mappers.ToOutUser(usr, nil) } // GetSelf returns the user that is currently logged in based of the token provided within diff --git a/backend/internal/services/service_user_defaults.go b/backend/internal/services/service_user_defaults.go new file mode 100644 index 0000000..185c782 --- /dev/null +++ b/backend/internal/services/service_user_defaults.go @@ -0,0 +1,55 @@ +package services + +import "github.com/hay-kot/content/backend/internal/types" + +func defaultLocations() []types.LocationCreate { + return []types.LocationCreate{ + { + Name: "Living Room", + }, + { + Name: "Garage", + }, + { + Name: "Kitchen", + }, + { + Name: "Bedroom", + }, + { + Name: "Bathroom", + }, + { + Name: "Office", + }, + { + Name: "Attic", + }, + { + Name: "Basement", + }, + } +} + +func defaultLabels() []types.LabelCreate { + return []types.LabelCreate{ + { + Name: "Appliances", + }, + { + Name: "IOT", + }, + { + Name: "Electronics", + }, + { + Name: "Servers", + }, + { + Name: "General", + }, + { + Name: "Important", + }, + } +} diff --git a/frontend/composables/use-api.ts b/frontend/composables/use-api.ts index 3e63488..d80dec7 100644 --- a/frontend/composables/use-api.ts +++ b/frontend/composables/use-api.ts @@ -1,23 +1,31 @@ -import { PublicApi } from "~~/lib/api/public"; -import { UserApi } from "~~/lib/api/user"; -import { Requests } from "~~/lib/requests"; -import { useAuthStore } from "~~/stores/auth"; +import { PublicApi } from '~~/lib/api/public'; +import { UserApi } from '~~/lib/api/user'; +import { Requests } from '~~/lib/requests'; +import { useAuthStore } from '~~/stores/auth'; -async function ApiDebugger(r: Response) { +function ApiDebugger(r: Response) { console.table({ - "Request Url": r.url, - "Response Status": r.status, - "Response Status Text": r.statusText, + 'Request Url': r.url, + 'Response Status': r.status, + 'Response Status Text': r.statusText, }); } export function usePublicApi(): PublicApi { - const requests = new Requests("", "", {}, ApiDebugger); + const requests = new Requests('', '', {}); return new PublicApi(requests); } export function useUserApi(): UserApi { const authStore = useAuthStore(); - const requests = new Requests("", () => authStore.token, {}, ApiDebugger); + + const requests = new Requests('', () => authStore.token, {}); + requests.addResponseInterceptor(ApiDebugger); + requests.addResponseInterceptor(r => { + if (r.status === 401) { + authStore.clearSession(); + } + }); + return new UserApi(requests); } diff --git a/frontend/lib/requests/requests.ts b/frontend/lib/requests/requests.ts index 2d2d9ca..3627d93 100644 --- a/frontend/lib/requests/requests.ts +++ b/frontend/lib/requests/requests.ts @@ -5,6 +5,9 @@ export enum Method { DELETE = 'DELETE', } +export type RequestInterceptor = (r: Response) => void; +export type ResponseInterceptor = (r: Response) => void; + export interface TResponse { status: number; error: boolean; @@ -16,22 +19,24 @@ export class Requests { private baseUrl: string; private token: () => string; private headers: Record = {}; - private logger?: (response: Response) => void; + private responseInterceptors: ResponseInterceptor[] = []; + + addResponseInterceptor(interceptor: ResponseInterceptor) { + this.responseInterceptors.push(interceptor); + } + + private callResponseInterceptors(response: Response) { + this.responseInterceptors.forEach(i => i(response)); + } private url(rest: string): string { return this.baseUrl + rest; } - constructor( - baseUrl: string, - token: string | (() => string) = '', - headers: Record = {}, - logger?: (response: Response) => void - ) { + constructor(baseUrl: string, token: string | (() => string) = '', headers: Record = {}) { this.baseUrl = baseUrl; this.token = typeof token === 'string' ? () => token : token; this.headers = headers; - this.logger = logger; } public get(url: string): Promise> { @@ -73,10 +78,7 @@ export class Requests { } const response = await fetch(this.url(url), args); - - if (this.logger) { - this.logger(response); - } + this.callResponseInterceptors(response); const data: T = await (async () => { if (response.status === 204) { diff --git a/frontend/pages/home.vue b/frontend/pages/home.vue index 9673985..640249c 100644 --- a/frontend/pages/home.vue +++ b/frontend/pages/home.vue @@ -44,7 +44,7 @@