Personal page basis

This commit is contained in:
LittleSheep 2024-04-02 20:23:25 +08:00
parent e8aac7bb66
commit 0b436c0a1e
11 changed files with 209 additions and 4 deletions

11
.idea/dataSources.xml generated
View File

@ -1,11 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true"> <component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="hy_passport@localhost" uuid="49a1c31c-500d-4f9f-bbf4-b4ddc9f3dc56"> <data-source source="LOCAL" name="hy_identity@localhost" uuid="49a1c31c-500d-4f9f-bbf4-b4ddc9f3dc56">
<driver-ref>postgresql</driver-ref> <driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize> <synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver> <jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://localhost:5432/hy_passport</jdbc-url> <jdbc-url>jdbc:postgresql://localhost:5432/hy_identity</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
<data-source source="LOCAL" name="identity@id.solsynth.dev" uuid="df97c878-c355-4a1b-b7fb-3280c4ad4553">
<driver-ref>postgresql</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.postgresql.Driver</jdbc-driver>
<jdbc-url>jdbc:postgresql://id.solsynth.dev:5432/identity</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir> <working-dir>$ProjectFileDir$</working-dir>
</data-source> </data-source>
</component> </component>

View File

@ -10,6 +10,7 @@ func RunMigration(source *gorm.DB) error {
&models.Account{}, &models.Account{},
&models.AuthFactor{}, &models.AuthFactor{},
&models.AccountProfile{}, &models.AccountProfile{},
&models.AccountPage{},
&models.AccountContact{}, &models.AccountContact{},
&models.AuthSession{}, &models.AuthSession{},
&models.AuthChallenge{}, &models.AuthChallenge{},

View File

@ -17,6 +17,7 @@ type Account struct {
Avatar string `json:"avatar"` Avatar string `json:"avatar"`
Banner string `json:"banner"` Banner string `json:"banner"`
Profile AccountProfile `json:"profile"` Profile AccountProfile `json:"profile"`
PersonalPage AccountPage `json:"personal_page"`
Sessions []AuthSession `json:"sessions"` Sessions []AuthSession `json:"sessions"`
Challenges []AuthChallenge `json:"challenges"` Challenges []AuthChallenge `json:"challenges"`
Factors []AuthFactor `json:"factors"` Factors []AuthFactor `json:"factors"`

View File

@ -1,6 +1,9 @@
package models package models
import "time" import (
"gorm.io/datatypes"
"time"
)
type AccountProfile struct { type AccountProfile struct {
BaseModel BaseModel
@ -11,3 +14,18 @@ type AccountProfile struct {
Birthday *time.Time `json:"birthday"` Birthday *time.Time `json:"birthday"`
AccountID uint `json:"account_id"` AccountID uint `json:"account_id"`
} }
type AccountPage struct {
BaseModel
Content string `json:"content"`
Script string `json:"script"`
Style string `json:"style"`
Links datatypes.JSONSlice[AccountPageLinks] `json:"links"`
AccountID uint `json:"account_id"`
}
type AccountPageLinks struct {
Label string `json:"label"`
Url string `json:"url"`
}

69
pkg/server/page_api.go Normal file
View File

@ -0,0 +1,69 @@
package server
import (
"git.solsynth.dev/hydrogen/identity/pkg/database"
"git.solsynth.dev/hydrogen/identity/pkg/models"
"github.com/gofiber/fiber/v2"
)
func getPersonalPage(c *fiber.Ctx) error {
alias := c.Params("alias")
var account models.Account
if err := database.C.
Where(&models.Account{Name: alias}).
First(&account).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
var page models.AccountPage
if err := database.C.
Where(&models.AccountPage{AccountID: account.ID}).
First(&page).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.JSON(page)
}
func getOwnPersonalPage(c *fiber.Ctx) error {
user := c.Locals("principal").(models.Account)
var page models.AccountPage
if err := database.C.
Where(&models.AccountPage{AccountID: user.ID}).
FirstOrCreate(&page, &models.AccountPage{AccountID: user.ID}).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.JSON(page)
}
func editPersonalPage(c *fiber.Ctx) error {
user := c.Locals("principal").(models.Account)
var data struct {
Content string `json:"content"`
Links []models.AccountPageLinks `json:"links"`
}
if err := BindAndValidate(c, &data); err != nil {
return err
}
var page models.AccountPage
if err := database.C.
Where(&models.AccountPage{AccountID: user.ID}).
FirstOrInit(&page).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
page.Content = data.Content
page.Links = data.Links
if err := database.C.Save(&page).Error; err != nil {
return fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
return c.SendStatus(fiber.StatusOK)
}

View File

@ -77,7 +77,9 @@ func NewServer() {
me.Put("/banner", authMiddleware, setBanner) me.Put("/banner", authMiddleware, setBanner)
me.Get("/", authMiddleware, getUserinfo) me.Get("/", authMiddleware, getUserinfo)
me.Get("/page", authMiddleware, getOwnPersonalPage)
me.Put("/", authMiddleware, editUserinfo) me.Put("/", authMiddleware, editUserinfo)
me.Put("/page", authMiddleware, editPersonalPage)
me.Get("/events", authMiddleware, getEvents) me.Get("/events", authMiddleware, getEvents)
me.Get("/challenges", authMiddleware, getChallenges) me.Get("/challenges", authMiddleware, getChallenges)
me.Get("/sessions", authMiddleware, getSessions) me.Get("/sessions", authMiddleware, getSessions)
@ -86,6 +88,12 @@ func NewServer() {
me.Post("/confirm", doRegisterConfirm) me.Post("/confirm", doRegisterConfirm)
} }
directory := api.Group("/users/:alias").Name("User Directory")
{
directory.Get("/", getOtherUserinfo)
directory.Get("/page", getPersonalPage)
}
api.Post("/users", doRegister) api.Post("/users", doRegister)
api.Put("/auth", startChallenge) api.Put("/auth", startChallenge)

21
pkg/server/userinfo..go Normal file
View File

@ -0,0 +1,21 @@
package server
import (
"git.solsynth.dev/hydrogen/identity/pkg/database"
"git.solsynth.dev/hydrogen/identity/pkg/models"
"github.com/gofiber/fiber/v2"
)
func getOtherUserinfo(c *fiber.Ctx) error {
alias := c.Params("alias")
var account models.Account
if err := database.C.
Where(&models.Account{Name: alias}).
Omit("sessions", "challenges", "factors", "events", "clients", "notifications", "notify_subscribers").
First(&account).Error; err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
return c.JSON(account)
}

View File

@ -6,6 +6,7 @@
<v-list density="comfortable"> <v-list density="comfortable">
<v-list-item title="Dashboard" prepend-icon="mdi-view-dashboard" :to="{ name: 'dashboard' }" exact /> <v-list-item title="Dashboard" prepend-icon="mdi-view-dashboard" :to="{ name: 'dashboard' }" exact />
<v-list-item title="Personalize" prepend-icon="mdi-card-bulleted-outline" :to="{ name: 'personalize' }" /> <v-list-item title="Personalize" prepend-icon="mdi-card-bulleted-outline" :to="{ name: 'personalize' }" />
<v-list-item title="Personal Page" prepend-icon="mdi-sitemap" :to="{ name: 'personal-page' }" />
<v-list-item title="Security" prepend-icon="mdi-security" :to="{ name: 'security' }" /> <v-list-item title="Security" prepend-icon="mdi-security" :to="{ name: 'security' }" />
</v-list> </v-list>
</v-card> </v-card>
@ -17,3 +18,5 @@
</v-row> </v-row>
</v-container> </v-container>
</template> </template>
<script setup lang="ts">
</script>

View File

@ -26,6 +26,12 @@ const router = createRouter({
component: () => import("@/views/personalize.vue"), component: () => import("@/views/personalize.vue"),
meta: { title: "Your personality" }, meta: { title: "Your personality" },
}, },
{
path: "/me/personal-page",
name: "personal-page",
component: () => import("@/views/personal-page.vue"),
meta: { title: "Your personal page" },
},
{ {
path: "/me/security", path: "/me/security",
name: "security", name: "security",

View File

@ -0,0 +1,71 @@
<template>
<div>
<v-card class="mb-3" title="Design" prepend-icon="mdi-pencil-ruler" :loading="loading">
<template #text>
<v-form class="mt-1" @submit.prevent="submit">
<v-row dense>
<v-col :cols="12">
<v-textarea hide-details label="Content" density="comfortable" variant="outlined"
v-model="data.content" />
</v-col>
</v-row>
<v-btn type="submit" class="mt-2" variant="text" prepend-icon="mdi-content-save" :disabled="loading">
Apply Changes
</v-btn>
</v-form>
</template>
</v-card>
<v-snackbar v-model="done" :timeout="3000"> Your personal page has been updated.</v-snackbar>
<!-- @vue-ignore -->
<v-snackbar v-model="error" :timeout="5000">Something went wrong... {{ error }}</v-snackbar>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { getAtk } from "@/stores/userinfo";
import { request } from "@/scripts/request";
const error = ref<string | null>(null);
const done = ref(false);
const loading = ref(false);
const data = ref<any>({});
async function read() {
loading.value = true;
const res = await request("/api/users/me/page", {
headers: { Authorization: `Bearer ${(getAtk())}` }
});
if (res.status !== 200) {
error.value = await res.text();
} else {
data.value = await res.json();
}
loading.value = false;
}
async function submit() {
const payload = data.value;
loading.value = true;
const res = await request("/api/users/me/page", {
method: "PUT",
headers: { "Content-Type": "application/json", Authorization: `Bearer ${getAtk()}` },
body: JSON.stringify(payload)
});
if (res.status !== 200) {
error.value = await res.text();
} else {
await read();
done.value = true;
error.value = null;
}
loading.value = false;
}
read();
</script>

View File

@ -48,7 +48,7 @@
<v-img cover class="bg-grey-lighten-2" :height="320" :src="'/api/avatar/' + id.userinfo.data.banner" /> <v-img cover class="bg-grey-lighten-2" :height="320" :src="'/api/avatar/' + id.userinfo.data.banner" />
<v-card-text> <v-card-text>
<v-file-input clearable hide-details label="Update your banner" variant="solo-filled" density="comfortable" <v-file-input clearable hide-details label="Update your banner" variant="outlined" density="comfortable"
accept="image/*" prepend-icon="" append-icon="mdi-upload" v-model="banner" @click:append="applyBanner" /> accept="image/*" prepend-icon="" append-icon="mdi-upload" v-model="banner" @click:append="applyBanner" />
</v-card-text> </v-card-text>
</v-card> </v-card>