✨ Realms utilities
This commit is contained in:
parent
012ee55c3a
commit
57dc2771c2
@ -10,6 +10,7 @@ func RunMigration(source *gorm.DB) error {
|
||||
&models.Account{},
|
||||
&models.AccountMembership{},
|
||||
&models.Realm{},
|
||||
&models.RealmMember{},
|
||||
&models.Category{},
|
||||
&models.Tag{},
|
||||
&models.Post{},
|
||||
|
@ -69,7 +69,7 @@ func inviteRealm(c *fiber.Ctx) error {
|
||||
realmId, _ := c.ParamsInt("realmId", 0)
|
||||
|
||||
var data struct {
|
||||
AccountID uint `json:"account_id" validate:"required"`
|
||||
AccountName string `json:"account_name" validate:"required"`
|
||||
}
|
||||
|
||||
if err := BindAndValidate(c, &data); err != nil {
|
||||
@ -86,7 +86,7 @@ func inviteRealm(c *fiber.Ctx) error {
|
||||
|
||||
var account models.Account
|
||||
if err := database.C.Where(&models.Account{
|
||||
BaseModel: models.BaseModel{ID: uint(realmId)},
|
||||
Name: data.AccountName,
|
||||
}).First(&account).Error; err != nil {
|
||||
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 {
|
||||
user := c.Locals("principal").(models.Account)
|
||||
id, _ := c.ParamsInt("realmId", 0)
|
||||
|
@ -81,6 +81,7 @@ func NewServer() {
|
||||
api.Get("/realms/:realmId", getRealm)
|
||||
api.Post("/realms", auth, createRealm)
|
||||
api.Post("/realms/:realmId/invite", auth, inviteRealm)
|
||||
api.Post("/realms/:realmId/kick", auth, kickRealm)
|
||||
api.Put("/realms/:realmId", auth, editRealm)
|
||||
api.Delete("/realms/:realmId", auth, deleteRealm)
|
||||
}
|
||||
|
@ -50,6 +50,19 @@ func InviteRealmMember(user models.Account, target models.Realm) error {
|
||||
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) {
|
||||
realm.Name = name
|
||||
realm.Description = description
|
||||
|
@ -1,7 +1,10 @@
|
||||
import { createSignal, For, Show } from "solid-js";
|
||||
import { closeModel, openModel } from "../../scripts/modals.ts";
|
||||
import { getAtk } from "../../stores/userinfo.tsx";
|
||||
|
||||
export default function RealmDirectoryPage() {
|
||||
const [error, setError] = createSignal<string | null>(null);
|
||||
const [submitting, setSubmitting] = createSignal(false);
|
||||
|
||||
const [realms, setRealms] = createSignal<any>(null);
|
||||
|
||||
@ -16,6 +19,32 @@ export default function RealmDirectoryPage() {
|
||||
|
||||
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 (
|
||||
<>
|
||||
<div id="alerts">
|
||||
@ -31,6 +60,13 @@ export default function RealmDirectoryPage() {
|
||||
</Show>
|
||||
</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()}>
|
||||
{item => <div class="px-7 pt-7 pb-5 border-t border-base-200">
|
||||
<h2 class="text-xl font-bold">{item.name}</h2>
|
||||
@ -41,6 +77,38 @@ export default function RealmDirectoryPage() {
|
||||
</div>
|
||||
</div>}
|
||||
</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 { createStore } from "solid-js/store";
|
||||
import { useParams } from "@solidjs/router";
|
||||
import { useNavigate, useParams } from "@solidjs/router";
|
||||
|
||||
import PostList from "../../components/PostList.tsx";
|
||||
import PostPublish from "../../components/PostPublish.tsx";
|
||||
|
||||
import styles from "./realm.module.css";
|
||||
import { getAtk, useUserinfo } from "../../stores/userinfo.tsx";
|
||||
import { closeModel, openModel } from "../../scripts/modals.ts";
|
||||
|
||||
export default function RealmPage() {
|
||||
const userinfo = useUserinfo();
|
||||
|
||||
const [error, setError] = createSignal<string | null>(null);
|
||||
const [submitting, setSubmitting] = createSignal(false);
|
||||
|
||||
const [realm, setRealm] = createSignal<any>(null);
|
||||
const [page, setPage] = createSignal(0);
|
||||
const [info, setInfo] = createSignal<any>(null);
|
||||
|
||||
const params = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
async function readRealm() {
|
||||
const res = await fetch(`/api/realms/${params["realmId"]}`);
|
||||
@ -32,7 +38,7 @@ export default function RealmPage() {
|
||||
const res = await fetch(`/api/posts?` + new URLSearchParams({
|
||||
take: (10).toString(),
|
||||
offset: ((page() - 1) * 10).toString(),
|
||||
realmId: params["realmId"],
|
||||
realmId: params["realmId"]
|
||||
}));
|
||||
if (res.status !== 200) {
|
||||
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) {
|
||||
const meta: { [id: string]: any } = {
|
||||
reposting: null,
|
||||
@ -81,6 +171,14 @@ export default function RealmPage() {
|
||||
|
||||
<div class={`${styles.description} text-sm mt-3`}>
|
||||
<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>
|
||||
|
||||
@ -102,6 +200,91 @@ export default function RealmPage() {
|
||||
onReply={(item) => setMeta(item, "replying")}
|
||||
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