diff --git a/client/src/Post.scss b/client/src/Post.scss
index 7646b78..e944445 100644
--- a/client/src/Post.scss
+++ b/client/src/Post.scss
@@ -11,9 +11,31 @@
word-wrap: anywhere;
}
- .createdDate {
+ .footer {
margin-top: 0.3rem;
font-size: 0.7rem;
color: #A0A0A0;
+
+ display: flex;
+ flex: auto;
+ width: 100%;
+
+ .info {
+ flex-grow: 1;
+ align-self: start;
+ }
+
+ .actions {
+ align-self: end;
+ button {
+ background: none;
+ border: none;
+ padding: 0;
+ text-decoration: none;
+ cursor: pointer;
+ color: inherit;
+ font-size: inherit;
+ }
+ }
}
}
\ No newline at end of file
diff --git a/client/src/Post.tsx b/client/src/Post.tsx
index fc55ed4..2cf4f99 100644
--- a/client/src/Post.tsx
+++ b/client/src/Post.tsx
@@ -1,16 +1,43 @@
import "./Post.scss";
+import { Form } from "react-router-dom";
export function Post({
text,
createdDate,
+ actions,
+ id,
}: {
text: string;
createdDate: string;
+ actions: boolean;
+ id: number;
}) {
return (
{text}
-
{createdDate}
+
+
+ {createdDate}
+
+ {actions && (
+
+
+
+ )}
+
);
}
diff --git a/client/src/Profile.tsx b/client/src/Profile.tsx
index e371aa4..c4536c9 100644
--- a/client/src/Profile.tsx
+++ b/client/src/Profile.tsx
@@ -9,7 +9,7 @@ export interface IProfileProps {
self: boolean;
}
-export function Profile(props: IProfileProps) {
+export function Profile({ self }: IProfileProps) {
const loaderData = useLoaderData() as LoaderToType<
typeof profileSelfLoader
>;
@@ -31,7 +31,9 @@ export function Profile(props: IProfileProps) {
@@ -43,6 +45,8 @@ export function Profile(props: IProfileProps) {
text={p.text}
createdDate={`${date.toUTCString()}`}
key={p.id}
+ id={p.id}
+ actions={self}
/>
);
})}
diff --git a/client/src/actions.ts b/client/src/actions.ts
index d1944d6..75ff543 100644
--- a/client/src/actions.ts
+++ b/client/src/actions.ts
@@ -3,7 +3,7 @@ import { ActionFunctionArgs, redirect } from "react-router-dom";
import { login } from "./api/Token";
import { isError } from "./api/dto";
import { setToken } from "./api/utils";
-import { post } from "./api/Post";
+import { createPost, deletePost } from "./api/Post";
export type ActionToType
any> =
| Exclude>, Response>
@@ -52,5 +52,12 @@ export async function signupAction({ request }: ActionFunctionArgs) {
export async function profileSelfAction({ request }: ActionFunctionArgs) {
const formData = await request.formData();
- return await post(formData.get("text")!.toString());
+ const intent = formData.get("intent")!.toString();
+ if (intent == "post") {
+ return await createPost(formData.get("text")!.toString());
+ } else if (intent == "deletePost") {
+ return await deletePost(
+ parseInt(formData.get("postToDeleteId")!.toString()),
+ );
+ }
}
diff --git a/client/src/api/Post.ts b/client/src/api/Post.ts
index bce708b..65d6218 100644
--- a/client/src/api/Post.ts
+++ b/client/src/api/Post.ts
@@ -1,11 +1,21 @@
-import { PostToArrResp, PostToResp, TPostToArrResp, TPostToResp } from "./dto";
+import {
+ NoContentToResp,
+ PostToArrResp,
+ PostToResp,
+ TNoContentToResp,
+ TPostToArrResp,
+ TPostToResp,
+} from "./dto";
import { fetchJSONAuth } from "./utils";
-export async function post(text: string): Promise {
+export async function createPost(text: string): Promise {
return fetchJSONAuth("/post", "POST", PostToResp, {
text,
});
}
+export async function deletePost(id: number): Promise {
+ return fetchJSONAuth(`/post/${id.toString()}`, "DELETE", NoContentToResp);
+}
export async function getPosts(author: string): Promise {
return fetchJSONAuth(`/post?author=${author}`, "GET", PostToArrResp);
diff --git a/client/src/api/dto.ts b/client/src/api/dto.ts
index 9af6570..baca452 100644
--- a/client/src/api/dto.ts
+++ b/client/src/api/dto.ts
@@ -10,6 +10,12 @@ function CreateAPIResponse(obj: T) {
return z.union([ErrorTo, obj]);
}
+export const NoContentTo = z.object({});
+export type TNoContentTo = z.infer;
+
+export const NoContentToResp = CreateAPIResponse(NoContentTo);
+export type TNoContentToResp = z.infer;
+
export const PersonSignupTo = z.object({
username: z.string(),
fullName: z.string(),
diff --git a/client/src/api/utils.ts b/client/src/api/utils.ts
index 0ad738f..bf309ef 100644
--- a/client/src/api/utils.ts
+++ b/client/src/api/utils.ts
@@ -47,7 +47,11 @@ export async function fetchJSON T }>(
headers: reqHeaders(),
body: reqBody(),
});
- return parser.parse(await response.json());
+
+ const json = await response.json().catch(() => {
+ return {};
+ });
+ return parser.parse(json);
}
export async function fetchJSONAuth T }>(
diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/controller/PostController.java b/server/src/main/java/com/usatiuk/tjv/y/server/controller/PostController.java
index a709efb..a878fb8 100644
--- a/server/src/main/java/com/usatiuk/tjv/y/server/controller/PostController.java
+++ b/server/src/main/java/com/usatiuk/tjv/y/server/controller/PostController.java
@@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.server.ResponseStatusException;
import java.security.Principal;
+import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;
@@ -55,4 +56,16 @@ public class PostController {
return PostMapper.makeDto(post.get());
}
+
+ @DeleteMapping(path = "/{id}")
+ @ResponseStatus(HttpStatus.NO_CONTENT)
+ public void delete(Principal principal, @PathVariable long id) {
+ var read = postService.readById(id);
+ if (read.isEmpty()) return;
+ if (!Objects.equals(read.get().getAuthor().getId(), principal.getName())) {
+ throw new ResponseStatusException(HttpStatus.FORBIDDEN);
+ }
+ postService.deleteById(id);
+ }
+
}
diff --git a/server/src/main/java/com/usatiuk/tjv/y/server/security/WebSecurityConfig.java b/server/src/main/java/com/usatiuk/tjv/y/server/security/WebSecurityConfig.java
index 16b8d04..24c87df 100644
--- a/server/src/main/java/com/usatiuk/tjv/y/server/security/WebSecurityConfig.java
+++ b/server/src/main/java/com/usatiuk/tjv/y/server/security/WebSecurityConfig.java
@@ -76,7 +76,9 @@ public class WebSecurityConfig {
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
+ var config = new CorsConfiguration().applyPermitDefaultValues();
+ config.setAllowedMethods(List.of("*"));
+ source.registerCorsConfiguration("/**", config);
return source;
}
}