mirror of
https://github.com/usatiuk/y.git
synced 2025-10-29 02:37:49 +01:00
editing chats!
This commit is contained in:
@@ -12,6 +12,7 @@ import { Signup } from "./Signup";
|
|||||||
import { Home } from "./Home";
|
import { Home } from "./Home";
|
||||||
import {
|
import {
|
||||||
chatAction,
|
chatAction,
|
||||||
|
editChatAction,
|
||||||
homeAction,
|
homeAction,
|
||||||
loginAction,
|
loginAction,
|
||||||
newChatAction,
|
newChatAction,
|
||||||
@@ -22,6 +23,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
chatListLoader,
|
chatListLoader,
|
||||||
chatLoader,
|
chatLoader,
|
||||||
|
editChatLoader,
|
||||||
feedLoader,
|
feedLoader,
|
||||||
homeLoader,
|
homeLoader,
|
||||||
newChatLoader,
|
newChatLoader,
|
||||||
@@ -34,6 +36,7 @@ import { UserList } from "./UserList";
|
|||||||
import { Chats } from "./Chats";
|
import { Chats } from "./Chats";
|
||||||
import { ChatCreate } from "./ChatCreate";
|
import { ChatCreate } from "./ChatCreate";
|
||||||
import { Chat } from "./Chat";
|
import { Chat } from "./Chat";
|
||||||
|
import { ChatEdit } from "./ChatEdit";
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@@ -71,6 +74,12 @@ const router = createBrowserRouter([
|
|||||||
loader: chatLoader,
|
loader: chatLoader,
|
||||||
action: chatAction,
|
action: chatAction,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "messages/chat/:id/edit",
|
||||||
|
element: <ChatEdit />,
|
||||||
|
loader: editChatLoader,
|
||||||
|
action: editChatAction,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "users",
|
path: "users",
|
||||||
element: <UserList />,
|
element: <UserList />,
|
||||||
|
|||||||
@@ -27,6 +27,10 @@
|
|||||||
margin-right: 1rem;
|
margin-right: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chatHeaderActions {
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
padding-bottom: 0.2rem;
|
padding-bottom: 0.2rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import { useFetcher, useLoaderData, useRevalidator } from "react-router-dom";
|
import {
|
||||||
|
Link,
|
||||||
|
useFetcher,
|
||||||
|
useLoaderData,
|
||||||
|
useRevalidator,
|
||||||
|
} from "react-router-dom";
|
||||||
import { chatLoader, LoaderToType } from "./loaders";
|
import { chatLoader, LoaderToType } from "./loaders";
|
||||||
import { isError } from "./api/dto";
|
import { isError } from "./api/dto";
|
||||||
|
|
||||||
@@ -37,7 +42,14 @@ export function Chat() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"chat"}>
|
<div className={"chat"}>
|
||||||
<div className={"chatHeader"}>{chat.name}</div>
|
<div className={"chatHeader"}>
|
||||||
|
<span className={"chatHeaderName"}>{chat.name}</span>
|
||||||
|
{chat.creatorUuid == getTokenUserUuid() && (
|
||||||
|
<span className={"chatHeaderActions"}>
|
||||||
|
<Link to={"edit"}>edit</Link>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<fetcher.Form className={"messageForm postForm"} method="post">
|
<fetcher.Form className={"messageForm postForm"} method="post">
|
||||||
<textarea placeholder={"Write something!"} name="text" />
|
<textarea placeholder={"Write something!"} name="text" />
|
||||||
<input hidden={true} value={chat.id} name={"chatId"} />
|
<input hidden={true} value={chat.id} name={"chatId"} />
|
||||||
|
|||||||
86
client/src/ChatEdit.tsx
Normal file
86
client/src/ChatEdit.tsx
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import {
|
||||||
|
Form,
|
||||||
|
useActionData,
|
||||||
|
useLoaderData,
|
||||||
|
useNavigation,
|
||||||
|
} from "react-router-dom";
|
||||||
|
import { editChatLoader, LoaderToType } from "./loaders";
|
||||||
|
import { isError } from "./api/dto";
|
||||||
|
import { ActionToType, editChatAction } from "./actions";
|
||||||
|
import { getTokenUserUuid } from "./api/utils";
|
||||||
|
|
||||||
|
import "./ChatCreate.scss";
|
||||||
|
|
||||||
|
export function ChatEdit() {
|
||||||
|
const loaderData = useLoaderData() as LoaderToType<typeof editChatLoader>;
|
||||||
|
|
||||||
|
if (!loaderData) return <div>error</div>;
|
||||||
|
|
||||||
|
const { people, chat, chatMembers } = loaderData;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!people ||
|
||||||
|
isError(people) ||
|
||||||
|
!chat ||
|
||||||
|
isError(chat) ||
|
||||||
|
!chatMembers ||
|
||||||
|
isError(chatMembers)
|
||||||
|
) {
|
||||||
|
return <div>error</div>;
|
||||||
|
}
|
||||||
|
let errors: JSX.Element[] = [];
|
||||||
|
|
||||||
|
const actionData = useActionData() as ActionToType<typeof editChatAction>;
|
||||||
|
|
||||||
|
if (isError(actionData)) {
|
||||||
|
errors = actionData.errors.map((e) => {
|
||||||
|
return <a>{e}</a>;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigation = useNavigation();
|
||||||
|
const busy = navigation.state === "submitting";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={"chatsNew"}>
|
||||||
|
<div className={"errors"}>{errors}</div>
|
||||||
|
<Form method="patch">
|
||||||
|
<input type={"hidden"} name={"chatId"} value={chat.id} />
|
||||||
|
<label htmlFor="fname">Chat name:</label>
|
||||||
|
<input type="text" name="name" defaultValue={chat.name} />
|
||||||
|
<label htmlFor="members">Members:</label>
|
||||||
|
<select
|
||||||
|
multiple
|
||||||
|
defaultValue={chatMembers.map((cme) => cme.uuid)}
|
||||||
|
name={"members"}
|
||||||
|
>
|
||||||
|
{people
|
||||||
|
.filter((p) => p.uuid != getTokenUserUuid())
|
||||||
|
.map((p) => (
|
||||||
|
<option value={p.uuid}>{p.username}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
name={"intent"}
|
||||||
|
value={"update"}
|
||||||
|
disabled={busy}
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</button>
|
||||||
|
</Form>
|
||||||
|
<Form method={"delete"}>
|
||||||
|
<input type={"hidden"} name={"chatId"} value={chat.id} />
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
name={"intent"}
|
||||||
|
value={"delete"}
|
||||||
|
disabled={busy}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@ import { login } from "./api/Token";
|
|||||||
import { isError } from "./api/dto";
|
import { isError } from "./api/dto";
|
||||||
import { deleteToken, getTokenUserUuid, setToken } from "./api/utils";
|
import { deleteToken, getTokenUserUuid, setToken } from "./api/utils";
|
||||||
import { createPost, deletePost, updatePost } from "./api/Post";
|
import { createPost, deletePost, updatePost } from "./api/Post";
|
||||||
import { createChat } from "./api/Chat";
|
import { createChat, deleteChat, updateChat } from "./api/Chat";
|
||||||
import { addMessagesToChat, deleteMessage, editMessage } from "./api/Message";
|
import { addMessagesToChat, deleteMessage, editMessage } from "./api/Message";
|
||||||
|
|
||||||
export type ActionToType<T extends (...args: any) => any> =
|
export type ActionToType<T extends (...args: any) => any> =
|
||||||
@@ -115,6 +115,33 @@ export async function newChatAction({ request }: ActionFunctionArgs) {
|
|||||||
} else return ret;
|
} else return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function editChatAction({ request }: ActionFunctionArgs) {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const intent = formData.get("intent")!.toString();
|
||||||
|
if (intent == "update") {
|
||||||
|
const ret = await updateChat(
|
||||||
|
Number(formData.get("chatId")!.toString()),
|
||||||
|
formData.get("name")!.toString(),
|
||||||
|
[
|
||||||
|
...formData.getAll("members")!.map((p) => p.toString()),
|
||||||
|
getTokenUserUuid()!,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ret && !isError(ret)) {
|
||||||
|
return redirect("/home/messages/chat/" + ret.id);
|
||||||
|
} else return ret;
|
||||||
|
} else if (intent == "delete") {
|
||||||
|
const ret = await deleteChat(
|
||||||
|
Number(formData.get("chatId")!.toString()),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ret && !isError(ret)) {
|
||||||
|
return redirect("/home/messages/chats");
|
||||||
|
} else return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function chatAction({ request }: ActionFunctionArgs) {
|
export async function chatAction({ request }: ActionFunctionArgs) {
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
const intent = formData.get("intent")!.toString();
|
const intent = formData.get("intent")!.toString();
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
import { fetchJSONAuth } from "./utils";
|
import { fetchJSONAuth } from "./utils";
|
||||||
import { ChatsToResp, ChatToResp, TChatsToResp, TChatToResp } from "./dto";
|
import {
|
||||||
|
ChatsToResp,
|
||||||
|
ChatToResp,
|
||||||
|
NoContentToResp,
|
||||||
|
PersonToArrResp,
|
||||||
|
TChatsToResp,
|
||||||
|
TChatToResp,
|
||||||
|
TNoContentToResp,
|
||||||
|
TPersonToArrResp,
|
||||||
|
} from "./dto";
|
||||||
|
|
||||||
export async function getMyChats(): Promise<TChatsToResp> {
|
export async function getMyChats(): Promise<TChatsToResp> {
|
||||||
return fetchJSONAuth("/chat/my", "GET", ChatsToResp);
|
return fetchJSONAuth("/chat/my", "GET", ChatsToResp);
|
||||||
@@ -9,9 +18,32 @@ export async function getChat(id: number): Promise<TChatToResp> {
|
|||||||
return fetchJSONAuth("/chat/by-id/" + id, "GET", ChatToResp);
|
return fetchJSONAuth("/chat/by-id/" + id, "GET", ChatToResp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getMembers(id: number): Promise<TPersonToArrResp> {
|
||||||
|
return fetchJSONAuth(
|
||||||
|
"/chat/by-id/" + id + "/members",
|
||||||
|
"GET",
|
||||||
|
PersonToArrResp,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export async function createChat(
|
export async function createChat(
|
||||||
name: string,
|
name: string,
|
||||||
memberUuids: string[],
|
memberUuids: string[],
|
||||||
): Promise<TChatToResp> {
|
): Promise<TChatToResp> {
|
||||||
return fetchJSONAuth("/chat", "POST", ChatToResp, { name, memberUuids });
|
return fetchJSONAuth("/chat", "POST", ChatToResp, { name, memberUuids });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updateChat(
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
memberUuids: string[],
|
||||||
|
): Promise<TChatToResp> {
|
||||||
|
return fetchJSONAuth("/chat/by-id/" + id, "PATCH", ChatToResp, {
|
||||||
|
name,
|
||||||
|
memberUuids,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteChat(id: number): Promise<TNoContentToResp> {
|
||||||
|
return fetchJSONAuth("/chat/by-id/" + id, "DELETE", NoContentToResp);
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
getPostsByAuthorUuid,
|
getPostsByAuthorUuid,
|
||||||
getPostsByFollowees,
|
getPostsByFollowees,
|
||||||
} from "./api/Post";
|
} from "./api/Post";
|
||||||
import { getChat, getMyChats } from "./api/Chat";
|
import { getChat, getMembers, getMyChats } from "./api/Chat";
|
||||||
import { getMessagesByChat } from "./api/Message";
|
import { getMessagesByChat } from "./api/Message";
|
||||||
|
|
||||||
export type LoaderToType<T extends (...args: any) => any> =
|
export type LoaderToType<T extends (...args: any) => any> =
|
||||||
@@ -86,6 +86,14 @@ export async function newChatLoader() {
|
|||||||
return await getAllPerson();
|
return await getAllPerson();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function editChatLoader({ params }: { params: { id?: number } }) {
|
||||||
|
return {
|
||||||
|
people: await getAllPerson(),
|
||||||
|
chat: await getChat(params.id!),
|
||||||
|
chatMembers: await getMembers(params.id!),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function chatLoader({ params }: { params: { id?: number } }) {
|
export async function chatLoader({ params }: { params: { id?: number } }) {
|
||||||
return {
|
return {
|
||||||
chat: await getChat(params.id!),
|
chat: await getChat(params.id!),
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package com.usatiuk.tjv.y.server.controller;
|
|||||||
|
|
||||||
import com.usatiuk.tjv.y.server.dto.ChatCreateTo;
|
import com.usatiuk.tjv.y.server.dto.ChatCreateTo;
|
||||||
import com.usatiuk.tjv.y.server.dto.ChatTo;
|
import com.usatiuk.tjv.y.server.dto.ChatTo;
|
||||||
|
import com.usatiuk.tjv.y.server.dto.PersonTo;
|
||||||
import com.usatiuk.tjv.y.server.dto.converters.ChatMapper;
|
import com.usatiuk.tjv.y.server.dto.converters.ChatMapper;
|
||||||
|
import com.usatiuk.tjv.y.server.dto.converters.PersonMapper;
|
||||||
import com.usatiuk.tjv.y.server.entity.Chat;
|
import com.usatiuk.tjv.y.server.entity.Chat;
|
||||||
import com.usatiuk.tjv.y.server.entity.Person;
|
import com.usatiuk.tjv.y.server.entity.Person;
|
||||||
import com.usatiuk.tjv.y.server.service.ChatService;
|
import com.usatiuk.tjv.y.server.service.ChatService;
|
||||||
@@ -13,6 +15,7 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
import java.security.Principal;
|
import java.security.Principal;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@@ -23,11 +26,13 @@ public class ChatController {
|
|||||||
private final EntityManager entityManager;
|
private final EntityManager entityManager;
|
||||||
private final ChatService chatService;
|
private final ChatService chatService;
|
||||||
private final ChatMapper chatMapper;
|
private final ChatMapper chatMapper;
|
||||||
|
private final PersonMapper personMapper;
|
||||||
|
|
||||||
public ChatController(EntityManager entityManager, ChatService chatService, ChatMapper chatMapper) {
|
public ChatController(EntityManager entityManager, ChatService chatService, ChatMapper chatMapper, PersonMapper personMapper) {
|
||||||
this.entityManager = entityManager;
|
this.entityManager = entityManager;
|
||||||
this.chatService = chatService;
|
this.chatService = chatService;
|
||||||
this.chatMapper = chatMapper;
|
this.chatMapper = chatMapper;
|
||||||
|
this.personMapper = personMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping
|
@PostMapping
|
||||||
@@ -68,11 +73,41 @@ public class ChatController {
|
|||||||
chatService.deleteById(id);
|
chatService.deleteById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PatchMapping(path = "/by-id/{id}")
|
||||||
|
public ChatTo update(Principal principal, @PathVariable Long id, @RequestBody ChatCreateTo chatCreateTo) {
|
||||||
|
var chat = chatService.readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
|
||||||
|
if (!Objects.equals(chat.getCreator().getUuid(), principal.getName()))
|
||||||
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't creator of the chat");
|
||||||
|
|
||||||
|
if (Arrays.stream(chatCreateTo.memberUuids()).noneMatch(n -> Objects.equals(n, principal.getName())))
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Creator of chat must be its member");
|
||||||
|
|
||||||
|
if (chatCreateTo.memberUuids().length <= 1)
|
||||||
|
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Chat must have members other than its creator");
|
||||||
|
|
||||||
|
chat.setMembers(new ArrayList<>(Arrays.stream(chatCreateTo.memberUuids()).map(
|
||||||
|
p -> entityManager.getReference(Person.class, p)
|
||||||
|
).toList()));
|
||||||
|
chat.setName(chatCreateTo.name());
|
||||||
|
|
||||||
|
chatService.update(chat);
|
||||||
|
return chatMapper.makeDto(chat);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@GetMapping(path = "/my")
|
@GetMapping(path = "/my")
|
||||||
public Stream<ChatTo> getMy(Principal principal) {
|
public Stream<ChatTo> getMy(Principal principal) {
|
||||||
return chatService.readByMember(principal.getName()).stream().map(chatMapper::makeDto);
|
return chatService.readByMember(principal.getName()).stream().map(chatMapper::makeDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping(path = "/by-id/{id}/members")
|
||||||
|
public Stream<PersonTo> getMembers(Principal principal, @PathVariable Long id) {
|
||||||
|
var chat = chatService.readById(id).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Chat not found"));
|
||||||
|
var userRef = entityManager.getReference(Person.class, principal.getName());
|
||||||
|
if (!chat.getMembers().contains(userRef))
|
||||||
|
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "User isn't member of the chat");
|
||||||
|
return chat.getMembers().stream().map(personMapper::makeDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user