diff --git a/pkg/database/migrator.go b/pkg/database/migrator.go index 412b01e..d7b02b0 100644 --- a/pkg/database/migrator.go +++ b/pkg/database/migrator.go @@ -16,6 +16,7 @@ func RunMigration(source *gorm.DB) error { &models.MagicToken{}, &models.ThirdClient{}, &models.ActionEvent{}, + &models.Notification{}, ); err != nil { return err } diff --git a/pkg/models/accounts.go b/pkg/models/accounts.go index 0645b54..26663f6 100644 --- a/pkg/models/accounts.go +++ b/pkg/models/accounts.go @@ -19,20 +19,21 @@ const ( type Account struct { BaseModel - Name string `json:"name" gorm:"uniqueIndex"` - Nick string `json:"nick"` - Avatar string `json:"avatar"` - State AccountState `json:"state"` - Profile AccountProfile `json:"profile"` - Sessions []AuthSession `json:"sessions"` - Challenges []AuthChallenge `json:"challenges"` - Factors []AuthFactor `json:"factors"` - Contacts []AccountContact `json:"contacts"` - Events []ActionEvent `json:"events"` - MagicTokens []MagicToken `json:"-" gorm:"foreignKey:AssignTo"` - ThirdClients []ThirdClient `json:"clients"` - ConfirmedAt *time.Time `json:"confirmed_at"` - Permissions datatypes.JSONType[[]string] `json:"permissions"` + Name string `json:"name" gorm:"uniqueIndex"` + Nick string `json:"nick"` + Avatar string `json:"avatar"` + State AccountState `json:"state"` + Profile AccountProfile `json:"profile"` + Sessions []AuthSession `json:"sessions"` + Challenges []AuthChallenge `json:"challenges"` + Factors []AuthFactor `json:"factors"` + Contacts []AccountContact `json:"contacts"` + Events []ActionEvent `json:"events"` + MagicTokens []MagicToken `json:"-" gorm:"foreignKey:AssignTo"` + ThirdClients []ThirdClient `json:"clients"` + Notifications []Notification `json:"notifications" gorm:"foreignKey:RecipientID"` + ConfirmedAt *time.Time `json:"confirmed_at"` + Permissions datatypes.JSONType[[]string] `json:"permissions"` } func (v Account) GetPrimaryEmail() AccountContact { diff --git a/pkg/models/notifications.go b/pkg/models/notifications.go new file mode 100644 index 0000000..9eed3ad --- /dev/null +++ b/pkg/models/notifications.go @@ -0,0 +1,13 @@ +package models + +import "time" + +type Notification struct { + BaseModel + + Subject string `json:"subject"` + Content string `json:"content"` + IsImportant bool `json:"is_important"` + ReadAt *time.Time `json:"read_at"` + RecipientID uint `json:"recipient_id"` +} diff --git a/pkg/server/accounts_api.go b/pkg/server/accounts_api.go index 0360358..c405d2d 100644 --- a/pkg/server/accounts_api.go +++ b/pkg/server/accounts_api.go @@ -21,8 +21,7 @@ func getUserinfo(c *fiber.Ctx) error { Preload("Profile"). Preload("Contacts"). Preload("Factors"). - Preload("Sessions"). - Preload("Challenges"). + Preload("Notifications", "read_at IS NULL"). First(&data).Error; err != nil { return fiber.NewError(fiber.StatusInternalServerError, err.Error()) } diff --git a/pkg/server/security_api.go b/pkg/server/security_api.go new file mode 100644 index 0000000..06d987b --- /dev/null +++ b/pkg/server/security_api.go @@ -0,0 +1,63 @@ +package server + +import ( + "code.smartsheep.studio/hydrogen/passport/pkg/database" + "code.smartsheep.studio/hydrogen/passport/pkg/models" + "github.com/gofiber/fiber/v2" +) + +func getChallenges(c *fiber.Ctx) error { + user := c.Locals("principal").(models.Account) + take := c.QueryInt("take", 0) + offset := c.QueryInt("offset", 0) + + var count int64 + var challenges []models.AuthChallenge + if err := database.C. + Where(&models.AuthChallenge{AccountID: user.ID}). + Model(&models.AuthChallenge{}). + Count(&count).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + if err := database.C. + Where(&models.AuthChallenge{AccountID: user.ID}). + Limit(take). + Offset(offset). + Find(&challenges).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + return c.JSON(fiber.Map{ + "count": count, + "data": challenges, + }) +} + +func getSessions(c *fiber.Ctx) error { + user := c.Locals("principal").(models.Account) + take := c.QueryInt("take", 0) + offset := c.QueryInt("offset", 0) + + var count int64 + var sessions []models.AuthSession + if err := database.C. + Where(&models.AuthSession{AccountID: user.ID}). + Model(&models.AuthSession{}). + Count(&count).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + if err := database.C. + Where(&models.AuthSession{AccountID: user.ID}). + Limit(take). + Offset(offset). + Find(&sessions).Error; err != nil { + return fiber.NewError(fiber.StatusInternalServerError, err.Error()) + } + + return c.JSON(fiber.Map{ + "count": count, + "data": sessions, + }) +} diff --git a/pkg/server/startup.go b/pkg/server/startup.go index 233cfe7..8e37bca 100644 --- a/pkg/server/startup.go +++ b/pkg/server/startup.go @@ -63,6 +63,8 @@ func NewServer() { api.Get("/users/me", auth, getUserinfo) api.Put("/users/me", auth, editUserinfo) api.Get("/users/me/events", auth, getEvents) + api.Get("/users/me/challenges", auth, getChallenges) + api.Get("/users/me/sessions", auth, getSessions) api.Delete("/users/me/sessions/:sessionId", auth, killSession) api.Post("/users", doRegister) diff --git a/pkg/view/src/layouts/shared/Navbar.tsx b/pkg/view/src/layouts/shared/Navbar.tsx index 50fd0ba..686a46e 100644 --- a/pkg/view/src/layouts/shared/Navbar.tsx +++ b/pkg/view/src/layouts/shared/Navbar.tsx @@ -1,18 +1,23 @@ -import { For, Match, Switch } from "solid-js"; +import { For, Match, Show, Switch } from "solid-js"; import { clearUserinfo, useUserinfo } from "../../stores/userinfo.tsx"; import { useNavigate } from "@solidjs/router"; import { useWellKnown } from "../../stores/wellKnown.tsx"; interface MenuItem { label: string; - href: string; + href?: string; + children?: MenuItem[]; } export default function Navbar() { const nav: MenuItem[] = [ - { label: "Dashboard", href: "/" }, - { label: "Security", href: "/security" }, - { label: "Personalise", href: "/personalise" } + { + label: "You", children: [ + { label: "Dashboard", href: "/" }, + { label: "Security", href: "/security" }, + { label: "Personalise", href: "/personalise" } + ] + } ]; const wellKnown = useWellKnown(); @@ -52,6 +57,17 @@ export default function Navbar() { {(item) => (