✨ Avatar
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +1,2 @@ | |||||||
| /dist | /dist | ||||||
|  | /uploads | ||||||
| @@ -2,6 +2,8 @@ package models | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"github.com/samber/lo" | 	"github.com/samber/lo" | ||||||
|  | 	"github.com/spf13/viper" | ||||||
|  | 	"path/filepath" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"gorm.io/datatypes" | 	"gorm.io/datatypes" | ||||||
| @@ -19,6 +21,7 @@ type Account struct { | |||||||
|  |  | ||||||
| 	Name         string                       `json:"name" gorm:"uniqueIndex"` | 	Name         string                       `json:"name" gorm:"uniqueIndex"` | ||||||
| 	Nick         string                       `json:"nick"` | 	Nick         string                       `json:"nick"` | ||||||
|  | 	Avatar       string                       `json:"avatar"` | ||||||
| 	State        AccountState                 `json:"state"` | 	State        AccountState                 `json:"state"` | ||||||
| 	Profile      AccountProfile               `json:"profile"` | 	Profile      AccountProfile               `json:"profile"` | ||||||
| 	Sessions     []AuthSession                `json:"sessions"` | 	Sessions     []AuthSession                `json:"sessions"` | ||||||
| @@ -39,6 +42,11 @@ func (v Account) GetPrimaryEmail() AccountContact { | |||||||
| 	return val | 	return val | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func (v Account) GetAvatarPath() string { | ||||||
|  | 	basepath := viper.GetString("content") | ||||||
|  | 	return filepath.Join(basepath, v.Avatar) | ||||||
|  | } | ||||||
|  |  | ||||||
| type AccountContactType = int8 | type AccountContactType = int8 | ||||||
|  |  | ||||||
| const ( | const ( | ||||||
|   | |||||||
							
								
								
									
										35
									
								
								pkg/server/avatar_api.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								pkg/server/avatar_api.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | package server | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"code.smartsheep.studio/hydrogen/passport/pkg/database" | ||||||
|  | 	"code.smartsheep.studio/hydrogen/passport/pkg/models" | ||||||
|  | 	"github.com/gofiber/fiber/v2" | ||||||
|  | 	"github.com/google/uuid" | ||||||
|  | 	"github.com/spf13/viper" | ||||||
|  | 	"path/filepath" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func getAvatar(c *fiber.Ctx) error { | ||||||
|  | 	id := c.Params("avatarId") | ||||||
|  | 	basepath := viper.GetString("content") | ||||||
|  |  | ||||||
|  | 	return c.SendFile(filepath.Join(basepath, id)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func setAvatar(c *fiber.Ctx) error { | ||||||
|  | 	user := c.Locals("principal").(models.Account) | ||||||
|  | 	file, err := c.FormFile("avatar") | ||||||
|  | 	if err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	user.Avatar = uuid.NewString() | ||||||
|  |  | ||||||
|  | 	if err := c.SaveFile(file, user.GetAvatarPath()); err != nil { | ||||||
|  | 		return err | ||||||
|  | 	} else { | ||||||
|  | 		database.C.Save(&user) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return c.SendStatus(fiber.StatusOK) | ||||||
|  | } | ||||||
| @@ -57,6 +57,9 @@ func NewServer() { | |||||||
|  |  | ||||||
| 	api := A.Group("/api").Name("API") | 	api := A.Group("/api").Name("API") | ||||||
| 	{ | 	{ | ||||||
|  | 		api.Get("/avatar/:avatarId", getAvatar) | ||||||
|  | 		api.Put("/avatar", auth, setAvatar) | ||||||
|  |  | ||||||
| 		api.Get("/users/me", auth, getUserinfo) | 		api.Get("/users/me", auth, getUserinfo) | ||||||
| 		api.Put("/users/me", auth, editUserinfo) | 		api.Put("/users/me", auth, editUserinfo) | ||||||
| 		api.Get("/users/me/events", auth, getEvents) | 		api.Get("/users/me/events", auth, getEvents) | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ export default function PersonalPage() { | |||||||
|   const [success, setSuccess] = createSignal<null | string>(null); |   const [success, setSuccess] = createSignal<null | string>(null); | ||||||
|   const [loading, setLoading] = createSignal(false); |   const [loading, setLoading] = createSignal(false); | ||||||
|  |  | ||||||
|   async function update(evt: SubmitEvent) { |   async function updateBasis(evt: SubmitEvent) { | ||||||
|     evt.preventDefault(); |     evt.preventDefault(); | ||||||
|  |  | ||||||
|     const data = Object.fromEntries(new FormData(evt.target as HTMLFormElement)); |     const data = Object.fromEntries(new FormData(evt.target as HTMLFormElement)); | ||||||
| @@ -33,6 +33,27 @@ export default function PersonalPage() { | |||||||
|     setLoading(false); |     setLoading(false); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   async function updateAvatar(evt: SubmitEvent) { | ||||||
|  |     evt.preventDefault(); | ||||||
|  |  | ||||||
|  |     setLoading(true); | ||||||
|  |     const data = new FormData(evt.target as HTMLFormElement); | ||||||
|  |     const res = await fetch("/api/avatar", { | ||||||
|  |       method: "PUT", | ||||||
|  |       headers: { "Authorization": `Bearer ${getAtk()}` }, | ||||||
|  |       body: data | ||||||
|  |     }); | ||||||
|  |     if (res.status !== 200) { | ||||||
|  |       setSuccess(null); | ||||||
|  |       setError(await res.text()); | ||||||
|  |     } else { | ||||||
|  |       await readProfiles(); | ||||||
|  |       setSuccess("Your avatar has been update."); | ||||||
|  |       setError(null); | ||||||
|  |     } | ||||||
|  |     setLoading(false); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <div class="max-w-[720px] mx-auto px-5 pt-12"> |     <div class="max-w-[720px] mx-auto px-5 pt-12"> | ||||||
|       <div class="px-5"> |       <div class="px-5"> | ||||||
| @@ -40,36 +61,34 @@ export default function PersonalPage() { | |||||||
|         <p>Joined at {new Date(userinfo?.meta?.created_at).toLocaleString()}</p> |         <p>Joined at {new Date(userinfo?.meta?.created_at).toLocaleString()}</p> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|       <div class="card shadow mt-5"> |       <div id="alerts"> | ||||||
|         <div class="card-body"> |         <Show when={error()}> | ||||||
|  |           <div role="alert" class="alert alert-error mt-3"> | ||||||
|  |             <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" | ||||||
|  |                  viewBox="0 0 24 24"> | ||||||
|  |               <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | ||||||
|  |                     d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /> | ||||||
|  |             </svg> | ||||||
|  |             <span class="capitalize">{error()}</span> | ||||||
|  |           </div> | ||||||
|  |         </Show> | ||||||
|  |  | ||||||
|           <Show when={error()}> |         <Show when={success()}> | ||||||
|             <div id="alerts"> |           <div role="alert" class="alert alert-success mt-3"> | ||||||
|               <div role="alert" class="alert alert-error"> |             <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" | ||||||
|                 <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" |                  viewBox="0 0 24 24"> | ||||||
|                      viewBox="0 0 24 24"> |               <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" | ||||||
|                   <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |                     d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> | ||||||
|                         d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" /> |             </svg> | ||||||
|                 </svg> |             <span class="capitalize">{success()}</span> | ||||||
|                 <span class="capitalize">{error()}</span> |           </div> | ||||||
|               </div> |         </Show> | ||||||
|             </div> |       </div> | ||||||
|           </Show> |  | ||||||
|  |  | ||||||
|           <Show when={success()}> |       <div class="card shadow-xl mt-5"> | ||||||
|             <div id="alerts"> |  | ||||||
|               <div role="alert" class="alert alert-success"> |  | ||||||
|                 <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" |  | ||||||
|                      viewBox="0 0 24 24"> |  | ||||||
|                   <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" |  | ||||||
|                         d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /> |  | ||||||
|                 </svg> |  | ||||||
|                 <span class="capitalize">{success()}</span> |  | ||||||
|               </div> |  | ||||||
|             </div> |  | ||||||
|           </Show> |  | ||||||
|  |  | ||||||
|           <form class="grid grid-cols-1 gap-2" onSubmit={update}> |         <div class="card-body border-b border-base-200"> | ||||||
|  |           <form class="grid grid-cols-1 gap-2" onSubmit={updateBasis}> | ||||||
|             <label class="form-control w-full"> |             <label class="form-control w-full"> | ||||||
|               <div class="label"> |               <div class="label"> | ||||||
|                 <span class="label-text">Username</span> |                 <span class="label-text">Username</span> | ||||||
| @@ -107,7 +126,30 @@ export default function PersonalPage() { | |||||||
|                        placeholder="Type here" class="input input-bordered w-full" /> |                        placeholder="Type here" class="input input-bordered w-full" /> | ||||||
|               </label> |               </label> | ||||||
|             </div> |             </div> | ||||||
|             <button type="submit" class="btn btn-primary btn-block mt-5" disabled={loading()}> |  | ||||||
|  |             <div> | ||||||
|  |               <button type="submit" class="btn btn-primary mt-5" disabled={loading()}> | ||||||
|  |                 <Show when={loading()} fallback={"Save changes"}> | ||||||
|  |                   <span class="loading loading-spinner"></span> | ||||||
|  |                 </Show> | ||||||
|  |               </button> | ||||||
|  |             </div> | ||||||
|  |           </form> | ||||||
|  |         </div> | ||||||
|  |  | ||||||
|  |         <div class="card-body"> | ||||||
|  |           <form onSubmit={updateAvatar}> | ||||||
|  |             <label class="form-control w-full"> | ||||||
|  |               <div class="label"> | ||||||
|  |                 <span class="label-text">Pick an avatar</span> | ||||||
|  |               </div> | ||||||
|  |               <input type="file" name="avatar" accept="image/*" class="file-input file-input-bordered w-full" /> | ||||||
|  |               <div class="label"> | ||||||
|  |                 <span class="label-text-alt">Will took some time to apply to entire site</span> | ||||||
|  |               </div> | ||||||
|  |             </label> | ||||||
|  |  | ||||||
|  |             <button type="submit" class="btn btn-primary mt-5" disabled={loading()}> | ||||||
|               <Show when={loading()} fallback={"Save changes"}> |               <Show when={loading()} fallback={"Save changes"}> | ||||||
|                 <span class="loading loading-spinner"></span> |                 <span class="loading loading-spinner"></span> | ||||||
|               </Show> |               </Show> | ||||||
|   | |||||||
| @@ -7,6 +7,8 @@ bind = "0.0.0.0:8444" | |||||||
| domain = "id.smartsheep.studio" | domain = "id.smartsheep.studio" | ||||||
| secret = "LtTjzAGFLshwXhN4ZD4nG5KlMv1MWcsvfv03TSZYnT1VhiAnLIZFTnHUwR0XhGgi" | secret = "LtTjzAGFLshwXhN4ZD4nG5KlMv1MWcsvfv03TSZYnT1VhiAnLIZFTnHUwR0XhGgi" | ||||||
|  |  | ||||||
|  | content = "uploads" | ||||||
|  |  | ||||||
| use_registration_magic_token = true | use_registration_magic_token = true | ||||||
|  |  | ||||||
| [mailer] | [mailer] | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user