mirror of
https://github.com/usatiuk/y.git
synced 2025-10-29 02:37:49 +01:00
deleting posts
This commit is contained in:
@@ -11,9 +11,31 @@
|
|||||||
word-wrap: anywhere;
|
word-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.createdDate {
|
.footer {
|
||||||
margin-top: 0.3rem;
|
margin-top: 0.3rem;
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
color: #A0A0A0;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,43 @@
|
|||||||
import "./Post.scss";
|
import "./Post.scss";
|
||||||
|
import { Form } from "react-router-dom";
|
||||||
|
|
||||||
export function Post({
|
export function Post({
|
||||||
text,
|
text,
|
||||||
createdDate,
|
createdDate,
|
||||||
|
actions,
|
||||||
|
id,
|
||||||
}: {
|
}: {
|
||||||
text: string;
|
text: string;
|
||||||
createdDate: string;
|
createdDate: string;
|
||||||
|
actions: boolean;
|
||||||
|
id: number;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={"post"}>
|
<div className={"post"}>
|
||||||
<span className={"text"}>{text}</span>
|
<span className={"text"}>{text}</span>
|
||||||
<span className={"createdDate"}>{createdDate}</span>
|
<div className={"footer"}>
|
||||||
|
<div className={"info"}>
|
||||||
|
<span className={"createdDate"}>{createdDate}</span>
|
||||||
|
</div>
|
||||||
|
{actions && (
|
||||||
|
<div className={"actions"}>
|
||||||
|
<Form method={"delete"}>
|
||||||
|
<input
|
||||||
|
hidden={true}
|
||||||
|
name={"postToDeleteId"}
|
||||||
|
value={id}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
name="intent"
|
||||||
|
value="deletePost"
|
||||||
|
type={"submit"}
|
||||||
|
>
|
||||||
|
delete
|
||||||
|
</button>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface IProfileProps {
|
|||||||
self: boolean;
|
self: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Profile(props: IProfileProps) {
|
export function Profile({ self }: IProfileProps) {
|
||||||
const loaderData = useLoaderData() as LoaderToType<
|
const loaderData = useLoaderData() as LoaderToType<
|
||||||
typeof profileSelfLoader
|
typeof profileSelfLoader
|
||||||
>;
|
>;
|
||||||
@@ -31,7 +31,9 @@ export function Profile(props: IProfileProps) {
|
|||||||
<div className={"newPost"}>
|
<div className={"newPost"}>
|
||||||
<Form method="post">
|
<Form method="post">
|
||||||
<textarea placeholder={"Write something!"} name="text" />
|
<textarea placeholder={"Write something!"} name="text" />
|
||||||
<button type="submit">Post</button>
|
<button name="intent" value="post" type="submit">
|
||||||
|
Post
|
||||||
|
</button>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
<div className={"posts"}>
|
<div className={"posts"}>
|
||||||
@@ -43,6 +45,8 @@ export function Profile(props: IProfileProps) {
|
|||||||
text={p.text}
|
text={p.text}
|
||||||
createdDate={`${date.toUTCString()}`}
|
createdDate={`${date.toUTCString()}`}
|
||||||
key={p.id}
|
key={p.id}
|
||||||
|
id={p.id}
|
||||||
|
actions={self}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { ActionFunctionArgs, redirect } from "react-router-dom";
|
|||||||
import { login } from "./api/Token";
|
import { login } from "./api/Token";
|
||||||
import { isError } from "./api/dto";
|
import { isError } from "./api/dto";
|
||||||
import { setToken } from "./api/utils";
|
import { setToken } from "./api/utils";
|
||||||
import { post } from "./api/Post";
|
import { createPost, deletePost } from "./api/Post";
|
||||||
|
|
||||||
export type ActionToType<T extends (...args: any) => any> =
|
export type ActionToType<T extends (...args: any) => any> =
|
||||||
| Exclude<Awaited<ReturnType<T>>, Response>
|
| Exclude<Awaited<ReturnType<T>>, Response>
|
||||||
@@ -52,5 +52,12 @@ export async function signupAction({ request }: ActionFunctionArgs) {
|
|||||||
|
|
||||||
export async function profileSelfAction({ request }: ActionFunctionArgs) {
|
export async function profileSelfAction({ request }: ActionFunctionArgs) {
|
||||||
const formData = await request.formData();
|
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()),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
import { PostToArrResp, PostToResp, TPostToArrResp, TPostToResp } from "./dto";
|
import {
|
||||||
|
NoContentToResp,
|
||||||
|
PostToArrResp,
|
||||||
|
PostToResp,
|
||||||
|
TNoContentToResp,
|
||||||
|
TPostToArrResp,
|
||||||
|
TPostToResp,
|
||||||
|
} from "./dto";
|
||||||
import { fetchJSONAuth } from "./utils";
|
import { fetchJSONAuth } from "./utils";
|
||||||
|
|
||||||
export async function post(text: string): Promise<TPostToResp> {
|
export async function createPost(text: string): Promise<TPostToResp> {
|
||||||
return fetchJSONAuth("/post", "POST", PostToResp, {
|
return fetchJSONAuth("/post", "POST", PostToResp, {
|
||||||
text,
|
text,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
export async function deletePost(id: number): Promise<TNoContentToResp> {
|
||||||
|
return fetchJSONAuth(`/post/${id.toString()}`, "DELETE", NoContentToResp);
|
||||||
|
}
|
||||||
|
|
||||||
export async function getPosts(author: string): Promise<TPostToArrResp> {
|
export async function getPosts(author: string): Promise<TPostToArrResp> {
|
||||||
return fetchJSONAuth(`/post?author=${author}`, "GET", PostToArrResp);
|
return fetchJSONAuth(`/post?author=${author}`, "GET", PostToArrResp);
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ function CreateAPIResponse<T extends z.ZodTypeAny>(obj: T) {
|
|||||||
return z.union([ErrorTo, obj]);
|
return z.union([ErrorTo, obj]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const NoContentTo = z.object({});
|
||||||
|
export type TNoContentTo = z.infer<typeof NoContentTo>;
|
||||||
|
|
||||||
|
export const NoContentToResp = CreateAPIResponse(NoContentTo);
|
||||||
|
export type TNoContentToResp = z.infer<typeof NoContentToResp>;
|
||||||
|
|
||||||
export const PersonSignupTo = z.object({
|
export const PersonSignupTo = z.object({
|
||||||
username: z.string(),
|
username: z.string(),
|
||||||
fullName: z.string(),
|
fullName: z.string(),
|
||||||
|
|||||||
@@ -47,7 +47,11 @@ export async function fetchJSON<T, P extends { parse: (arg: string) => T }>(
|
|||||||
headers: reqHeaders(),
|
headers: reqHeaders(),
|
||||||
body: reqBody(),
|
body: reqBody(),
|
||||||
});
|
});
|
||||||
return parser.parse(await response.json());
|
|
||||||
|
const json = await response.json().catch(() => {
|
||||||
|
return {};
|
||||||
|
});
|
||||||
|
return parser.parse(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchJSONAuth<T, P extends { parse: (arg: string) => T }>(
|
export async function fetchJSONAuth<T, P extends { parse: (arg: string) => T }>(
|
||||||
|
|||||||
@@ -13,6 +13,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.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@@ -55,4 +56,16 @@ public class PostController {
|
|||||||
return PostMapper.makeDto(post.get());
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ public class WebSecurityConfig {
|
|||||||
@Bean
|
@Bean
|
||||||
CorsConfigurationSource corsConfigurationSource() {
|
CorsConfigurationSource corsConfigurationSource() {
|
||||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||||
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
|
var config = new CorsConfiguration().applyPermitDefaultValues();
|
||||||
|
config.setAllowedMethods(List.of("*"));
|
||||||
|
source.registerCorsConfiguration("/**", config);
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user