✨ Realms utilities
This commit is contained in:
parent
012ee55c3a
commit
57dc2771c2
@ -10,6 +10,7 @@ func RunMigration(source *gorm.DB) error {
|
|||||||
&models.Account{},
|
&models.Account{},
|
||||||
&models.AccountMembership{},
|
&models.AccountMembership{},
|
||||||
&models.Realm{},
|
&models.Realm{},
|
||||||
|
&models.RealmMember{},
|
||||||
&models.Category{},
|
&models.Category{},
|
||||||
&models.Tag{},
|
&models.Tag{},
|
||||||
&models.Post{},
|
&models.Post{},
|
||||||
|
@ -69,7 +69,7 @@ func inviteRealm(c *fiber.Ctx) error {
|
|||||||
realmId, _ := c.ParamsInt("realmId", 0)
|
realmId, _ := c.ParamsInt("realmId", 0)
|
||||||
|
|
||||||
var data struct {
|
var data struct {
|
||||||
AccountID uint `json:"account_id" validate:"required"`
|
AccountName string `json:"account_name" validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := BindAndValidate(c, &data); err != nil {
|
if err := BindAndValidate(c, &data); err != nil {
|
||||||
@ -86,7 +86,7 @@ func inviteRealm(c *fiber.Ctx) error {
|
|||||||
|
|
||||||
var account models.Account
|
var account models.Account
|
||||||
if err := database.C.Where(&models.Account{
|
if err := database.C.Where(&models.Account{
|
||||||
BaseModel: models.BaseModel{ID: uint(realmId)},
|
Name: data.AccountName,
|
||||||
}).First(&account).Error; err != nil {
|
}).First(&account).Error; err != nil {
|
||||||
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||||
}
|
}
|
||||||
@ -98,6 +98,40 @@ func inviteRealm(c *fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func kickRealm(c *fiber.Ctx) error {
|
||||||
|
user := c.Locals("principal").(models.Account)
|
||||||
|
realmId, _ := c.ParamsInt("realmId", 0)
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
AccountName string `json:"account_name" validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := BindAndValidate(c, &data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var realm models.Realm
|
||||||
|
if err := database.C.Where(&models.Realm{
|
||||||
|
BaseModel: models.BaseModel{ID: uint(realmId)},
|
||||||
|
AccountID: user.ID,
|
||||||
|
}).First(&realm).Error; err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
var account models.Account
|
||||||
|
if err := database.C.Where(&models.Account{
|
||||||
|
Name: data.AccountName,
|
||||||
|
}).First(&account).Error; err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusNotFound, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := services.KickRealmMember(account, realm); err != nil {
|
||||||
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||||
|
} else {
|
||||||
|
return c.SendStatus(fiber.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func editRealm(c *fiber.Ctx) error {
|
func editRealm(c *fiber.Ctx) error {
|
||||||
user := c.Locals("principal").(models.Account)
|
user := c.Locals("principal").(models.Account)
|
||||||
id, _ := c.ParamsInt("realmId", 0)
|
id, _ := c.ParamsInt("realmId", 0)
|
||||||
|
@ -81,6 +81,7 @@ func NewServer() {
|
|||||||
api.Get("/realms/:realmId", getRealm)
|
api.Get("/realms/:realmId", getRealm)
|
||||||
api.Post("/realms", auth, createRealm)
|
api.Post("/realms", auth, createRealm)
|
||||||
api.Post("/realms/:realmId/invite", auth, inviteRealm)
|
api.Post("/realms/:realmId/invite", auth, inviteRealm)
|
||||||
|
api.Post("/realms/:realmId/kick", auth, kickRealm)
|
||||||
api.Put("/realms/:realmId", auth, editRealm)
|
api.Put("/realms/:realmId", auth, editRealm)
|
||||||
api.Delete("/realms/:realmId", auth, deleteRealm)
|
api.Delete("/realms/:realmId", auth, deleteRealm)
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,19 @@ func InviteRealmMember(user models.Account, target models.Realm) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func KickRealmMember(user models.Account, target models.Realm) error {
|
||||||
|
var member models.RealmMember
|
||||||
|
|
||||||
|
if err := database.C.Where(&models.RealmMember{
|
||||||
|
RealmID: target.ID,
|
||||||
|
AccountID: user.ID,
|
||||||
|
}).First(&member).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return database.C.Delete(&member).Error
|
||||||
|
}
|
||||||
|
|
||||||
func EditRealm(realm models.Realm, name, description string, isPublic bool) (models.Realm, error) {
|
func EditRealm(realm models.Realm, name, description string, isPublic bool) (models.Realm, error) {
|
||||||
realm.Name = name
|
realm.Name = name
|
||||||
realm.Description = description
|
realm.Description = description
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { createSignal, For, Show } from "solid-js";
|
import { createSignal, For, Show } from "solid-js";
|
||||||
|
import { closeModel, openModel } from "../../scripts/modals.ts";
|
||||||
|
import { getAtk } from "../../stores/userinfo.tsx";
|
||||||
|
|
||||||
export default function RealmDirectoryPage() {
|
export default function RealmDirectoryPage() {
|
||||||
const [error, setError] = createSignal<string | null>(null);
|
const [error, setError] = createSignal<string | null>(null);
|
||||||
|
const [submitting, setSubmitting] = createSignal(false);
|
||||||
|
|
||||||
const [realms, setRealms] = createSignal<any>(null);
|
const [realms, setRealms] = createSignal<any>(null);
|
||||||
|
|
||||||
@ -16,6 +19,32 @@ export default function RealmDirectoryPage() {
|
|||||||
|
|
||||||
readRealms();
|
readRealms();
|
||||||
|
|
||||||
|
async function createRealm(evt: SubmitEvent) {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
const form = evt.target as HTMLFormElement;
|
||||||
|
const data = Object.fromEntries(new FormData(form));
|
||||||
|
|
||||||
|
setSubmitting(true);
|
||||||
|
const res = await fetch("/api/realms", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Authorization": `Bearer ${getAtk()}`, "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: data.name,
|
||||||
|
description: data.description,
|
||||||
|
is_public: data.is_public != null
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
setError(await res.text());
|
||||||
|
} else {
|
||||||
|
await readRealms();
|
||||||
|
closeModel("#create-realm");
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div id="alerts">
|
<div id="alerts">
|
||||||
@ -31,6 +60,13 @@ export default function RealmDirectoryPage() {
|
|||||||
</Show>
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-1 px-7 flex items-center justify-between">
|
||||||
|
<h3 class="py-3 font-bold">Realms directory</h3>
|
||||||
|
<button type="button" class="btn btn-primary" onClick={() => openModel("#create-realm")}>
|
||||||
|
<i class="fa-solid fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<For each={realms()}>
|
<For each={realms()}>
|
||||||
{item => <div class="px-7 pt-7 pb-5 border-t border-base-200">
|
{item => <div class="px-7 pt-7 pb-5 border-t border-base-200">
|
||||||
<h2 class="text-xl font-bold">{item.name}</h2>
|
<h2 class="text-xl font-bold">{item.name}</h2>
|
||||||
@ -41,6 +77,38 @@ export default function RealmDirectoryPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>}
|
</div>}
|
||||||
</For>
|
</For>
|
||||||
|
|
||||||
|
<dialog id="create-realm" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h2 class="card-title px-1">Create a realm</h2>
|
||||||
|
<form class="mt-2" onSubmit={createRealm}>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Realm name</span>
|
||||||
|
</div>
|
||||||
|
<input name="name" type="text" placeholder="Type here" class="input input-bordered w-full" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Realm description</span>
|
||||||
|
</div>
|
||||||
|
<textarea name="description" placeholder="Type here" class="textarea textarea-bordered w-full" />
|
||||||
|
</label>
|
||||||
|
<div class="form-control mt-2">
|
||||||
|
<label class="label cursor-pointer">
|
||||||
|
<span class="label-text">Make it public</span>
|
||||||
|
<input type="checkbox" name="is_public" class="checkbox checkbox-primary" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary mt-2" disabled={submitting()}>
|
||||||
|
<Show when={submitting()} fallback={"Submit"}>
|
||||||
|
<span class="loading"></span>
|
||||||
|
</Show>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,20 +1,26 @@
|
|||||||
import { createSignal, Show } from "solid-js";
|
import { createSignal, Show } from "solid-js";
|
||||||
import { createStore } from "solid-js/store";
|
import { createStore } from "solid-js/store";
|
||||||
import { useParams } from "@solidjs/router";
|
import { useNavigate, useParams } from "@solidjs/router";
|
||||||
|
|
||||||
import PostList from "../../components/PostList.tsx";
|
import PostList from "../../components/PostList.tsx";
|
||||||
import PostPublish from "../../components/PostPublish.tsx";
|
import PostPublish from "../../components/PostPublish.tsx";
|
||||||
|
|
||||||
import styles from "./realm.module.css";
|
import styles from "./realm.module.css";
|
||||||
|
import { getAtk, useUserinfo } from "../../stores/userinfo.tsx";
|
||||||
|
import { closeModel, openModel } from "../../scripts/modals.ts";
|
||||||
|
|
||||||
export default function RealmPage() {
|
export default function RealmPage() {
|
||||||
|
const userinfo = useUserinfo();
|
||||||
|
|
||||||
const [error, setError] = createSignal<string | null>(null);
|
const [error, setError] = createSignal<string | null>(null);
|
||||||
|
const [submitting, setSubmitting] = createSignal(false);
|
||||||
|
|
||||||
const [realm, setRealm] = createSignal<any>(null);
|
const [realm, setRealm] = createSignal<any>(null);
|
||||||
const [page, setPage] = createSignal(0);
|
const [page, setPage] = createSignal(0);
|
||||||
const [info, setInfo] = createSignal<any>(null);
|
const [info, setInfo] = createSignal<any>(null);
|
||||||
|
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
async function readRealm() {
|
async function readRealm() {
|
||||||
const res = await fetch(`/api/realms/${params["realmId"]}`);
|
const res = await fetch(`/api/realms/${params["realmId"]}`);
|
||||||
@ -32,7 +38,7 @@ export default function RealmPage() {
|
|||||||
const res = await fetch(`/api/posts?` + new URLSearchParams({
|
const res = await fetch(`/api/posts?` + new URLSearchParams({
|
||||||
take: (10).toString(),
|
take: (10).toString(),
|
||||||
offset: ((page() - 1) * 10).toString(),
|
offset: ((page() - 1) * 10).toString(),
|
||||||
realmId: params["realmId"],
|
realmId: params["realmId"]
|
||||||
}));
|
}));
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
setError(await res.text());
|
setError(await res.text());
|
||||||
@ -42,6 +48,90 @@ export default function RealmPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function editRealm(evt: SubmitEvent) {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
const form = evt.target as HTMLFormElement;
|
||||||
|
const data = Object.fromEntries(new FormData(form));
|
||||||
|
|
||||||
|
setSubmitting(true);
|
||||||
|
const res = await fetch(`/api/realms/${params["realmId"]}`, {
|
||||||
|
method: "PUT",
|
||||||
|
headers: { "Authorization": `Bearer ${getAtk()}`, "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: data.name,
|
||||||
|
description: data.description,
|
||||||
|
is_public: data.is_public != null
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
setError(await res.text());
|
||||||
|
} else {
|
||||||
|
await readRealm();
|
||||||
|
closeModel("#edit-realm");
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function inviteMember(evt: SubmitEvent) {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
const form = evt.target as HTMLFormElement;
|
||||||
|
const data = Object.fromEntries(new FormData(form));
|
||||||
|
|
||||||
|
setSubmitting(true);
|
||||||
|
const res = await fetch(`/api/realms/${params["realmId"]}/invite`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Authorization": `Bearer ${getAtk()}`, "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
setError(await res.text());
|
||||||
|
} else {
|
||||||
|
await readRealm();
|
||||||
|
closeModel("#invite-member");
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function kickMember(evt: SubmitEvent) {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
const form = evt.target as HTMLFormElement;
|
||||||
|
const data = Object.fromEntries(new FormData(form));
|
||||||
|
|
||||||
|
setSubmitting(true);
|
||||||
|
const res = await fetch(`/api/realms/${params["realmId"]}/kick`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Authorization": `Bearer ${getAtk()}`, "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
setError(await res.text());
|
||||||
|
} else {
|
||||||
|
await readRealm();
|
||||||
|
closeModel("#kick-member");
|
||||||
|
form.reset();
|
||||||
|
}
|
||||||
|
setSubmitting(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function breakRealm() {
|
||||||
|
if (!confirm("Are you sure about that? All posts in this realm will disappear forever.")) return;
|
||||||
|
|
||||||
|
const res = await fetch(`/api/realms/${params["realmId"]}`, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: { "Authorization": `Bearer ${getAtk()}` }
|
||||||
|
});
|
||||||
|
if (res.status !== 200) {
|
||||||
|
setError(await res.text());
|
||||||
|
} else {
|
||||||
|
navigate("/realms");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setMeta(data: any, field: string, scroll = true) {
|
function setMeta(data: any, field: string, scroll = true) {
|
||||||
const meta: { [id: string]: any } = {
|
const meta: { [id: string]: any } = {
|
||||||
reposting: null,
|
reposting: null,
|
||||||
@ -81,6 +171,14 @@ export default function RealmPage() {
|
|||||||
|
|
||||||
<div class={`${styles.description} text-sm mt-3`}>
|
<div class={`${styles.description} text-sm mt-3`}>
|
||||||
<p>Realm #{realm()?.id}</p>
|
<p>Realm #{realm()?.id}</p>
|
||||||
|
<Show when={realm()?.account_id === userinfo?.profiles?.id}>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<button class="link" onClick={() => openModel("#edit-realm")}>Edit</button>
|
||||||
|
<button class="link" onClick={() => openModel("#invite-member")}>Invite</button>
|
||||||
|
<button class="link" onClick={() => openModel("#kick-member")}>Kick</button>
|
||||||
|
<button class="link" onClick={() => breakRealm()}>Break-up</button>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -102,6 +200,91 @@ export default function RealmPage() {
|
|||||||
onReply={(item) => setMeta(item, "replying")}
|
onReply={(item) => setMeta(item, "replying")}
|
||||||
onEdit={(item) => setMeta(item, "editing")}
|
onEdit={(item) => setMeta(item, "editing")}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<dialog id="edit-realm" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h2 class="card-title px-1">Create a realm</h2>
|
||||||
|
<form class="mt-2" onSubmit={editRealm}>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Realm name</span>
|
||||||
|
</div>
|
||||||
|
<input value={realm()?.name} name="name" type="text" placeholder="Type here"
|
||||||
|
class="input input-bordered w-full" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Realm description</span>
|
||||||
|
</div>
|
||||||
|
<textarea value={realm()?.description} name="description" placeholder="Type here"
|
||||||
|
class="textarea textarea-bordered w-full" />
|
||||||
|
</label>
|
||||||
|
<div class="form-control mt-2">
|
||||||
|
<label class="label cursor-pointer">
|
||||||
|
<span class="label-text">Make it public</span>
|
||||||
|
<input checked={realm()?.is_public} type="checkbox" name="is_public"
|
||||||
|
class="checkbox checkbox-primary" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary mt-2" disabled={submitting()}>
|
||||||
|
<Show when={submitting()} fallback={"Submit"}>
|
||||||
|
<span class="loading"></span>
|
||||||
|
</Show>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<dialog id="invite-member" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h2 class="card-title px-1">Invite someone as a member</h2>
|
||||||
|
<form class="mt-2" onSubmit={inviteMember}>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Username</span>
|
||||||
|
</div>
|
||||||
|
<input name="account_name" type="text" placeholder="Type here" class="input input-bordered w-full" />
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text-alt">
|
||||||
|
Invite someone via their username so that they can publish content in non-public realm.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary mt-2" disabled={submitting()}>
|
||||||
|
<Show when={submitting()} fallback={"Submit"}>
|
||||||
|
<span class="loading"></span>
|
||||||
|
</Show>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<dialog id="kick-member" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h2 class="card-title px-1">Kick someone out of your realm</h2>
|
||||||
|
<form class="mt-2" onSubmit={kickMember}>
|
||||||
|
<label class="form-control w-full">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Username</span>
|
||||||
|
</div>
|
||||||
|
<input name="account_name" type="text" placeholder="Type here" class="input input-bordered w-full" />
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text-alt">
|
||||||
|
Remove someone out of your realm.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary mt-2" disabled={submitting()}>
|
||||||
|
<Show when={submitting()} fallback={"Submit"}>
|
||||||
|
<span class="loading"></span>
|
||||||
|
</Show>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user