forked from mirrors/homebox
inject defaults + cleanup
This commit is contained in:
parent
9b46ea7874
commit
5f589f95b8
8 changed files with 212 additions and 85 deletions
|
@ -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 {
|
func (a *app) mwStructLogger(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
record := &StatusRecorder{ResponseWriter: w, Status: http.StatusOK}
|
||||||
|
next.ServeHTTP(record, r)
|
||||||
|
|
||||||
scheme := "http"
|
scheme := "http"
|
||||||
if r.TLS != nil {
|
if r.TLS != nil {
|
||||||
scheme = "https"
|
scheme = "https"
|
||||||
|
@ -101,25 +114,35 @@ func (a *app) mwStructLogger(next http.Handler) http.Handler {
|
||||||
Str("url", url).
|
Str("url", url).
|
||||||
Str("method", r.Method).
|
Str("method", r.Method).
|
||||||
Str("remote_addr", r.RemoteAddr).
|
Str("remote_addr", r.RemoteAddr).
|
||||||
Msgf("[%s] %s", r.Method, url)
|
Int("status", record.Status).
|
||||||
next.ServeHTTP(w, r)
|
Msg(url)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *app) mwSummaryLogger(next http.Handler) http.Handler {
|
func (a *app) mwSummaryLogger(next http.Handler) http.Handler {
|
||||||
bold := func(s string) string {
|
bold := func(s string) string { return "\033[1m" + s + "\033[0m" }
|
||||||
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 {
|
fmtCode := func(code int) string {
|
||||||
return "\033[35m" + s + "\033[0m"
|
switch {
|
||||||
}
|
case code >= 500:
|
||||||
|
return red(fmt.Sprintf("%d", code))
|
||||||
aqua := func(s string) string {
|
case code >= 400:
|
||||||
return "\033[36m" + s + "\033[0m"
|
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) {
|
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"
|
scheme := "http"
|
||||||
if r.TLS != nil {
|
if r.TLS != nil {
|
||||||
scheme = "https"
|
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)
|
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))
|
log.Info().
|
||||||
next.ServeHTTP(w, r)
|
Msgf("%s %s %s",
|
||||||
|
bold(orange(""+r.Method+"")),
|
||||||
|
aqua(url),
|
||||||
|
bold(fmtCode(record.Status)),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ type UserService struct {
|
||||||
repos *repo.AllRepos
|
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) {
|
func (svc *UserService) RegisterUser(ctx context.Context, data types.UserRegistration) (*types.UserOut, error) {
|
||||||
group, err := svc.repos.Groups.Create(ctx, data.GroupName)
|
group, err := svc.repos.Groups.Create(ctx, data.GroupName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -38,7 +40,26 @@ func (svc *UserService) RegisterUser(ctx context.Context, data types.UserRegistr
|
||||||
GroupID: group.ID,
|
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
|
// GetSelf returns the user that is currently logged in based of the token provided within
|
||||||
|
|
55
backend/internal/services/service_user_defaults.go
Normal file
55
backend/internal/services/service_user_defaults.go
Normal file
|
@ -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",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,23 +1,31 @@
|
||||||
import { PublicApi } from "~~/lib/api/public";
|
import { PublicApi } from '~~/lib/api/public';
|
||||||
import { UserApi } from "~~/lib/api/user";
|
import { UserApi } from '~~/lib/api/user';
|
||||||
import { Requests } from "~~/lib/requests";
|
import { Requests } from '~~/lib/requests';
|
||||||
import { useAuthStore } from "~~/stores/auth";
|
import { useAuthStore } from '~~/stores/auth';
|
||||||
|
|
||||||
async function ApiDebugger(r: Response) {
|
function ApiDebugger(r: Response) {
|
||||||
console.table({
|
console.table({
|
||||||
"Request Url": r.url,
|
'Request Url': r.url,
|
||||||
"Response Status": r.status,
|
'Response Status': r.status,
|
||||||
"Response Status Text": r.statusText,
|
'Response Status Text': r.statusText,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function usePublicApi(): PublicApi {
|
export function usePublicApi(): PublicApi {
|
||||||
const requests = new Requests("", "", {}, ApiDebugger);
|
const requests = new Requests('', '', {});
|
||||||
return new PublicApi(requests);
|
return new PublicApi(requests);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useUserApi(): UserApi {
|
export function useUserApi(): UserApi {
|
||||||
const authStore = useAuthStore();
|
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);
|
return new UserApi(requests);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@ export enum Method {
|
||||||
DELETE = 'DELETE',
|
DELETE = 'DELETE',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type RequestInterceptor = (r: Response) => void;
|
||||||
|
export type ResponseInterceptor = (r: Response) => void;
|
||||||
|
|
||||||
export interface TResponse<T> {
|
export interface TResponse<T> {
|
||||||
status: number;
|
status: number;
|
||||||
error: boolean;
|
error: boolean;
|
||||||
|
@ -16,22 +19,24 @@ export class Requests {
|
||||||
private baseUrl: string;
|
private baseUrl: string;
|
||||||
private token: () => string;
|
private token: () => string;
|
||||||
private headers: Record<string, string> = {};
|
private headers: Record<string, string> = {};
|
||||||
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 {
|
private url(rest: string): string {
|
||||||
return this.baseUrl + rest;
|
return this.baseUrl + rest;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(baseUrl: string, token: string | (() => string) = '', headers: Record<string, string> = {}) {
|
||||||
baseUrl: string,
|
|
||||||
token: string | (() => string) = '',
|
|
||||||
headers: Record<string, string> = {},
|
|
||||||
logger?: (response: Response) => void
|
|
||||||
) {
|
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
this.token = typeof token === 'string' ? () => token : token;
|
this.token = typeof token === 'string' ? () => token : token;
|
||||||
this.headers = headers;
|
this.headers = headers;
|
||||||
this.logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get<T>(url: string): Promise<TResponse<T>> {
|
public get<T>(url: string): Promise<TResponse<T>> {
|
||||||
|
@ -73,10 +78,7 @@ export class Requests {
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(this.url(url), args);
|
const response = await fetch(this.url(url), args);
|
||||||
|
this.callResponseInterceptors(response);
|
||||||
if (this.logger) {
|
|
||||||
this.logger(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: T = await (async () => {
|
const data: T = await (async () => {
|
||||||
if (response.status === 204) {
|
if (response.status === 204) {
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BaseContainer class="space-y-16">
|
<BaseContainer class="space-y-16 pb-16">
|
||||||
<section aria-labelledby="profile-overview-title" class="mt-8">
|
<section aria-labelledby="profile-overview-title" class="mt-8">
|
||||||
<div class="overflow-hidden rounded-lg bg-white shadow">
|
<div class="overflow-hidden rounded-lg bg-white shadow">
|
||||||
<h2 class="sr-only" id="profile-overview-title">Profile Overview</h2>
|
<h2 class="sr-only" id="profile-overview-title">Profile Overview</h2>
|
||||||
|
|
|
@ -47,7 +47,7 @@
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
// Print Values of registerFields
|
// Print Values of registerFields
|
||||||
|
|
||||||
const { data, error } = await api.register({
|
const { error } = await api.register({
|
||||||
user: {
|
user: {
|
||||||
name: registerFields[0].value,
|
name: registerFields[0].value,
|
||||||
email: registerFields[1].value,
|
email: registerFields[1].value,
|
||||||
|
@ -58,11 +58,14 @@
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
toast.error('Problem registering user');
|
toast.error('Problem registering user');
|
||||||
} else {
|
return;
|
||||||
toast.success('User registered');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toast.success('User registered');
|
||||||
|
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
|
loginFields[0].value = registerFields[1].value;
|
||||||
|
registerForm.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginFields = [
|
const loginFields = [
|
||||||
|
@ -116,55 +119,57 @@
|
||||||
<p class="ml-1 text-lg text-base-content/50">Track, Organize, and Manage your Shit.</p>
|
<p class="ml-1 text-lg text-base-content/50">Track, Organize, and Manage your Shit.</p>
|
||||||
</header>
|
</header>
|
||||||
<div class="grid p-6 sm:place-items-center min-h-[50vh]">
|
<div class="grid p-6 sm:place-items-center min-h-[50vh]">
|
||||||
<Transition name="slide-fade">
|
<div>
|
||||||
<form v-if="registerForm" @submit.prevent="registerUser">
|
<Transition name="slide-fade">
|
||||||
<div class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl">
|
<form v-if="registerForm" @submit.prevent="registerUser">
|
||||||
<div class="card-body">
|
<div class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl">
|
||||||
<h2 class="card-title">Register</h2>
|
<div class="card-body">
|
||||||
<TextField
|
<h2 class="card-title">Register</h2>
|
||||||
v-for="field in registerFields"
|
<TextField
|
||||||
v-model="field.value"
|
v-for="field in registerFields"
|
||||||
:label="field.label"
|
v-model="field.value"
|
||||||
:key="field.label"
|
:label="field.label"
|
||||||
:type="field.type"
|
:key="field.label"
|
||||||
/>
|
:type="field.type"
|
||||||
<div class="card-actions justify-end">
|
/>
|
||||||
<button
|
<div class="card-actions justify-end">
|
||||||
type="submit"
|
<button
|
||||||
class="btn btn-primary mt-2"
|
type="submit"
|
||||||
:class="loading ? 'loading' : ''"
|
class="btn btn-primary mt-2"
|
||||||
:disabled="loading"
|
:class="loading ? 'loading' : ''"
|
||||||
>
|
:disabled="loading"
|
||||||
Register
|
>
|
||||||
</button>
|
Register
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
<form v-else @submit.prevent="login">
|
||||||
<form v-else @submit.prevent="login">
|
<div class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl">
|
||||||
<div class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl">
|
<div class="card-body">
|
||||||
<div class="card-body">
|
<h2 class="card-title">Login</h2>
|
||||||
<h2 class="card-title">Login</h2>
|
<TextField
|
||||||
<TextField
|
v-for="field in loginFields"
|
||||||
v-for="field in loginFields"
|
v-model="field.value"
|
||||||
v-model="field.value"
|
:label="field.label"
|
||||||
:label="field.label"
|
:key="field.label"
|
||||||
:key="field.label"
|
:type="field.type"
|
||||||
:type="field.type"
|
/>
|
||||||
/>
|
<div class="card-actions justify-end mt-2">
|
||||||
<div class="card-actions justify-end mt-2">
|
<button type="submit" class="btn btn-primary" :class="loading ? 'loading' : ''" :disabled="loading">
|
||||||
<button type="submit" class="btn btn-primary" :class="loading ? 'loading' : ''" :disabled="loading">
|
Login
|
||||||
Login
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
</form>
|
</Transition>
|
||||||
</Transition>
|
<div class="text-center mt-4">
|
||||||
<div class="text-center mt-2">
|
<button @click="toggleLogin" class="text-primary-content text-lg">
|
||||||
<button @click="toggleLogin">
|
{{ registerForm ? 'Already a User? Login' : 'Not a User? Register' }}
|
||||||
{{ registerForm ? 'Already a User? Login' : 'Not a User? Register' }}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="min-w-full absolute bottom-0 z-[-1]">
|
<div class="min-w-full absolute bottom-0 z-[-1]">
|
||||||
|
|
|
@ -33,5 +33,14 @@ export const useAuthStore = defineStore('auth', {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* clearSession is used when the user cannot be logged out via the API and
|
||||||
|
* must clear it's local session, usually when a 401 is received.
|
||||||
|
*/
|
||||||
|
clearSession() {
|
||||||
|
this.token = '';
|
||||||
|
this.expires = '';
|
||||||
|
navigateTo('/');
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue