Добавил изменение ФИО и инфы о пользователе
This commit is contained in:
parent
c8bc1c745d
commit
5c72b569da
6 changed files with 403 additions and 2 deletions
|
|
@ -58,6 +58,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)
|
||||||
|
lore String? @db.Text()
|
||||||
timetable timetable[]
|
timetable timetable[]
|
||||||
user_session user_session[]
|
user_session user_session[]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
39
src/db.ts
39
src/db.ts
|
|
@ -56,6 +56,15 @@ export async function getUserFromAuth(login: string, password: string) {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getUserFromLogin(login: string) {
|
||||||
|
const user = await client.users.findFirst({
|
||||||
|
where: {
|
||||||
|
login: login,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
export async function createUser(login: string, password: string) {
|
export async function createUser(login: string, password: string) {
|
||||||
const anyAdmins = await isThereAnyAdmins();
|
const anyAdmins = await isThereAnyAdmins();
|
||||||
const user = await client.users.create({
|
const user = await client.users.create({
|
||||||
|
|
@ -80,6 +89,30 @@ export async function updateUserPassword(login: string, newPassword: string) {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateUserFullName(login: string, newFullName: string) {
|
||||||
|
const user = await client.users.update({
|
||||||
|
where: {
|
||||||
|
login: login,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
fullName: newFullName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateUserLore(login: string, newLore: string) {
|
||||||
|
const user = await client.users.update({
|
||||||
|
where: {
|
||||||
|
login: login,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
lore: newLore,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
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: {
|
||||||
|
|
@ -173,7 +206,11 @@ export async function searchUsers(params: { login?: string; isAdmin?: boolean; f
|
||||||
contains: params.login,
|
contains: params.login,
|
||||||
},
|
},
|
||||||
is_admin: params.isAdmin,
|
is_admin: params.isAdmin,
|
||||||
fullName: params.fullName,
|
fullName: params.fullName
|
||||||
|
? {
|
||||||
|
contains: params.fullName,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
id: "asc",
|
id: "asc",
|
||||||
|
|
|
||||||
275
src/pages/user/[user].astro
Normal file
275
src/pages/user/[user].astro
Normal file
|
|
@ -0,0 +1,275 @@
|
||||||
|
---
|
||||||
|
import Layout from "../../layouts/Layout.astro";
|
||||||
|
|
||||||
|
import { getUserSession, getSessionUser, getUserFromLogin } from "../../db";
|
||||||
|
import Navbar from "../../components/Navbar.astro";
|
||||||
|
|
||||||
|
if (Astro.cookies.has("session")) {
|
||||||
|
const sessId = Astro.cookies.get("session").value!;
|
||||||
|
const dbSess = await getUserSession(sessId);
|
||||||
|
if (dbSess === null) {
|
||||||
|
Astro.cookies.delete("session");
|
||||||
|
return Astro.redirect("/login");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Astro.redirect("/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessId = Astro.cookies.get("session").value!;
|
||||||
|
const user = (await getSessionUser(sessId))!;
|
||||||
|
if (user === null) {
|
||||||
|
return Astro.redirect("/");
|
||||||
|
}
|
||||||
|
|
||||||
|
const openedUser = await getUserFromLogin(Astro.params.user ?? "");
|
||||||
|
if (openedUser === null) {
|
||||||
|
return Astro.redirect("/users");
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCurrentUser = user.id === openedUser.id;
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title={`Пользователь ${openedUser.fullName ?? openedUser.login}`}>
|
||||||
|
<main data-lore={openedUser.lore} data-login={openedUser.login}>
|
||||||
|
<Navbar is_user_admin={user.is_admin} />
|
||||||
|
<div class="container mt-4 d-flex flex-column gap-4">
|
||||||
|
<div>
|
||||||
|
<label for="login" class="form-label">Логин</label>
|
||||||
|
<input type="text" class="form-control" id="login" name="login" value={openedUser.login} readonly />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label for="fullName" class="form-label">Ф.И.О.</label>
|
||||||
|
{
|
||||||
|
isCurrentUser ? (
|
||||||
|
<div class="d-flex flex-row gap-4">
|
||||||
|
<input type="text" class="form-control" id="fullName" name="fullName" value={openedUser.fullName ?? "Не установлено"} />
|
||||||
|
<button class="btn btn-warning" onclick={`changeName("${user.login}")`}>
|
||||||
|
Изменить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<input type="email" class="form-control" id="fullName" name="fullName" value={openedUser.fullName ?? "Не установлено"} readonly />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div id="editorjs" class="mt-4">
|
||||||
|
<label>Обо мне</label>
|
||||||
|
{
|
||||||
|
isCurrentUser ?
|
||||||
|
<button type="button" class="btn btn-sm btn-success ms-3" id="saveBtn">Сохранить</button>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
<div id="loreRender"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@editorjs/header@latest"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@editorjs/quote@latest"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@editorjs/checklist@latest"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@editorjs/list@latest"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@editorjs/embed@latest"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@editorjs/image@latest"></script>
|
||||||
|
<script src ="https://cdn.jsdelivr.net/npm/editorjs-html@latest/build/edjsHTML.js"></script>
|
||||||
|
{
|
||||||
|
isCurrentUser ?
|
||||||
|
<script is:inline>
|
||||||
|
async function saveUserInfo(login, userLore) {
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.append("login", login);
|
||||||
|
fd.append("userLore", JSON.stringify(userLore));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch("/userapi/updateUserLore", {
|
||||||
|
method: "POST",
|
||||||
|
body: fd,
|
||||||
|
});
|
||||||
|
const json = await resp.json();
|
||||||
|
if (json.ok) {
|
||||||
|
location.reload()
|
||||||
|
} else {
|
||||||
|
throw new Error(json.reason);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.group("Ошибка сохранения информации");
|
||||||
|
console.error(e);
|
||||||
|
console.groupEnd();
|
||||||
|
alert("Не удалось сохранить! \nПроверьте консоль для подробностей");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
const editor = new EditorJS({
|
||||||
|
holder: "editorjs",
|
||||||
|
placeholder: "Начните писать",
|
||||||
|
tools: {
|
||||||
|
header: Header,
|
||||||
|
quote: Quote,
|
||||||
|
checklist: Checklist,
|
||||||
|
list: List,
|
||||||
|
embed: Embed,
|
||||||
|
image: {
|
||||||
|
class: ImageTool,
|
||||||
|
title: "Изображение",
|
||||||
|
config: {
|
||||||
|
endpoints: {
|
||||||
|
byFile: "/uploadFile",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
i18n: {
|
||||||
|
messages: {
|
||||||
|
ui: {
|
||||||
|
blockTunes: {
|
||||||
|
toggler: {
|
||||||
|
"Click to tune": "Нажмите, чтобы настроить",
|
||||||
|
"or drag to move": "или перетащите",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
inlineToolbar: {
|
||||||
|
converter: {
|
||||||
|
"Convert to": "Конвертировать в",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toolbar: {
|
||||||
|
toolbox: {
|
||||||
|
Add: "Добавить",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
toolNames: {
|
||||||
|
Text: "Параграф",
|
||||||
|
Heading: "Заголовок",
|
||||||
|
List: "Список",
|
||||||
|
Warning: "Примечание",
|
||||||
|
Checklist: "Чеклист",
|
||||||
|
Quote: "Цитата",
|
||||||
|
Code: "Код",
|
||||||
|
Delimiter: "Разделитель",
|
||||||
|
"Raw HTML": "HTML-фрагмент",
|
||||||
|
Table: "Таблица",
|
||||||
|
Link: "Ссылка",
|
||||||
|
Marker: "Маркер",
|
||||||
|
Bold: "Полужирный",
|
||||||
|
Italic: "Курсив",
|
||||||
|
InlineCode: "Моноширинный",
|
||||||
|
Image: "Изображение",
|
||||||
|
},
|
||||||
|
|
||||||
|
tools: {
|
||||||
|
link: {
|
||||||
|
"Add a link": "Вставьте ссылку",
|
||||||
|
},
|
||||||
|
stub: {
|
||||||
|
"The block can not be displayed correctly.": "Блок не может быть отображен",
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
"Couldn’t upload image. Please try another.": "Не удалось загрузить изображение. Попробуйте ещё раз.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
blockTunes: {
|
||||||
|
delete: {
|
||||||
|
Delete: "Удалить",
|
||||||
|
},
|
||||||
|
moveUp: {
|
||||||
|
"Move up": "Переместить вверх",
|
||||||
|
},
|
||||||
|
moveDown: {
|
||||||
|
"Move down": "Переместить вниз",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const userLogin = document.querySelector("[data-login]").dataset["login"];
|
||||||
|
|
||||||
|
document.getElementById("saveBtn").addEventListener("click", async () => {
|
||||||
|
const userLore = await editor.save();
|
||||||
|
saveUserInfo(userLogin, userLore);
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.isReady.then(() => {
|
||||||
|
try {
|
||||||
|
const article = JSON.parse(document.querySelector("[data-lore]").dataset["lore"]);
|
||||||
|
if (article && article.blocks) {
|
||||||
|
editor.render(article);
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script> : <script is:inline>
|
||||||
|
function checklist(data) {
|
||||||
|
const items = data.data.items.reduce(
|
||||||
|
(acc, item) => acc + `<div class="form-check"><input class="form-check-input" type="checkbox"${item.checked ? " checked" : ""} disabled/><label class="form-check-label">${item.text}</label></div>`,
|
||||||
|
""
|
||||||
|
);
|
||||||
|
return `<div>${items}</div>`;
|
||||||
|
}
|
||||||
|
function quote(data) {
|
||||||
|
const {text, caption} = data.data;
|
||||||
|
if (caption && caption.length > 0) {
|
||||||
|
return `<figure><blockquote class="blockquote"><p>${text}</p></blockquote><figcaption class="blockquote-footer">${caption}</figcaption></figure>`;
|
||||||
|
}
|
||||||
|
return `<blockquote class="blockquote"><p>${text}</p></blockquote>`;
|
||||||
|
}
|
||||||
|
function init() {
|
||||||
|
const edjsParser = edjsHTML({ checklist, quote });
|
||||||
|
try {
|
||||||
|
const article = JSON.parse(document.querySelector("[data-lore]").dataset["lore"]);
|
||||||
|
if (article) {
|
||||||
|
document.getElementById("loreRender").innerHTML = edjsParser.parse(article).reduce((a,b) => a+b, "");
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
</script> }
|
||||||
|
|
||||||
|
<script is:inline>
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
try {
|
||||||
|
init();
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</Layout>
|
||||||
|
|
||||||
|
{
|
||||||
|
isCurrentUser ?
|
||||||
|
<script is:inline>
|
||||||
|
async function changeName(login) {
|
||||||
|
const newFullName = document.getElementById("fullName");
|
||||||
|
if (!newFullName.value) {
|
||||||
|
alert("Введите Ф.И.О.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.append("login", login);
|
||||||
|
fd.append("fullName", newFullName.value);
|
||||||
|
const resp = await fetch("/userapi/updateFullName", {
|
||||||
|
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("Неизвестная ошибка");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script> : null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
44
src/pages/userapi/updateFullName.ts
Normal file
44
src/pages/userapi/updateFullName.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import type { APIContext } from "astro";
|
||||||
|
import { updateUserFullName, 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))!;
|
||||||
|
|
||||||
|
const formData = await request.formData();
|
||||||
|
const login = formData.get("login");
|
||||||
|
const fullName = formData.get("fullName");
|
||||||
|
if (login === null || fullName === null) {
|
||||||
|
throw new Error("Не предоставлены данные для обновления Ф.И.О.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.login !== login.toString()) {
|
||||||
|
throw new Error("Доступно только этому пользователю");
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedUser = await updateUserFullName(login.toString(), fullName.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),
|
||||||
|
};
|
||||||
|
}
|
||||||
44
src/pages/userapi/updateUserLore.ts
Normal file
44
src/pages/userapi/updateUserLore.ts
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
import type { APIContext } from "astro";
|
||||||
|
import { updateUserLore, 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))!;
|
||||||
|
|
||||||
|
const formData = await request.formData();
|
||||||
|
const login = formData.get("login");
|
||||||
|
const userLore = formData.get("userLore");
|
||||||
|
if (login === null || userLore === null) {
|
||||||
|
throw new Error("Не предоставлены данные для обновления информации");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.login !== login.toString()) {
|
||||||
|
throw new Error("Доступно только этому пользователю");
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedUser = await updateUserLore(login.toString(), userLore.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),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -65,7 +65,7 @@ const users = await searchUsers({
|
||||||
<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={`/user/${e.id}`} class="btn btn-primary btn-sm">
|
<a href={`/user/${e.login}`} class="btn btn-primary btn-sm">
|
||||||
Открыть профиль
|
Открыть профиль
|
||||||
</a>
|
</a>
|
||||||
<a href={`/timetable?userId=${e.id}`} class="btn btn-primary btn-sm">
|
<a href={`/timetable?userId=${e.id}`} class="btn btn-primary btn-sm">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue