Добавил систему чатов
This commit is contained in:
parent
be1d03b459
commit
f2a6469a55
10 changed files with 632 additions and 7 deletions
|
|
@ -48,8 +48,11 @@ model users {
|
||||||
fullName String? @db.VarChar(100)
|
fullName String? @db.VarChar(100)
|
||||||
is_admin Boolean @default(false)
|
is_admin Boolean @default(false)
|
||||||
lore String? @db.Text()
|
lore String? @db.Text()
|
||||||
|
|
||||||
timetable timetable[]
|
timetable timetable[]
|
||||||
user_session user_session[]
|
user_session user_session[]
|
||||||
|
chat_message chat_message[]
|
||||||
|
user_in_chat user_in_chat[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model user_session {
|
model user_session {
|
||||||
|
|
@ -58,3 +61,33 @@ model user_session {
|
||||||
|
|
||||||
user users @relation(fields: [usersId], references: [id])
|
user users @relation(fields: [usersId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model chat {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
title String
|
||||||
|
|
||||||
|
chat_message chat_message[]
|
||||||
|
user_in_chat user_in_chat[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model chat_message {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
text String
|
||||||
|
|
||||||
|
user users @relation(fields: [userId], references: [id])
|
||||||
|
chat chat @relation(fields: [chatId], references: [id])
|
||||||
|
|
||||||
|
sendAt DateTime @default(now())
|
||||||
|
|
||||||
|
chatId String
|
||||||
|
userId Int
|
||||||
|
}
|
||||||
|
|
||||||
|
model user_in_chat {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
chatId String
|
||||||
|
userId Int
|
||||||
|
|
||||||
|
chat chat @relation(fields: [chatId], references: [id])
|
||||||
|
user users @relation(fields: [userId], references: [id])
|
||||||
|
}
|
||||||
|
|
|
||||||
31
src/components/ChatList.astro
Normal file
31
src/components/ChatList.astro
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
---
|
||||||
|
import type { users, timetable } from "@prisma/client";
|
||||||
|
import { getUserChats } from "../db";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
user: users;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user } = Astro.props;
|
||||||
|
|
||||||
|
const chats = await getUserChats(user.id);
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="col-4 border-end">
|
||||||
|
<ol class="list-group list-group-flush">
|
||||||
|
<a class="list-group-item d-flex justify-content-between align-items-start" role="button" href="/newChat">
|
||||||
|
<div class="ms-2 me-auto">
|
||||||
|
<div class="fw-bold">+ Создать чат</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{
|
||||||
|
chats.map((e) => (
|
||||||
|
<a class="list-group-item d-flex justify-content-between align-items-start" role="button" href={`/chats?openChat=${e.id}`}>
|
||||||
|
<div class="ms-2 me-auto">
|
||||||
|
<div class="fw-bold">{e.title}</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
195
src/components/ChatView.astro
Normal file
195
src/components/ChatView.astro
Normal file
|
|
@ -0,0 +1,195 @@
|
||||||
|
---
|
||||||
|
import type { users } from "@prisma/client";
|
||||||
|
import type { CompositeChat } from "../db";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
user: users;
|
||||||
|
chat: CompositeChat;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { user, chat } = Astro.props;
|
||||||
|
|
||||||
|
const formatDate = (dt: Date) => {
|
||||||
|
return (
|
||||||
|
("0" + dt.getDay()).substr(-2) +
|
||||||
|
"." +
|
||||||
|
("0" + (0 + dt.getMonth())).substr(-2) +
|
||||||
|
"." +
|
||||||
|
dt.getFullYear() +
|
||||||
|
" в " +
|
||||||
|
dt.getHours() +
|
||||||
|
":" +
|
||||||
|
("0" + dt.getMinutes()).substr(-2)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let lastMessageAt: Date | null = null;
|
||||||
|
if (chat.messages.length > 0) {
|
||||||
|
lastMessageAt = chat.messages[chat.messages.length - 1].sendAt;
|
||||||
|
lastMessageAt.setSeconds(lastMessageAt.getSeconds() + 1);
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="col d-flex flex-column" data-chatId={chat.chat.id}>
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<a class="navbar-brand" href="#">{chat.chat.title}</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div id="chatHolder" class="flex-grow-1" data-lastMessageAt={lastMessageAt}>
|
||||||
|
{
|
||||||
|
chat.messages.map((e) => (
|
||||||
|
<div class="list-group m-1" data-messageId={e.id}>
|
||||||
|
<div class="list-group-item list-group-item-action">
|
||||||
|
<p class="mb-1">{e.text}</p>
|
||||||
|
<small class="text-muted">
|
||||||
|
{e.user.fullName ?? e.user.login} • {formatDate(e.sendAt)}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div class="container-fluid bg-light p-4 d-flex flex-row gap-2">
|
||||||
|
<input type="text" class="form-control" id="messageText" placeholder="Введите сообщение" />
|
||||||
|
<button type="button" class="btn btn-primary" id="sendMessage">Отправить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import type { CompositeChat } from "../db";
|
||||||
|
|
||||||
|
const attr = (attrName: string) => document.querySelector(`[${attrName}]`)!.getAttribute(attrName)!;
|
||||||
|
const chatId = () => attr("data-chatId");
|
||||||
|
const lastMessageAt = () => new Date(attr("data-lastMessageAt"));
|
||||||
|
|
||||||
|
const formatDate = (dt: Date) => {
|
||||||
|
return (
|
||||||
|
("0" + dt.getDay()).substr(-2) +
|
||||||
|
"." +
|
||||||
|
("0" + (0 + dt.getMonth())).substr(-2) +
|
||||||
|
"." +
|
||||||
|
dt.getFullYear() +
|
||||||
|
" в " +
|
||||||
|
dt.getHours() +
|
||||||
|
":" +
|
||||||
|
("0" + dt.getMinutes()).substr(-2)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderMessageTemplate = (messageId: string, text: string, sender: string, sendAt: Date) => `<div class="list-group m-1" data-messageId=${messageId}>
|
||||||
|
<div class="list-group-item list-group-item-action">
|
||||||
|
<p class="mb-1">${text}</p>
|
||||||
|
<small class="text-muted">
|
||||||
|
${sender} • ${formatDate(sendAt)}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
async function renderMessages(data: CompositeChat) {
|
||||||
|
const chatHolder = document.getElementById("chatHolder")!;
|
||||||
|
|
||||||
|
const lastMessage = chatHolder.querySelectorAll(`[data-messageId]`).item(chatHolder.childElementCount - 1);
|
||||||
|
const lastMessageId = lastMessage !== null ? lastMessage.getAttribute("data-messageId") : null;
|
||||||
|
|
||||||
|
for (const message of data.messages) {
|
||||||
|
if (lastMessageId !== null && message.id === lastMessageId) continue;
|
||||||
|
const sender = message.user.fullName ?? message.user.login;
|
||||||
|
const messageHtml = renderMessageTemplate(message.id, message.text, sender, new Date(message.sendAt));
|
||||||
|
chatHolder.innerHTML += messageHtml;
|
||||||
|
}
|
||||||
|
|
||||||
|
chatHolder.scrollTop = chatHolder.scrollHeight;
|
||||||
|
|
||||||
|
const lastMessageInPayload = data.messages[data.messages.length - 1];
|
||||||
|
if (lastMessageInPayload === undefined || lastMessageInPayload === null) return;
|
||||||
|
|
||||||
|
const lastMessageAt = new Date(lastMessageInPayload.sendAt);
|
||||||
|
chatHolder.setAttribute("data-lastMessageAt", lastMessageAt.toISOString());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchMessages() {
|
||||||
|
try {
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.append("chatId", chatId());
|
||||||
|
fd.append("since", lastMessageAt().toISOString());
|
||||||
|
|
||||||
|
const resp = await fetch("/chatapi/getMessages", {
|
||||||
|
method: "POST",
|
||||||
|
body: fd,
|
||||||
|
});
|
||||||
|
const json = await resp.json();
|
||||||
|
if (json.ok) {
|
||||||
|
const messagesData: CompositeChat = json.data;
|
||||||
|
renderMessages(messagesData);
|
||||||
|
} else {
|
||||||
|
throw new Error(json.reason);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
if (e instanceof Error) {
|
||||||
|
alert(`Ошибка загрузки сообщений: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendMessage(message: string) {
|
||||||
|
try {
|
||||||
|
const fd = new FormData();
|
||||||
|
fd.append("chatId", chatId());
|
||||||
|
fd.append("message", message);
|
||||||
|
|
||||||
|
const resp = await fetch("/chatapi/sendMessage", {
|
||||||
|
method: "POST",
|
||||||
|
body: fd,
|
||||||
|
});
|
||||||
|
const json = await resp.json();
|
||||||
|
if (json.ok) {
|
||||||
|
fetchMessages();
|
||||||
|
} else {
|
||||||
|
throw new Error(json.reason);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
if (e instanceof Error) {
|
||||||
|
alert(`Ошибка отправки сообщения: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const messageText = document.getElementById("messageText")! as HTMLInputElement;
|
||||||
|
const sendMessageBtn = document.getElementById("sendMessage")!;
|
||||||
|
|
||||||
|
sendMessageBtn.addEventListener("click", () => {
|
||||||
|
const text = messageText.value;
|
||||||
|
if (text.length === 0) return;
|
||||||
|
|
||||||
|
messageText.value = "";
|
||||||
|
messageText.focus();
|
||||||
|
|
||||||
|
sendMessage(text);
|
||||||
|
});
|
||||||
|
|
||||||
|
messageText.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
sendMessageBtn.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const chatHolder = document.getElementById("chatHolder")!;
|
||||||
|
chatHolder.scrollTop = chatHolder.scrollHeight;
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
fetchMessages();
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
#chatHolder {
|
||||||
|
max-height: calc(100vh - 13rem);
|
||||||
|
overflow: hidden;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -18,6 +18,10 @@ const items = [
|
||||||
href: "/users",
|
href: "/users",
|
||||||
title: "Пользователи",
|
title: "Пользователи",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
href: "/chats",
|
||||||
|
title: "Чаты",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const itemsRight = [
|
const itemsRight = [
|
||||||
|
|
|
||||||
106
src/db.ts
106
src/db.ts
|
|
@ -1,4 +1,4 @@
|
||||||
import { PrismaClient, users } from "@prisma/client";
|
import { PrismaClient, chat, chat_message, users } from "@prisma/client";
|
||||||
import { blake3 } from "@noble/hashes/blake3";
|
import { blake3 } from "@noble/hashes/blake3";
|
||||||
import { bytesToHex as toHex } from "@noble/hashes/utils";
|
import { bytesToHex as toHex } from "@noble/hashes/utils";
|
||||||
|
|
||||||
|
|
@ -371,3 +371,107 @@ export async function updateUserTimetable(userLogin: string, oddUpdate: UpdateTT
|
||||||
.reduce((acc, val) => acc.concat(val), []),
|
.reduce((acc, val) => acc.concat(val), []),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getUserChats(userId: number) {
|
||||||
|
const chats = await client.user_in_chat.findMany({
|
||||||
|
where: {
|
||||||
|
userId: userId,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
chat: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return chats.map((x) => x.chat);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CompositeChat = {
|
||||||
|
chat: chat;
|
||||||
|
messages: (chat_message & {
|
||||||
|
user: users;
|
||||||
|
})[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getChatMessages(chatId: string, since?: Date): Promise<CompositeChat> {
|
||||||
|
const chat = (await client.chat.findFirst({
|
||||||
|
where: {
|
||||||
|
id: chatId,
|
||||||
|
},
|
||||||
|
}))!;
|
||||||
|
const messages = await client.chat_message.findMany({
|
||||||
|
where: {
|
||||||
|
chatId: chatId,
|
||||||
|
sendAt: {
|
||||||
|
gt: since,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
sendAt: "asc",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
chat,
|
||||||
|
messages,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createChatMessage(chatId: string, userId: number, message: string) {
|
||||||
|
const msg = await client.chat_message.create({
|
||||||
|
data: {
|
||||||
|
chatId: chatId,
|
||||||
|
userId: userId,
|
||||||
|
text: message,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createChat(title: string, userIds: number[]) {
|
||||||
|
const chat = await client.chat.create({
|
||||||
|
data: {
|
||||||
|
title: title,
|
||||||
|
user_in_chat: {
|
||||||
|
createMany: {
|
||||||
|
data: userIds.map((x) => {
|
||||||
|
return {
|
||||||
|
userId: x,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return chat;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function userAllowedToChat(userId: number, chatId: string) {
|
||||||
|
const chat = await client.user_in_chat.findFirst({
|
||||||
|
where: {
|
||||||
|
chatId: chatId,
|
||||||
|
userId: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return chat !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function addUserToChat(userId: number, chatId: string) {
|
||||||
|
const chat = await client.user_in_chat.create({
|
||||||
|
data: {
|
||||||
|
chatId: chatId,
|
||||||
|
userId: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return chat !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeUserFromChat(userId: number, chatId: string) {
|
||||||
|
const chat = await client.user_in_chat.deleteMany({
|
||||||
|
where: {
|
||||||
|
chatId: chatId,
|
||||||
|
userId: userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return chat !== null;
|
||||||
|
}
|
||||||
|
|
|
||||||
37
src/pages/chatapi/createChat.ts
Normal file
37
src/pages/chatapi/createChat.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import type { APIContext } from "astro";
|
||||||
|
import { getSessionUser, createChat } 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 title = formData.get("title");
|
||||||
|
const selectedUsers = formData.getAll("selectedUsers").map((x) => parseInt(x.toString()));
|
||||||
|
if (title === null || selectedUsers.length === 0 || selectedUsers.find((x) => isNaN(x))) {
|
||||||
|
throw new Error("Предоставлены некорректные данные для создания чата");
|
||||||
|
}
|
||||||
|
|
||||||
|
await createChat(title.toString(), [...selectedUsers, user.id]);
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
43
src/pages/chatapi/getMessages.ts
Normal file
43
src/pages/chatapi/getMessages.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import type { APIContext } from "astro";
|
||||||
|
import { getSessionUser, userAllowedToChat, getChatMessages, CompositeChat } from "../../db";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
|
export async function post({ request, cookies }: APIContext) {
|
||||||
|
const response: { ok: boolean; reason?: string; data?: CompositeChat } = {
|
||||||
|
ok: true,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const sessId = cookies.get("session").value!;
|
||||||
|
const user = (await getSessionUser(sessId))!;
|
||||||
|
|
||||||
|
const formData = await request.formData();
|
||||||
|
const chatId = formData.get("chatId");
|
||||||
|
const since = formData.get("since");
|
||||||
|
if (chatId === null) {
|
||||||
|
throw new Error("Не предоставлены данные получения сообщений");
|
||||||
|
}
|
||||||
|
|
||||||
|
const userAllowed = await userAllowedToChat(user.id, chatId.toString());
|
||||||
|
if (!userAllowed) {
|
||||||
|
throw new Error("Нет доступа к чату");
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = await getChatMessages(chatId.toString(), since === null ? undefined : new Date(since.toString()));
|
||||||
|
|
||||||
|
response.ok = true;
|
||||||
|
response.data = messages;
|
||||||
|
} 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),
|
||||||
|
};
|
||||||
|
}
|
||||||
42
src/pages/chatapi/sendMessage.ts
Normal file
42
src/pages/chatapi/sendMessage.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
import type { APIContext } from "astro";
|
||||||
|
import { getSessionUser, userAllowedToChat, createChatMessage } 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 chatId = formData.get("chatId");
|
||||||
|
const message = formData.get("message");
|
||||||
|
if (chatId === null || message === null) {
|
||||||
|
throw new Error("Не предоставлены данные отправки сообщения");
|
||||||
|
}
|
||||||
|
|
||||||
|
const userAllowed = await userAllowedToChat(user.id, chatId.toString());
|
||||||
|
if (!userAllowed) {
|
||||||
|
throw new Error("Нет доступа к чату");
|
||||||
|
}
|
||||||
|
|
||||||
|
await createChatMessage(chatId.toString(), user.id, message.toString());
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
}
|
||||||
48
src/pages/chats.astro
Normal file
48
src/pages/chats.astro
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
---
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
import Navbar from "../components/Navbar.astro";
|
||||||
|
import ChatList from "../components/ChatList.astro";
|
||||||
|
import ChatView from "../components/ChatView.astro";
|
||||||
|
|
||||||
|
import { getUserSession, getSessionUser, CompositeChat, getChatMessages } from "../db";
|
||||||
|
import type { chat, chat_message, users } from "@prisma/client";
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
let openChat: CompositeChat | null = null;
|
||||||
|
if (Astro.url.searchParams.has("openChat")) {
|
||||||
|
let openChatId = Astro.url.searchParams.get("openChat")!;
|
||||||
|
openChat = await getChatMessages(openChatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sessId = Astro.cookies.get("session").value!;
|
||||||
|
const user = (await getSessionUser(sessId))!;
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title="Чаты">
|
||||||
|
<main class="d-flex flex-column" style="min-height: 100vh;">
|
||||||
|
<Navbar is_user_admin={user.is_admin} />
|
||||||
|
<div class="flex-grow-1 container-fluid m-0 p-0 d-flex flex-row">
|
||||||
|
<ChatList user={user} />
|
||||||
|
{openChat !== null && <ChatView user={user} chat={openChat} />}
|
||||||
|
{
|
||||||
|
openChat === null && (
|
||||||
|
<div class="col flex-grow-1 d-flex text-center align-items-stretch align-self-center">
|
||||||
|
<div class="alert alert-info w-100 m-5" role="alert">
|
||||||
|
Выберите чат из списка слева
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</Layout>
|
||||||
88
src/pages/newChat.astro
Normal file
88
src/pages/newChat.astro
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
---
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
import Navbar from "../components/Navbar.astro";
|
||||||
|
|
||||||
|
import { getUserSession, getSessionUser, searchUsers } from "../db";
|
||||||
|
|
||||||
|
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))!;
|
||||||
|
|
||||||
|
const users = (await searchUsers({})).filter((e) => e.id !== user.id);
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title="Новый чат">
|
||||||
|
<main>
|
||||||
|
<Navbar is_user_admin={user.is_admin} />
|
||||||
|
<div class="container mt-5">
|
||||||
|
<form>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="chatTitle" class="form-label">Название чата</label>
|
||||||
|
<input type="text" class="form-control" id="chatTitle" name="title" />
|
||||||
|
</div>
|
||||||
|
<label for="chatTitle" class="form-label">Кто участвует в чате:</label>
|
||||||
|
<div class="d-flex flex-wrap flex-row">
|
||||||
|
{
|
||||||
|
users.map((e) => (
|
||||||
|
<div class="mb-3 form-check col-2">
|
||||||
|
<input type="checkbox" class="form-check-input" id={`userCheck-${e.login}`} name="selectedUsers" value={e.id} />
|
||||||
|
<label class="form-check-label" for={`userCheck-${e.login}`}>
|
||||||
|
{e.login}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Создать</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function createChat(form: HTMLFormElement) {
|
||||||
|
try {
|
||||||
|
const fd = new FormData(form);
|
||||||
|
if (fd.get("title")?.length === 0) {
|
||||||
|
throw new Error("Не указано название чата");
|
||||||
|
}
|
||||||
|
if (fd.getAll("selectedUsers").length === 0) {
|
||||||
|
throw new Error("Не выбраны пользователи");
|
||||||
|
}
|
||||||
|
|
||||||
|
const resp = await fetch("/chatapi/createChat", {
|
||||||
|
method: "POST",
|
||||||
|
body: fd,
|
||||||
|
});
|
||||||
|
const json = await resp.json();
|
||||||
|
if (json.ok) {
|
||||||
|
alert("Чат создан");
|
||||||
|
window.location.href = "/chats";
|
||||||
|
} else {
|
||||||
|
throw new Error(json.reason);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
if (e instanceof Error) alert(e.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const form = document.querySelector("form");
|
||||||
|
form?.addEventListener("submit", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
createChat(form);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</Layout>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue