✨ Personal page basis
This commit is contained in:
parent
e8aac7bb66
commit
0b436c0a1e
11
.idea/dataSources.xml
generated
11
.idea/dataSources.xml
generated
@ -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>
|
||||||
|
@ -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{},
|
||||||
|
@ -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"`
|
||||||
|
@ -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
69
pkg/server/page_api.go
Normal 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)
|
||||||
|
}
|
@ -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
21
pkg/server/userinfo..go
Normal 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)
|
||||||
|
}
|
@ -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>
|
@ -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",
|
||||||
|
71
pkg/views/src/views/personal-page.vue
Normal file
71
pkg/views/src/views/personal-page.vue
Normal 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>
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user