mirror of
				https://github.com/hay-kot/homebox.git
				synced 2025-10-27 03:16:43 +00:00 
			
		
		
		
	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 { | ||||
| 	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)), | ||||
| 			) | ||||
| 	}) | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
							
								
								
									
										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 { 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); | ||||
| } | ||||
|  |  | |||
|  | @ -5,6 +5,9 @@ export enum Method { | |||
|   DELETE = 'DELETE', | ||||
| } | ||||
| 
 | ||||
| export type RequestInterceptor = (r: Response) => void; | ||||
| export type ResponseInterceptor = (r: Response) => void; | ||||
| 
 | ||||
| export interface TResponse<T> { | ||||
|   status: number; | ||||
|   error: boolean; | ||||
|  | @ -16,22 +19,24 @@ export class Requests { | |||
|   private baseUrl: string; | ||||
|   private token: () => 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 { | ||||
|     return this.baseUrl + rest; | ||||
|   } | ||||
| 
 | ||||
|   constructor( | ||||
|     baseUrl: string, | ||||
|     token: string | (() => string) = '', | ||||
|     headers: Record<string, string> = {}, | ||||
|     logger?: (response: Response) => void | ||||
|   ) { | ||||
|   constructor(baseUrl: string, token: string | (() => string) = '', headers: Record<string, string> = {}) { | ||||
|     this.baseUrl = baseUrl; | ||||
|     this.token = typeof token === 'string' ? () => token : token; | ||||
|     this.headers = headers; | ||||
|     this.logger = logger; | ||||
|   } | ||||
| 
 | ||||
|   public get<T>(url: string): Promise<TResponse<T>> { | ||||
|  | @ -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) { | ||||
|  |  | |||
|  | @ -44,7 +44,7 @@ | |||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <BaseContainer class="space-y-16"> | ||||
|   <BaseContainer class="space-y-16 pb-16"> | ||||
|     <section aria-labelledby="profile-overview-title" class="mt-8"> | ||||
|       <div class="overflow-hidden rounded-lg bg-white shadow"> | ||||
|         <h2 class="sr-only" id="profile-overview-title">Profile Overview</h2> | ||||
|  |  | |||
|  | @ -47,7 +47,7 @@ | |||
|     loading.value = true; | ||||
|     // Print Values of registerFields | ||||
| 
 | ||||
|     const { data, error } = await api.register({ | ||||
|     const { error } = await api.register({ | ||||
|       user: { | ||||
|         name: registerFields[0].value, | ||||
|         email: registerFields[1].value, | ||||
|  | @ -58,11 +58,14 @@ | |||
| 
 | ||||
|     if (error) { | ||||
|       toast.error('Problem registering user'); | ||||
|     } else { | ||||
|       toast.success('User registered'); | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     toast.success('User registered'); | ||||
| 
 | ||||
|     loading.value = false; | ||||
|     loginFields[0].value = registerFields[1].value; | ||||
|     registerForm.value = false; | ||||
|   } | ||||
| 
 | ||||
|   const loginFields = [ | ||||
|  | @ -116,55 +119,57 @@ | |||
|       <p class="ml-1 text-lg text-base-content/50">Track, Organize, and Manage your Shit.</p> | ||||
|     </header> | ||||
|     <div class="grid p-6 sm:place-items-center min-h-[50vh]"> | ||||
|       <Transition name="slide-fade"> | ||||
|         <form v-if="registerForm" @submit.prevent="registerUser"> | ||||
|           <div class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl"> | ||||
|             <div class="card-body"> | ||||
|               <h2 class="card-title">Register</h2> | ||||
|               <TextField | ||||
|                 v-for="field in registerFields" | ||||
|                 v-model="field.value" | ||||
|                 :label="field.label" | ||||
|                 :key="field.label" | ||||
|                 :type="field.type" | ||||
|               /> | ||||
|               <div class="card-actions justify-end"> | ||||
|                 <button | ||||
|                   type="submit" | ||||
|                   class="btn btn-primary mt-2" | ||||
|                   :class="loading ? 'loading' : ''" | ||||
|                   :disabled="loading" | ||||
|                 > | ||||
|                   Register | ||||
|                 </button> | ||||
|       <div> | ||||
|         <Transition name="slide-fade"> | ||||
|           <form v-if="registerForm" @submit.prevent="registerUser"> | ||||
|             <div class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl"> | ||||
|               <div class="card-body"> | ||||
|                 <h2 class="card-title">Register</h2> | ||||
|                 <TextField | ||||
|                   v-for="field in registerFields" | ||||
|                   v-model="field.value" | ||||
|                   :label="field.label" | ||||
|                   :key="field.label" | ||||
|                   :type="field.type" | ||||
|                 /> | ||||
|                 <div class="card-actions justify-end"> | ||||
|                   <button | ||||
|                     type="submit" | ||||
|                     class="btn btn-primary mt-2" | ||||
|                     :class="loading ? 'loading' : ''" | ||||
|                     :disabled="loading" | ||||
|                   > | ||||
|                     Register | ||||
|                   </button> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </form> | ||||
|         <form v-else @submit.prevent="login"> | ||||
|           <div class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl"> | ||||
|             <div class="card-body"> | ||||
|               <h2 class="card-title">Login</h2> | ||||
|               <TextField | ||||
|                 v-for="field in loginFields" | ||||
|                 v-model="field.value" | ||||
|                 :label="field.label" | ||||
|                 :key="field.label" | ||||
|                 :type="field.type" | ||||
|               /> | ||||
|               <div class="card-actions justify-end mt-2"> | ||||
|                 <button type="submit" class="btn btn-primary" :class="loading ? 'loading' : ''" :disabled="loading"> | ||||
|                   Login | ||||
|                 </button> | ||||
|           </form> | ||||
|           <form v-else @submit.prevent="login"> | ||||
|             <div class="card w-max-[500px] md:w-[500px] bg-base-100 shadow-xl"> | ||||
|               <div class="card-body"> | ||||
|                 <h2 class="card-title">Login</h2> | ||||
|                 <TextField | ||||
|                   v-for="field in loginFields" | ||||
|                   v-model="field.value" | ||||
|                   :label="field.label" | ||||
|                   :key="field.label" | ||||
|                   :type="field.type" | ||||
|                 /> | ||||
|                 <div class="card-actions justify-end mt-2"> | ||||
|                   <button type="submit" class="btn btn-primary" :class="loading ? 'loading' : ''" :disabled="loading"> | ||||
|                     Login | ||||
|                   </button> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|           </div> | ||||
|         </form> | ||||
|       </Transition> | ||||
|       <div class="text-center mt-2"> | ||||
|         <button @click="toggleLogin"> | ||||
|           {{ registerForm ? 'Already a User? Login' : 'Not a User? Register' }} | ||||
|         </button> | ||||
|           </form> | ||||
|         </Transition> | ||||
|         <div class="text-center mt-4"> | ||||
|           <button @click="toggleLogin" class="text-primary-content text-lg"> | ||||
|             {{ registerForm ? 'Already a User? Login' : 'Not a User? Register' }} | ||||
|           </button> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class="min-w-full absolute bottom-0 z-[-1]"> | ||||
|  |  | |||
|  | @ -33,5 +33,14 @@ export const useAuthStore = defineStore('auth', { | |||
| 
 | ||||
|       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…
	
	Add table
		Add a link
		
	
		Reference in a new issue