mirror of
https://github.com/usatiuk/y.git
synced 2025-10-28 10:37:47 +01:00
messages!
This commit is contained in:
@@ -11,6 +11,7 @@ import { Login } from "./Login";
|
||||
import { Signup } from "./Signup";
|
||||
import { Home } from "./Home";
|
||||
import {
|
||||
chatAction,
|
||||
homeAction,
|
||||
loginAction,
|
||||
newChatAction,
|
||||
@@ -68,6 +69,7 @@ const router = createBrowserRouter([
|
||||
path: "messages/chat/:id",
|
||||
element: <Chat />,
|
||||
loader: chatLoader,
|
||||
action: chatAction,
|
||||
},
|
||||
{
|
||||
path: "users",
|
||||
|
||||
60
client/src/Chat.scss
Normal file
60
client/src/Chat.scss
Normal file
@@ -0,0 +1,60 @@
|
||||
@import "./common";
|
||||
|
||||
.chat {
|
||||
width: 20rem;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2rem 0;
|
||||
|
||||
a {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.chatHeader {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: auto;
|
||||
|
||||
border-bottom: solid 1px #A0A0A0;
|
||||
|
||||
span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.chatMessages {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
|
||||
a {
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 0.3rem;
|
||||
flex-grow: 0;
|
||||
@include border-shadow;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.messageForm {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 0.5rem;
|
||||
//margin: 2rem;
|
||||
min-height: 3rem;
|
||||
|
||||
@include border-shadow;
|
||||
border: solid gray 1px;
|
||||
|
||||
@include post-editor;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,59 @@
|
||||
import { useLoaderData } from "react-router-dom";
|
||||
import { useFetcher, useLoaderData, useRevalidator } from "react-router-dom";
|
||||
import { chatLoader, LoaderToType } from "./loaders";
|
||||
import { isError } from "./api/dto";
|
||||
|
||||
import "./Chat.scss";
|
||||
import "./PostForm.scss";
|
||||
import { Message } from "./Message";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export function Chat() {
|
||||
const loaderData = useLoaderData() as LoaderToType<typeof chatLoader>;
|
||||
if (!loaderData || isError(loaderData)) {
|
||||
|
||||
if (!loaderData) {
|
||||
return <div>error</div>;
|
||||
}
|
||||
|
||||
return <div className={"chat"}>{loaderData.name}</div>;
|
||||
const chat = loaderData.chat;
|
||||
const messages = loaderData.messages;
|
||||
if (!chat || isError(chat) || !messages || isError(messages)) {
|
||||
return <div>error</div>;
|
||||
}
|
||||
|
||||
const revalidator = useRevalidator();
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => revalidator.revalidate(), 1000);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const fetcher = useFetcher();
|
||||
const busy = fetcher.state === "submitting";
|
||||
|
||||
const sortedMessages = messages.sort((a, b) => b.createdAt - a.createdAt);
|
||||
|
||||
return (
|
||||
<div className={"chat"}>
|
||||
<div className={"chatHeader"}>{chat.name}</div>
|
||||
<fetcher.Form className={"messageForm postForm"} method="post">
|
||||
<textarea placeholder={"Write something!"} name="text" />
|
||||
<input hidden={true} value={chat.id} name={"chatId"} />
|
||||
<button
|
||||
name="intent"
|
||||
value="addMessage"
|
||||
type="submit"
|
||||
disabled={busy}
|
||||
>
|
||||
Write
|
||||
</button>
|
||||
</fetcher.Form>
|
||||
<div className={"messages"}>
|
||||
{sortedMessages.map((m) => (
|
||||
<Message key={m.id} message={m} chat={chat} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
29
client/src/ChatCreate.scss
Normal file
29
client/src/ChatCreate.scss
Normal file
@@ -0,0 +1,29 @@
|
||||
.chatsNew {
|
||||
width: 20rem;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2rem 0;
|
||||
|
||||
.errors {
|
||||
margin: 2rem 0;
|
||||
color: red;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
* {
|
||||
margin: 0.3rem 0;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,41 @@
|
||||
import { Form, useLoaderData, useNavigation } from "react-router-dom";
|
||||
import {
|
||||
Form,
|
||||
useActionData,
|
||||
useLoaderData,
|
||||
useNavigation,
|
||||
} from "react-router-dom";
|
||||
import { LoaderToType, newChatLoader } from "./loaders";
|
||||
import { isError } from "./api/dto";
|
||||
import { getTokenUserUuid } from "./api/utils";
|
||||
|
||||
import "./ChatCreate.scss";
|
||||
import { ActionToType, newChatAction } from "./actions";
|
||||
|
||||
export function ChatCreate() {
|
||||
const loaderData = useLoaderData() as LoaderToType<typeof newChatLoader>;
|
||||
if (!loaderData || isError(loaderData)) {
|
||||
return <div>error</div>;
|
||||
}
|
||||
let errors: JSX.Element[] = [];
|
||||
|
||||
const actionData = useActionData() as ActionToType<typeof newChatAction>;
|
||||
|
||||
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="post">
|
||||
<label htmlFor="fname">name:</label>
|
||||
<label htmlFor="fname">Chat name:</label>
|
||||
<input type="text" name="name" />
|
||||
<label htmlFor="members">Members:</label>
|
||||
<select multiple name={"members"}>
|
||||
{loaderData
|
||||
.filter((p) => p.uuid != getTokenUserUuid())
|
||||
|
||||
@@ -1,3 +1,46 @@
|
||||
@import "./common";
|
||||
|
||||
.chats {
|
||||
|
||||
width: 20rem;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 2rem 0;
|
||||
|
||||
a {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.chatsHeader {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex: auto;
|
||||
|
||||
border-bottom: solid 1px #A0A0A0;
|
||||
|
||||
span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
> :first-child {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.chatsList {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
|
||||
a {
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 0.3rem;
|
||||
flex-grow: 0;
|
||||
@include border-shadow;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ export function Chats() {
|
||||
return (
|
||||
<div className={"chats"}>
|
||||
<div className={"chatsHeader"}>
|
||||
<span>Your chats</span>
|
||||
<Link to={"../messages/chats/new"}>create</Link>
|
||||
</div>
|
||||
<div className={"chatsList"}>
|
||||
|
||||
79
client/src/Message.scss
Normal file
79
client/src/Message.scss
Normal file
@@ -0,0 +1,79 @@
|
||||
@import "./common";
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #E0E0E0;
|
||||
border-radius: 7px;
|
||||
padding: 7px;
|
||||
resize: none;
|
||||
margin: 1rem 0;
|
||||
min-height: 3rem;
|
||||
|
||||
@include border-shadow;
|
||||
|
||||
&.messageEditing {
|
||||
padding: 0;
|
||||
min-height: 6rem;
|
||||
border: 1px solid #D0D0D0;
|
||||
|
||||
@include post-editor;
|
||||
}
|
||||
|
||||
.text {
|
||||
word-wrap: anywhere;
|
||||
}
|
||||
|
||||
.footer {
|
||||
margin-top: 0.3rem;
|
||||
margin-bottom: -0.3rem;
|
||||
font-size: 0.7rem;
|
||||
color: #A0A0A0;
|
||||
|
||||
display: flex;
|
||||
flex: auto;
|
||||
width: 100%;
|
||||
|
||||
.info {
|
||||
flex-grow: 1;
|
||||
align-self: start;
|
||||
|
||||
> * {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
> *:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-self: end;
|
||||
|
||||
> * {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
> *:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
|
||||
button {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
client/src/Message.tsx
Normal file
50
client/src/Message.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import "./Message.scss";
|
||||
import { TChatTo, TMessageTo } from "./api/dto";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
export function Message({
|
||||
message,
|
||||
chat,
|
||||
}: {
|
||||
message: TMessageTo;
|
||||
chat: TChatTo;
|
||||
}) {
|
||||
return (
|
||||
<div className={"message"}>
|
||||
<span className={"text"}>{message.contents}</span>
|
||||
<div className={"footer"}>
|
||||
<div className={"info"}>
|
||||
<span className={"createdDate"}>
|
||||
{new Date(message.createdAt * 1000).toUTCString()}
|
||||
</span>
|
||||
<Link
|
||||
className={"authorLink"}
|
||||
to={"/home/profile/" + message.authorUsername}
|
||||
>
|
||||
by {message.authorUsername}
|
||||
</Link>
|
||||
</div>
|
||||
{/*{actions && (*/}
|
||||
{/* <div className={"actions"}>*/}
|
||||
{/* {<button onClick={() => setEditing(true)}>edit</button>}*/}
|
||||
{/* <Form method={"delete"}>*/}
|
||||
{/* <input*/}
|
||||
{/* hidden={true}*/}
|
||||
{/* name={"postToDeleteId"}*/}
|
||||
{/* value={id}*/}
|
||||
{/* />*/}
|
||||
{/* <button*/}
|
||||
{/* name="intent"*/}
|
||||
{/* value="deletePost"*/}
|
||||
{/* type={"submit"}*/}
|
||||
{/* disabled={busy}*/}
|
||||
{/* >*/}
|
||||
{/* delete*/}
|
||||
{/* </button>*/}
|
||||
{/* </Form>*/}
|
||||
{/* </div>*/}
|
||||
{/*)}*/}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
export function Messages() {
|
||||
return <a>Messages</a>;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { isError } from "./api/dto";
|
||||
import { deleteToken, getTokenUserUuid, setToken } from "./api/utils";
|
||||
import { createPost, deletePost, updatePost } from "./api/Post";
|
||||
import { createChat } from "./api/Chat";
|
||||
import { addMessagesToChat } from "./api/Message";
|
||||
|
||||
export type ActionToType<T extends (...args: any) => any> =
|
||||
| Exclude<Awaited<ReturnType<T>>, Response>
|
||||
@@ -89,8 +90,19 @@ export async function userListAction({ request }: ActionFunctionArgs) {
|
||||
|
||||
export async function newChatAction({ request }: ActionFunctionArgs) {
|
||||
const formData = await request.formData();
|
||||
return await createChat(formData.get("name")!.toString(), [
|
||||
const ret = await createChat(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;
|
||||
}
|
||||
|
||||
export async function chatAction({ request }: ActionFunctionArgs) {
|
||||
const formData = await request.formData();
|
||||
return await addMessagesToChat(
|
||||
Number(formData.get("chatId")!.toString()),
|
||||
formData.get("text")!.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
22
client/src/api/Message.ts
Normal file
22
client/src/api/Message.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import {
|
||||
MessagesToResp,
|
||||
MessageToResp,
|
||||
TMessagesToResp,
|
||||
TMessageToResp,
|
||||
} from "./dto";
|
||||
import { fetchJSONAuth } from "./utils";
|
||||
|
||||
export async function getMessagesByChat(
|
||||
chatId: number,
|
||||
): Promise<TMessagesToResp> {
|
||||
return fetchJSONAuth("/message/by-chat/" + chatId, "GET", MessagesToResp);
|
||||
}
|
||||
|
||||
export async function addMessagesToChat(
|
||||
chatId: number,
|
||||
messageContents: string,
|
||||
): Promise<TMessageToResp> {
|
||||
return fetchJSONAuth("/message/by-chat/" + chatId, "POST", MessageToResp, {
|
||||
contents: messageContents,
|
||||
});
|
||||
}
|
||||
@@ -79,19 +79,23 @@ export const MessageTo = z.object({
|
||||
id: number(),
|
||||
chatId: z.number(),
|
||||
authorUuid: z.string(),
|
||||
authorUsername: z.string(),
|
||||
contents: z.string(),
|
||||
createdAt: z.number(),
|
||||
});
|
||||
export type TMessageTo = z.infer<typeof MessageTo>;
|
||||
|
||||
export const MessageToResp = CreateAPIResponse(MessageTo);
|
||||
export type TMessageToResp = z.infer<typeof MessageToResp>;
|
||||
|
||||
export const MessagesToResp = CreateAPIResponse(z.array(MessageTo));
|
||||
export type TMessagesToResp = z.infer<typeof MessagesToResp>;
|
||||
|
||||
export const ChatTo = z.object({
|
||||
id: z.number(),
|
||||
name: z.string(),
|
||||
creatorUuid: z.string(),
|
||||
members: z.array(PersonTo),
|
||||
messages: z.array(MessageTo),
|
||||
memberCount: z.number(),
|
||||
});
|
||||
export type TChatTo = z.infer<typeof ChatTo>;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
getPostsByFollowees,
|
||||
} from "./api/Post";
|
||||
import { getChat, getMyChats } from "./api/Chat";
|
||||
import { getMessagesByChat } from "./api/Message";
|
||||
|
||||
export type LoaderToType<T extends (...args: any) => any> =
|
||||
| Exclude<Awaited<ReturnType<T>>, Response>
|
||||
@@ -86,5 +87,8 @@ export async function newChatLoader() {
|
||||
}
|
||||
|
||||
export async function chatLoader({ params }: { params: { id?: number } }) {
|
||||
return getChat(params.id!);
|
||||
return {
|
||||
chat: await getChat(params.id!),
|
||||
messages: await getMessagesByChat(params.id!),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -37,6 +37,9 @@ public class ChatController {
|
||||
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.setCreator(entityManager.getReference(Person.class, principal.getName()));
|
||||
chat.setMembers(Arrays.stream(chatCreateTo.memberUuids()).map(
|
||||
p -> entityManager.getReference(Person.class, p)
|
||||
|
||||
@@ -1,10 +1,55 @@
|
||||
package com.usatiuk.tjv.y.server.controller;
|
||||
|
||||
import com.usatiuk.tjv.y.server.dto.MessageCreateTo;
|
||||
import com.usatiuk.tjv.y.server.dto.MessageTo;
|
||||
import com.usatiuk.tjv.y.server.dto.converters.MessageMapper;
|
||||
import com.usatiuk.tjv.y.server.entity.Message;
|
||||
import com.usatiuk.tjv.y.server.entity.Person;
|
||||
import com.usatiuk.tjv.y.server.service.ChatService;
|
||||
import com.usatiuk.tjv.y.server.service.MessageService;
|
||||
import jakarta.persistence.EntityManager;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(value = "/message", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public class MessageController {
|
||||
private final ChatService chatService;
|
||||
private final EntityManager entityManager;
|
||||
private final MessageMapper messageMapper;
|
||||
private final MessageService messageService;
|
||||
|
||||
public MessageController(ChatService chatService, EntityManager entityManager, MessageMapper messageMapper, MessageService messageService) {
|
||||
this.chatService = chatService;
|
||||
this.entityManager = entityManager;
|
||||
this.messageMapper = messageMapper;
|
||||
this.messageService = messageService;
|
||||
}
|
||||
|
||||
@GetMapping(path = "/by-chat/{chatTd}")
|
||||
public Stream<MessageTo> get(Principal principal, @PathVariable Long chatTd) {
|
||||
var chat = chatService.readById(chatTd).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.getMessages().stream().map(messageMapper::makeDto);
|
||||
}
|
||||
|
||||
@PostMapping(path = "/by-chat/{chatId}")
|
||||
public MessageTo post(Principal principal, @PathVariable Long chatId, @RequestBody MessageCreateTo messageCreateTo) {
|
||||
var chat = chatService.readById(chatId).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");
|
||||
|
||||
Message message = new Message().setChat(chat).setAuthor(userRef).setContents(messageCreateTo.contents());
|
||||
messageService.create(message);
|
||||
return messageMapper.makeDto(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package com.usatiuk.tjv.y.server.dto;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public record ChatTo(Long id, String name, String creatorUuid, Collection<PersonTo> members,
|
||||
Collection<MessageTo> messages) {
|
||||
public record ChatTo(Long id, String name, String creatorUuid, Long memberCount) {
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
package com.usatiuk.tjv.y.server.dto;
|
||||
|
||||
public record MessageCreateTo(String contents) {
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
package com.usatiuk.tjv.y.server.dto;
|
||||
|
||||
public record MessageTo(Long id, Long chatId, String authorUuid, String contents) {
|
||||
public record MessageTo(Long id, Long chatId, String authorUuid, String authorUsername, String contents,
|
||||
Long createdAt) {
|
||||
}
|
||||
|
||||
@@ -6,18 +6,7 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ChatMapper {
|
||||
|
||||
private final PersonMapper personMapper;
|
||||
private final MessageMapper messageMapper;
|
||||
|
||||
public ChatMapper(PersonMapper personMapper, MessageMapper messageMapper) {
|
||||
this.personMapper = personMapper;
|
||||
this.messageMapper = messageMapper;
|
||||
}
|
||||
|
||||
public ChatTo makeDto(Chat chat) {
|
||||
return new ChatTo(chat.getId(), chat.getName(), chat.getCreator().getUuid(),
|
||||
chat.getMembers().stream().map(personMapper::makeDto).toList(),
|
||||
chat.getMessages().stream().map(messageMapper::makeDto).toList());
|
||||
return new ChatTo(chat.getId(), chat.getName(), chat.getCreator().getUuid(), (long) chat.getMembers().size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,6 @@ import org.springframework.stereotype.Component;
|
||||
public class MessageMapper {
|
||||
public MessageTo makeDto(Message message) {
|
||||
return new MessageTo(message.getId(), message.getChat().getId(),
|
||||
message.getAuthor().getUuid(), message.getContents());
|
||||
message.getAuthor().getUuid(), message.getAuthor().getUsername(), message.getContents(), message.getCreatedAt().getEpochSecond());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import org.hibernate.annotations.CreationTimestamp;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
@Entity
|
||||
@Getter
|
||||
@@ -21,10 +24,13 @@ public class Message implements EntityWithId<Long> {
|
||||
|
||||
@ManyToOne
|
||||
private Chat chat;
|
||||
|
||||
|
||||
@ManyToOne
|
||||
private Person author;
|
||||
|
||||
@CreationTimestamp
|
||||
private Instant createdAt;
|
||||
|
||||
@Lob
|
||||
@NotBlank
|
||||
private String contents;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
jwt.secret=JKLASJKLASJKLJHKLDFAHJKFDSHJKFJHKDSHJKFHJKSDFJHKSDJHKFJHKS98346783467899782345jkhgsdoigh938g
|
||||
logging.level.root=DEBUG
|
||||
logging.level.org.springframework.security=DEBUG
|
||||
logging.level.org.springframework.security=DEBUG
|
||||
spring.datasource.url=jdbc:h2:file:~/tjvserver.h2
|
||||
spring.jpa.hibernate.ddl-auto=update
|
||||
Reference in New Issue
Block a user