Добавил модераторскую роль

This commit is contained in:
Artem VV 2023-05-19 21:15:28 +07:00
parent fb6fb58d11
commit 3e1cfefdd9
15 changed files with 141 additions and 48 deletions

View file

@ -47,6 +47,7 @@ model users {
pass String @db.VarChar(100) pass String @db.VarChar(100)
fullName String? @db.VarChar(100) fullName String? @db.VarChar(100)
is_admin Boolean @default(false) is_admin Boolean @default(false)
is_moderator Boolean @default(false)
lore String? @db.Text() lore String? @db.Text()
timetable timetable[] timetable timetable[]

View file

@ -1,9 +1,12 @@
--- ---
import type { users } from "@prisma/client";
export interface Props { export interface Props {
is_user_admin: boolean; is_user_admin: boolean;
user: users;
} }
const { is_user_admin } = Astro.props; const { is_user_admin, user } = Astro.props;
const items = [ const items = [
{ {
@ -25,6 +28,10 @@ const items = [
]; ];
const itemsRight = [ const itemsRight = [
{
href: `/user/${user.login}`,
title: "Профиль",
},
{ {
href: "/logout", href: "/logout",
title: "Выйти", title: "Выйти",

View file

@ -22,7 +22,7 @@ const formatDate = (dt: Date) => {
--- ---
<div class="container"> <div class="container">
{user.is_admin ? ( {user.is_admin || user.is_moderator ? (
<a class="btn btn-sm btn-primary d-block mt-2" href="/articleEditor"> <a class="btn btn-sm btn-primary d-block mt-2" href="/articleEditor">
Новая статья Новая статья
</a> </a>
@ -35,7 +35,7 @@ const formatDate = (dt: Date) => {
<h4 class="alert-heading">{article.title}</h4> <h4 class="alert-heading">{article.title}</h4>
<h6>{formatDate(article.created_at)}</h6> <h6>{formatDate(article.created_at)}</h6>
<p data-article={article.message} /> <p data-article={article.message} />
{user.is_admin ? ( {user.is_admin || user.is_moderator ? (
<hr /> <hr />
<div class="d-flex gap-3"> <div class="d-flex gap-3">
<button class="btn btn-sm btn-danger flex-grow-1" onclick={`deleteArticle(${article.id})`}> <button class="btn btn-sm btn-danger flex-grow-1" onclick={`deleteArticle(${article.id})`}>
@ -58,7 +58,7 @@ const formatDate = (dt: Date) => {
<h4 class="alert-heading">{article.title}</h4> <h4 class="alert-heading">{article.title}</h4>
<h6>{formatDate(article.created_at)}</h6> <h6>{formatDate(article.created_at)}</h6>
<p data-article={article.message} /> <p data-article={article.message} />
{user.is_admin ? ( {user.is_admin || user.is_moderator ? (
<hr /> <hr />
<div class="d-flex gap-3"> <div class="d-flex gap-3">
<button class="btn btn-sm btn-danger flex-grow-1" onclick={`deleteArticle(${article.id})`}> <button class="btn btn-sm btn-danger flex-grow-1" onclick={`deleteArticle(${article.id})`}>
@ -79,7 +79,7 @@ const formatDate = (dt: Date) => {
</div> </div>
} }
</div> </div>
{user.is_admin ? {user.is_admin || user.is_moderator ?
<script is:inline> <script is:inline>
async function deleteArticle(articleId) { async function deleteArticle(articleId) {
try { try {

View file

@ -113,6 +113,18 @@ export async function updateUserLore(login: string, newLore: string) {
return user; return user;
} }
export async function makeUserModerator(login: string) {
const user = await client.users.update({
where: {
login: login,
},
data: {
is_moderator: true,
},
});
return user;
}
export async function deleteUser(login: string) { export async function deleteUser(login: string) {
const user = await client.users.delete({ const user = await client.users.delete({
where: { where: {

View file

@ -7,7 +7,7 @@ import type { news } from "@prisma/client";
const sessId = Astro.cookies.get("session").value!; const sessId = Astro.cookies.get("session").value!;
const user = await getSessionUser(sessId); const user = await getSessionUser(sessId);
if (user === null || !user.is_admin) { if (user === null || (!user.is_admin && !user.is_moderator)) {
return Astro.redirect("/login"); return Astro.redirect("/login");
} }
@ -20,7 +20,7 @@ if (articleId) {
<Layout title="Создание новости"> <Layout title="Создание новости">
<main data-article={article?.message} data-articleId={articleId}> <main data-article={article?.message} data-articleId={articleId}>
<Navbar is_user_admin={user.is_admin} /> <Navbar is_user_admin={user.is_admin} user={user} />
<div class="container mt-5" style="max-width: 650px;"> <div class="container mt-5" style="max-width: 650px;">
<div class="d-flex"> <div class="d-flex">
<button type="button" class="btn btn-sm btn-success flex-fill" id="saveBtn">Сохранить</button> <button type="button" class="btn btn-sm btn-success flex-fill" id="saveBtn">Сохранить</button>

View file

@ -1,11 +1,17 @@
import type { APIContext } from "astro"; import type { APIContext } from "astro";
import { createArticle, updateArticle } from "../../db"; import { createArticle, updateArticle, getSessionUser } from "../../db";
export async function post({ request, url }: APIContext) { export async function post({ request, cookies }: APIContext) {
const response: { ok: boolean; reason?: string } = { const response: { ok: boolean; reason?: string } = {
ok: true, ok: true,
}; };
try { try {
const sessId = cookies.get("session").value!;
const user = (await getSessionUser(sessId))!;
if (!user.is_admin && !user.is_moderator) {
throw new Error("Доступно только администраторам или модераторам");
}
const fd = await request.formData(); const fd = await request.formData();
const title = fd.get("title"); const title = fd.get("title");
const data = fd.get("data"); const data = fd.get("data");

View file

@ -1,11 +1,17 @@
import type { APIContext } from "astro"; import type { APIContext } from "astro";
import { deleteArticle } from "../../db"; import { deleteArticle, getSessionUser } from "../../db";
export async function post({ request, url }: APIContext) { export async function post({ request, url, cookies }: APIContext) {
const response: { ok: boolean; reason?: string } = { const response: { ok: boolean; reason?: string } = {
ok: true, ok: true,
}; };
try { try {
const sessId = cookies.get("session").value!;
const user = (await getSessionUser(sessId))!;
if (!user.is_admin && !user.is_moderator) {
throw new Error("Доступно только администраторам или модераторам");
}
const postId = parseInt(url.searchParams.get("id") ?? ""); const postId = parseInt(url.searchParams.get("id") ?? "");
if (!postId) { if (!postId) {
throw new Error("Неправильный формат запроса"); throw new Error("Неправильный формат запроса");

View file

@ -24,9 +24,9 @@ const user = (await getSessionUser(sessId))!;
<Layout title="Пользователи"> <Layout title="Пользователи">
<main> <main>
<Navbar is_user_admin={user.is_admin} /> <Navbar is_user_admin={user.is_admin} user={user} />
{user.is_admin && <StudyItemsList />} {(user.is_admin || user.is_moderator) && <StudyItemsList />}
{user.is_admin && <TimetableEditor />} {(user.is_admin || user.is_moderator) && <TimetableEditor />}
{!user.is_admin && <TimetableViewer user={user} />} {!user.is_admin && !user.is_moderator && <TimetableViewer user={user} />}
</main> </main>
</Layout> </Layout>

View file

@ -9,8 +9,8 @@ export async function post({ request, cookies }: APIContext) {
try { try {
const sessId = cookies.get("session").value!; const sessId = cookies.get("session").value!;
const user = (await getSessionUser(sessId))!; const user = (await getSessionUser(sessId))!;
if (!user.is_admin) { if (!user.is_admin && !user.is_moderator) {
throw new Error("Доступно только администраторам"); throw new Error("Доступно только администраторам или модераторам");
} }
const formData = await request.formData(); const formData = await request.formData();

View file

@ -9,8 +9,8 @@ export async function post({ request, cookies }: APIContext) {
try { try {
const sessId = cookies.get("session").value!; const sessId = cookies.get("session").value!;
const user = (await getSessionUser(sessId))!; const user = (await getSessionUser(sessId))!;
if (!user.is_admin) { if (!user.is_admin && !user.is_moderator) {
throw new Error("Доступно только администраторам"); throw new Error("Доступно только администраторам или модераторам");
} }
const formData = await request.formData(); const formData = await request.formData();

View file

@ -15,8 +15,8 @@ export async function post({ request, cookies }: APIContext) {
try { try {
const sessId = cookies.get("session").value!; const sessId = cookies.get("session").value!;
const user = (await getSessionUser(sessId))!; const user = (await getSessionUser(sessId))!;
if (!user.is_admin) { if (!user.is_admin && !user.is_moderator) {
throw new Error("Доступно только администраторам"); throw new Error("Доступно только администраторам или модераторам");
} }
const data: UpdateTTModel = await request.json(); const data: UpdateTTModel = await request.json();

View file

@ -31,7 +31,7 @@ const isCurrentUser = user.id === openedUser.id;
<Layout title={`Пользователь ${openedUser.fullName ?? openedUser.login}`}> <Layout title={`Пользователь ${openedUser.fullName ?? openedUser.login}`}>
<main data-lore={openedUser.lore} data-login={openedUser.login}> <main data-lore={openedUser.lore} data-login={openedUser.login}>
<Navbar is_user_admin={user.is_admin} /> <Navbar is_user_admin={user.is_admin} user={user} />
<div class="container mt-4 d-flex flex-column gap-4"> <div class="container mt-4 d-flex flex-column gap-4">
<div> <div>
<label for="login" class="form-label">Логин</label> <label for="login" class="form-label">Логин</label>
@ -42,13 +42,13 @@ const isCurrentUser = user.id === openedUser.id;
{ {
isCurrentUser ? ( isCurrentUser ? (
<div class="d-flex flex-row gap-4"> <div class="d-flex flex-row gap-4">
<input type="text" class="form-control" id="fullName" name="fullName" value={openedUser.fullName ?? "Не установлено"} /> <input type="text" class="form-control" id="fullName" name="fullName" value={openedUser.fullName ?? ""} placeholder="Не установлено" />
<button class="btn btn-warning" onclick={`changeName("${user.login}")`}> <button class="btn btn-warning" onclick={`changeName("${user.login}")`}>
Изменить Изменить
</button> </button>
</div> </div>
) : ( ) : (
<input type="email" class="form-control" id="fullName" name="fullName" value={openedUser.fullName ?? "Не установлено"} readonly /> <input type="email" class="form-control" id="fullName" name="fullName" value={openedUser.fullName ?? ""} placeholder="Не установлено" readonly />
) )
} }
</div> </div>

View file

@ -0,0 +1,42 @@
import type { APIContext } from "astro";
import { makeUserModerator, getSessionUser } from "../../db";
import { Prisma } from "@prisma/client";
export async function post({ request, cookies }: APIContext) {
const response: { ok: boolean; reason?: string } = {
ok: true,
};
try {
const sessId = cookies.get("session").value!;
const user = (await getSessionUser(sessId))!;
if (!user.is_admin) {
throw new Error("Доступно только администраторам");
}
const formData = await request.formData();
const login = formData.get("login");
if (login === null) {
throw new Error("Не предоставлены данные для наделения полномочиями модератора");
}
const updatedUser = await makeUserModerator(login.toString());
if (updatedUser === null) {
throw new Error("Не удалось наделить полномочиями модератора");
}
response.ok = true;
} catch (e: any) {
response.ok = false;
if (e instanceof Prisma.PrismaClientKnownRequestError) {
response.reason = `Неизвестная ошибка базы данных. Код ${e.code}`;
} else if (e instanceof Error) {
response.reason = e.message.trim();
} else {
response.reason = e.toString().trim();
}
}
return {
body: JSON.stringify(response),
};
}

View file

@ -9,8 +9,8 @@ export async function post({ request, cookies }: APIContext) {
try { try {
const sessId = cookies.get("session").value!; const sessId = cookies.get("session").value!;
const user = (await getSessionUser(sessId))!; const user = (await getSessionUser(sessId))!;
if (!user.is_admin) { if (!user.is_admin && !user.is_moderator) {
throw new Error("Доступно только администраторам"); throw new Error("Доступно только администраторам или модераторам");
} }
const formData = await request.formData(); const formData = await request.formData();

View file

@ -20,17 +20,16 @@ const user = (await getSessionUser(sessId))!;
const sLogin = Astro.url.searchParams.get("login"); const sLogin = Astro.url.searchParams.get("login");
const sFullName = Astro.url.searchParams.get("fullName"); const sFullName = Astro.url.searchParams.get("fullName");
const sIsAdmin = Astro.url.searchParams.get("isAdmin");
const users = await searchUsers({ const users = await searchUsers({
login: sLogin ? sLogin : undefined, login: sLogin ? sLogin : undefined,
fullName: sFullName ? sFullName : undefined, fullName: sFullName ? sFullName : undefined,
isAdmin: sIsAdmin ? sIsAdmin === "isAdmin" : undefined, isAdmin: undefined,
}); });
--- ---
<Layout title="Пользователи"> <Layout title="Пользователи">
<main> <main>
<Navbar is_user_admin={user.is_admin} /> <Navbar is_user_admin={user.is_admin} user={user} />
<div class="container mt-4 d-flex flex-column gap-4"> <div class="container mt-4 d-flex flex-column gap-4">
<form class="mb-4" method="GET" action="/users"> <form class="mb-4" method="GET" action="/users">
<div class="mb-2"> <div class="mb-2">
@ -41,21 +40,11 @@ const users = await searchUsers({
<label for="fullName" class="form-label">Ф.И.О.</label> <label for="fullName" class="form-label">Ф.И.О.</label>
<input type="text" class="form-control form-control-sm" name="fullName" id="fullName" value={Astro.url.searchParams.get("fullName")} /> <input type="text" class="form-control form-control-sm" name="fullName" id="fullName" value={Astro.url.searchParams.get("fullName")} />
</div> </div>
<div class="mb-2">
{
sIsAdmin === "isAdmin" ? (
<input class="form-check-input" type="checkbox" value="isAdmin" name="isAdmin" id="isAdmin" checked />
) : (
<input class="form-check-input" type="checkbox" value="isAdmin" name="isAdmin" id="isAdmin" />
)
}
<label class="form-check-label" for="isAdmin">Администратор</label>
</div>
<button type="submit" class="btn btn-sm btn-warning w-100">Найти</button> <button type="submit" class="btn btn-sm btn-warning w-100">Найти</button>
</form> </form>
{ {
users.map((e) => ( users.map((e) => (
<div class="card flex-grow-1"> <div class="card flex-grow-1 border border-5">
<div class="card-body"> <div class="card-body">
<h5 class="card-title">{e.fullName}</h5> <h5 class="card-title">{e.fullName}</h5>
<h6 class="card-subtitle mb-2 text-muted">{e.login}</h6> <h6 class="card-subtitle mb-2 text-muted">{e.login}</h6>
@ -63,17 +52,24 @@ const users = await searchUsers({
<a href={`/user/${e.login}`} class="btn btn-primary btn-sm"> <a href={`/user/${e.login}`} class="btn btn-primary btn-sm">
Открыть профиль Открыть профиль
</a> </a>
{user.is_admin ? ( {(user.is_admin || user.is_moderator) && !e.is_admin ? (
<button type="button" class="btn btn-primary btn-sm" onclick={`doChangePassword("${e.login}")`}> <button type="button" class="btn btn-primary btn-sm" onclick={`doChangePassword("${e.login}")`}>
Изменить пароль Изменить пароль
</button> </button>
<a href={`/timetable?userId=${e.id}`} class="btn btn-primary btn-sm"> <a href={`/timetable?login=${e.login}`} class="btn btn-primary btn-sm">
Редактировать расписание Редактировать расписание
</a> </a>
) : null}
{user.is_admin && e.id !== user.id && !e.is_admin ? (
<button type="button" class="btn btn-danger btn-sm" onclick={`doDeleteUser("${e.login}")`}> <button type="button" class="btn btn-danger btn-sm" onclick={`doDeleteUser("${e.login}")`}>
Удалить пользователя Удалить пользователя
</button> </button>
) : null} ) : null}
{user.is_admin && !e.is_admin && !e.is_moderator ? (
<button type="button" class="btn btn-warning btn-sm" onclick={`doMakeModerator("${e.login}")`}>
Назначить модератором
</button>
) : null}
</div> </div>
</div> </div>
</div> </div>
@ -111,6 +107,29 @@ const users = await searchUsers({
} }
} }
} }
async function doMakeModerator(login) {
try {
const fd = new FormData();
fd.append("login", login);
const resp = await fetch("/userapi/makeUserModerator", {
method: "POST",
body: fd,
});
const json = await resp.json();
if (json.ok) {
alert("Успех");
} else {
throw new Error(json.reason);
}
} catch (e) {
console.error(e);
if (e instanceof Error) {
alert(e.message);
} else {
alert("Неизвестная ошибка");
}
}
}
async function doDeleteUser(login) { async function doDeleteUser(login) {
const confirmLogin = prompt(`Это действие невозможно отменить. \nВведите логин пользователя '${login}' для подтверждения`); const confirmLogin = prompt(`Это действие невозможно отменить. \nВведите логин пользователя '${login}' для подтверждения`);