From d4e437624a8696db7d4ab948dbdcef4bdfeafcd1 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 31 Jan 2024 21:16:54 +0800 Subject: [PATCH] :sparkles: User Center --- pkg/server/accounts_api.go | 51 +++++++++-- pkg/server/challanges_api.go | 8 +- pkg/server/startup.go | 7 +- pkg/server/utils.go | 2 +- pkg/view/src/index.tsx | 1 + pkg/view/src/layouts/shared/Navbar.tsx | 5 +- pkg/view/src/pages/auth/login.tsx | 4 +- pkg/view/src/pages/dashboard.tsx | 4 +- pkg/view/src/pages/personalise.tsx | 121 +++++++++++++++++++++++++ pkg/view/src/stores/userinfo.tsx | 8 +- 10 files changed, 188 insertions(+), 23 deletions(-) create mode 100644 pkg/view/src/pages/personalise.tsx diff --git a/pkg/server/accounts_api.go b/pkg/server/accounts_api.go index 167f423..69029e1 100644 --- a/pkg/server/accounts_api.go +++ b/pkg/server/accounts_api.go @@ -9,9 +9,10 @@ import ( jsoniter "github.com/json-iterator/go" "github.com/spf13/viper" "strconv" + "time" ) -func getPrincipal(c *fiber.Ctx) error { +func getUserinfo(c *fiber.Ctx) error { user := c.Locals("principal").(models.Account) var data models.Account @@ -68,6 +69,44 @@ func getEvents(c *fiber.Ctx) error { }) } +func editUserinfo(c *fiber.Ctx) error { + user := c.Locals("principal").(models.Account) + + var data struct { + Nick string `json:"nick" validate:"required,min=4,max=24"` + FirstName string `json:"first_name"` + MiddleName string `json:"middle_name"` + LastName string `json:"last_name"` + Birthday time.Time `json:"birthday"` + } + + if err := BindAndValidate(c, &data); err != nil { + return err + } + + var account models.Account + if err := database.C. + Where(&models.Account{BaseModel: models.BaseModel{ID: user.ID}}). + Preload("Profile"). + First(&account).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + account.Nick = data.Nick + account.Profile.FirstName = data.FirstName + account.Profile.MiddleName = data.MiddleName + account.Profile.LastName = data.LastName + account.Profile.Birthday = &data.Birthday + + if err := database.C.Save(&account).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } else if err := database.C.Save(&account.Profile).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + return c.SendStatus(fiber.StatusOK) +} + func killSession(c *fiber.Ctx) error { user := c.Locals("principal").(models.Account) id, _ := c.ParamsInt("sessionId", 0) @@ -84,10 +123,10 @@ func killSession(c *fiber.Ctx) error { func doRegister(c *fiber.Ctx) error { var data struct { - Name string `json:"name"` - Nick string `json:"nick"` - Email string `json:"email"` - Password string `json:"password"` + Name string `json:"name" validate:"required,lowercase,alphanum,min=4,max=16"` + Nick string `json:"nick" validate:"required,min=4,max=24"` + Email string `json:"email" validate:"required,email"` + Password string `json:"password" validate:"required,min=4,max=32"` MagicToken string `json:"magic_token"` } @@ -117,7 +156,7 @@ func doRegister(c *fiber.Ctx) error { func doRegisterConfirm(c *fiber.Ctx) error { var data struct { - Code string `json:"code"` + Code string `json:"code" validate:"required"` } if err := BindAndValidate(c, &data); err != nil { diff --git a/pkg/server/challanges_api.go b/pkg/server/challanges_api.go index e4cc381..d028ab5 100644 --- a/pkg/server/challanges_api.go +++ b/pkg/server/challanges_api.go @@ -11,7 +11,7 @@ import ( func startChallenge(c *fiber.Ctx) error { var data struct { - ID string `json:"id"` + ID string `json:"id" validate:"required"` } if err := BindAndValidate(c, &data); err != nil { @@ -42,9 +42,9 @@ func startChallenge(c *fiber.Ctx) error { func doChallenge(c *fiber.Ctx) error { var data struct { - ChallengeID uint `json:"challenge_id"` - FactorID uint `json:"factor_id"` - Secret string `json:"secret"` + ChallengeID uint `json:"challenge_id" validate:"required"` + FactorID uint `json:"factor_id" validate:"required"` + Secret string `json:"secret" validate:"required"` } if err := BindAndValidate(c, &data); err != nil { diff --git a/pkg/server/startup.go b/pkg/server/startup.go index 972c9d2..93479f4 100644 --- a/pkg/server/startup.go +++ b/pkg/server/startup.go @@ -22,8 +22,8 @@ func NewServer() { A = fiber.New(fiber.Config{ DisableStartupMessage: true, EnableIPValidation: true, - ServerHeader: "passport", - AppName: "passport", + ServerHeader: "Hydrogen.Passport", + AppName: "Hydrogen.Passport", ProxyHeader: fiber.HeaderXForwardedFor, JSONEncoder: jsoniter.ConfigCompatibleWithStandardLibrary.Marshal, JSONDecoder: jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal, @@ -57,7 +57,8 @@ func NewServer() { api := A.Group("/api").Name("API") { - api.Get("/users/me", auth, getPrincipal) + api.Get("/users/me", auth, getUserinfo) + api.Put("/users/me", auth, editUserinfo) api.Get("/users/me/events", auth, getEvents) api.Delete("/users/me/sessions/:sessionId", auth, killSession) diff --git a/pkg/server/utils.go b/pkg/server/utils.go index f857afd..8502c7f 100644 --- a/pkg/server/utils.go +++ b/pkg/server/utils.go @@ -5,7 +5,7 @@ import ( "github.com/gofiber/fiber/v2" ) -var validation = validator.New() +var validation = validator.New(validator.WithRequiredStructEnabled()) func BindAndValidate(c *fiber.Ctx, out any) error { if err := c.BodyParser(out); err != nil { diff --git a/pkg/view/src/index.tsx b/pkg/view/src/index.tsx index 6d26901..3af80ee 100644 --- a/pkg/view/src/index.tsx +++ b/pkg/view/src/index.tsx @@ -19,6 +19,7 @@ render(() => ( import("./pages/dashboard.tsx"))} /> + import("./pages/personalise.tsx"))} /> import("./pages/auth/login.tsx"))} /> import("./pages/auth/register.tsx"))} /> import("./pages/auth/connect.tsx"))} /> diff --git a/pkg/view/src/layouts/shared/Navbar.tsx b/pkg/view/src/layouts/shared/Navbar.tsx index 95268f0..30b490b 100644 --- a/pkg/view/src/layouts/shared/Navbar.tsx +++ b/pkg/view/src/layouts/shared/Navbar.tsx @@ -9,7 +9,10 @@ interface MenuItem { } export default function Navbar() { - const nav: MenuItem[] = [{ label: "Dashboard", href: "/" }]; + const nav: MenuItem[] = [ + { label: "Dashboard", href: "/" }, + { label: "Personalise", href: "/personalise" } + ]; const wellKnown = useWellKnown(); const userinfo = useUserinfo(); diff --git a/pkg/view/src/pages/auth/login.tsx b/pkg/view/src/pages/auth/login.tsx index 7d6b404..7a0bdac 100644 --- a/pkg/view/src/pages/auth/login.tsx +++ b/pkg/view/src/pages/auth/login.tsx @@ -117,8 +117,8 @@ export default function LoginPage() { throw new Error(err); } else { const data = await res.json(); - new Cookie().set("access_token", data["access_token"], { path: "/" }); - new Cookie().set("refresh_token", data["refresh_token"], { path: "/" }); + new Cookie().set("access_token", data["access_token"], { path: "/", maxAge: undefined }); + new Cookie().set("refresh_token", data["refresh_token"], { path: "/", maxAge: undefined }); setError(null); } } diff --git a/pkg/view/src/pages/dashboard.tsx b/pkg/view/src/pages/dashboard.tsx index de5e332..d245445 100644 --- a/pkg/view/src/pages/dashboard.tsx +++ b/pkg/view/src/pages/dashboard.tsx @@ -128,7 +128,7 @@ export default function DashboardPage() {
-
+
Challenges @@ -164,7 +164,7 @@ export default function DashboardPage() {
-
+
Sessions diff --git a/pkg/view/src/pages/personalise.tsx b/pkg/view/src/pages/personalise.tsx new file mode 100644 index 0000000..00d9485 --- /dev/null +++ b/pkg/view/src/pages/personalise.tsx @@ -0,0 +1,121 @@ +import { getAtk, readProfiles, useUserinfo } from "../stores/userinfo.tsx"; +import { createSignal, Show } from "solid-js"; + +export default function PersonalPage() { + const userinfo = useUserinfo(); + + const [error, setError] = createSignal(null); + const [success, setSuccess] = createSignal(null); + const [loading, setLoading] = createSignal(false); + + async function update(evt: SubmitEvent) { + evt.preventDefault(); + + const data = Object.fromEntries(new FormData(evt.target as HTMLFormElement)); + + setLoading(true); + const res = await fetch("/api/users/me", { + method: "PUT", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${getAtk()}` + }, + body: JSON.stringify(data) + }); + if (res.status !== 200) { + setSuccess(null); + setError(await res.text()); + } else { + await readProfiles(); + setSuccess("Your basic information has been update."); + setError(null); + } + setLoading(false); + } + + return ( +
+
+

{userinfo?.displayName}

+

Joined at {new Date(userinfo?.meta?.created_at).toLocaleString()}

+
+ +
+
+ + +
+ +
+
+ + +
+ +
+
+ +
+ + +
+ + + +
+ +
+
+ +
+
+ ); +} \ No newline at end of file diff --git a/pkg/view/src/stores/userinfo.tsx b/pkg/view/src/stores/userinfo.tsx index eaa0ce7..fd4d10f 100644 --- a/pkg/view/src/stores/userinfo.tsx +++ b/pkg/view/src/stores/userinfo.tsx @@ -39,8 +39,8 @@ export async function refreshAtk() { console.error(await res.text()) } else { const data = await res.json(); - new Cookie().set("access_token", data["access_token"], { path: "/" }); - new Cookie().set("refresh_token", data["refresh_token"], { path: "/" }); + new Cookie().set("access_token", data["access_token"], { path: "/", maxAge: undefined }); + new Cookie().set("refresh_token", data["refresh_token"], { path: "/", maxAge: undefined }); } } @@ -77,8 +77,8 @@ export async function readProfiles(recovering = true) { } export function clearUserinfo() { - new Cookie().remove("access_token", { path: "/" }); - new Cookie().remove("refresh_token", { path: "/" }); + new Cookie().remove("access_token", { path: "/", maxAge: undefined }); + new Cookie().remove("refresh_token", { path: "/", maxAge: undefined }); setUserinfo(defaultUserinfo); }