⚡ Optimized userinfo endpoint
This commit is contained in:
parent
cfc1115b2f
commit
e2b609cf43
@ -16,6 +16,7 @@ func RunMigration(source *gorm.DB) error {
|
||||
&models.MagicToken{},
|
||||
&models.ThirdClient{},
|
||||
&models.ActionEvent{},
|
||||
&models.Notification{},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ type Account struct {
|
||||
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"`
|
||||
}
|
||||
|
13
pkg/models/notifications.go
Normal file
13
pkg/models/notifications.go
Normal file
@ -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"`
|
||||
}
|
@ -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())
|
||||
}
|
||||
|
63
pkg/server/security_api.go
Normal file
63
pkg/server/security_api.go
Normal file
@ -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,
|
||||
})
|
||||
}
|
@ -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)
|
||||
|
@ -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: "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) => (
|
||||
<li>
|
||||
<a href={item.href}>{item.label}</a>
|
||||
<Show when={item.children}>
|
||||
<ul class="p-2">
|
||||
<For each={item.children}>
|
||||
{(item) =>
|
||||
<li>
|
||||
<a href={item.href}>{item.label}</a>
|
||||
</li>
|
||||
}
|
||||
</For>
|
||||
</ul>
|
||||
</Show>
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
@ -66,7 +82,22 @@ export default function Navbar() {
|
||||
<For each={nav}>
|
||||
{(item) => (
|
||||
<li>
|
||||
<Show when={item.children} fallback={<a href={item.href}>{item.label}</a>}>
|
||||
<details>
|
||||
<summary>
|
||||
<a href={item.href}>{item.label}</a>
|
||||
</summary>
|
||||
<ul class="p-2">
|
||||
<For each={item.children}>
|
||||
{(item) =>
|
||||
<li>
|
||||
<a href={item.href}>{item.label}</a>
|
||||
</li>
|
||||
}
|
||||
</For>
|
||||
</ul>
|
||||
</details>
|
||||
</Show>
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
|
@ -1,15 +1,43 @@
|
||||
import { getAtk, readProfiles, useUserinfo } from "../stores/userinfo.tsx";
|
||||
import { getAtk } from "../stores/userinfo.tsx";
|
||||
import { createSignal, For, Show } from "solid-js";
|
||||
|
||||
export default function DashboardPage() {
|
||||
const userinfo = useUserinfo();
|
||||
|
||||
const [challenges, setChallenges] = createSignal<any[]>([]);
|
||||
const [challengeCount, setChallengeCount] = createSignal(0);
|
||||
const [sessions, setSessions] = createSignal<any[]>([]);
|
||||
const [sessionCount, setSessionCount] = createSignal(0);
|
||||
const [events, setEvents] = createSignal<any[]>([]);
|
||||
const [eventCount, setEventCount] = createSignal(0);
|
||||
|
||||
const [error, setError] = createSignal<string | null>(null);
|
||||
const [submitting, setSubmitting] = createSignal(false);
|
||||
|
||||
async function readChallenges() {
|
||||
const res = await fetch("/api/users/me/challenges?take=10", {
|
||||
headers: { Authorization: `Bearer ${getAtk()}` }
|
||||
});
|
||||
if (res.status !== 200) {
|
||||
setError(await res.text());
|
||||
} else {
|
||||
const data = await res.json();
|
||||
setChallenges(data["data"]);
|
||||
setChallengeCount(data["count"]);
|
||||
}
|
||||
}
|
||||
|
||||
async function readSessions() {
|
||||
const res = await fetch("/api/users/me/sessions?take=10", {
|
||||
headers: { Authorization: `Bearer ${getAtk()}` }
|
||||
});
|
||||
if (res.status !== 200) {
|
||||
setError(await res.text());
|
||||
} else {
|
||||
const data = await res.json();
|
||||
setSessions(data["data"]);
|
||||
setSessionCount(data["count"]);
|
||||
}
|
||||
}
|
||||
|
||||
async function readEvents() {
|
||||
const res = await fetch("/api/users/me/events?take=10", {
|
||||
headers: { Authorization: `Bearer ${getAtk()}` }
|
||||
@ -32,12 +60,14 @@ export default function DashboardPage() {
|
||||
if (res.status !== 200) {
|
||||
setError(await res.text());
|
||||
} else {
|
||||
await readProfiles();
|
||||
await readSessions();
|
||||
setError(null);
|
||||
}
|
||||
setSubmitting(false);
|
||||
}
|
||||
|
||||
readChallenges();
|
||||
readSessions();
|
||||
readEvents();
|
||||
|
||||
return (
|
||||
@ -71,7 +101,7 @@ export default function DashboardPage() {
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-title">Challenges</div>
|
||||
<div class="stat-value">{userinfo?.meta?.challenges?.length}</div>
|
||||
<div class="stat-value">{challengeCount()}</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
@ -83,7 +113,7 @@ export default function DashboardPage() {
|
||||
</svg>
|
||||
</div>
|
||||
<div class="stat-title">Sessions</div>
|
||||
<div class="stat-value">{userinfo?.meta?.sessions?.length}</div>
|
||||
<div class="stat-value">{sessionCount()}</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
@ -120,7 +150,7 @@ export default function DashboardPage() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={userinfo?.meta?.challenges ?? []}>
|
||||
<For each={challenges()}>
|
||||
{item => <tr>
|
||||
<th>{item.id}</th>
|
||||
<td>{item.state}</td>
|
||||
@ -155,7 +185,7 @@ export default function DashboardPage() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={userinfo?.meta?.sessions ?? []}>
|
||||
<For each={sessions()}>
|
||||
{item => <tr>
|
||||
<th>{item.id}</th>
|
||||
<td>{item.client_id ? "Linked" : "Non-linked"}</td>
|
||||
|
Loading…
Reference in New Issue
Block a user