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 { | 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…
	
	Add table
		Add a link
		
	
		Reference in a new issue