feat: add collection create\edit api request

This commit is contained in:
Kentai Radiquum 2024-08-17 22:51:06 +05:00
parent 5cde53c1d3
commit 9f3ca2e6d9
Signed by: Radiquum
GPG key ID: 858E8EE696525EED
4 changed files with 140 additions and 48 deletions

View file

@ -1,6 +1,7 @@
import { NextResponse, NextRequest } from "next/server"; import { NextResponse, NextRequest } from "next/server";
import { fetchDataViaGet, fetchDataViaPost } from "../utils"; import { fetchDataViaGet, fetchDataViaPost } from "../utils";
import { API_URL } from "../config"; import { API_URL } from "../config";
import { buffer } from "stream/consumers";
export async function GET( export async function GET(
req: NextRequest, req: NextRequest,
@ -25,14 +26,24 @@ export async function POST(
) { ) {
const { endpoint } = params; const { endpoint } = params;
let API_V2: boolean | string = let API_V2: boolean | string =
req.nextUrl.searchParams.get("API_V2") || false; req.nextUrl.searchParams.get("API_V2") || false;
if (API_V2 === "true") { if (API_V2 === "true") {
req.nextUrl.searchParams.delete("API_V2"); req.nextUrl.searchParams.delete("API_V2");
} }
const query = req.nextUrl.searchParams.toString(); const query = req.nextUrl.searchParams.toString();
const url = `${API_URL}/${endpoint.join("/")}${query ? `?${query}` : ""}`; const url = `${API_URL}/${endpoint.join("/")}${query ? `?${query}` : ""}`;
const body = JSON.stringify( await req.json()); let body;
const ReqContentTypeHeader = req.headers.get("Content-Type") || "";
let ResContentTypeHeader = "";
const response = await fetchDataViaPost(url, body, API_V2); if (ReqContentTypeHeader.split(";")[0] == "multipart/form-data") {
ResContentTypeHeader = ReqContentTypeHeader;
body = await req.arrayBuffer();
} else {
ResContentTypeHeader = "application/json; charset=UTF-8";
body = JSON.stringify(await req.json());
}
const response = await fetchDataViaPost(url, body, API_V2, ResContentTypeHeader);
return NextResponse.json(response); return NextResponse.json(response);
} }

View file

@ -28,11 +28,16 @@ export const fetchDataViaGet = async (
export const fetchDataViaPost = async ( export const fetchDataViaPost = async (
url: string, url: string,
body: string, body: string,
API_V2: string | boolean = false API_V2: string | boolean = false,
contentType: string = ""
) => { ) => {
if (API_V2) { if (API_V2) {
HEADERS["API-Version"] = "v2"; HEADERS["API-Version"] = "v2";
} }
if (contentType != "") {
HEADERS["Content-Type"] = contentType;
}
try { try {
const response = await fetch(url, { const response = await fetch(url, {
method: "POST", method: "POST",

View file

@ -2,13 +2,11 @@ import React, { useRef } from "react";
import Cropper, { ReactCropperElement } from "react-cropper"; import Cropper, { ReactCropperElement } from "react-cropper";
import "cropperjs/dist/cropper.css"; import "cropperjs/dist/cropper.css";
import { Button, Modal } from "flowbite-react"; import { Button, Modal } from "flowbite-react";
import { b64toBlob } from "#/api/utils";
type Props = { type Props = {
src: string; src: string;
setSrc: (src: string) => void; setSrc: (src: string) => void;
setTempSrc: (src: string) => void; setTempSrc: (src: string) => void;
setImageData: (src: string) => void;
isOpen: boolean; isOpen: boolean;
setIsOpen: (isOpen: boolean) => void; setIsOpen: (isOpen: boolean) => void;
height: number; height: number;
@ -24,36 +22,16 @@ export const CropModal: React.FC<Props> = (props) => {
const getCropData = () => { const getCropData = () => {
if (typeof cropperRef.current?.cropper !== "undefined") { if (typeof cropperRef.current?.cropper !== "undefined") {
props.setSrc(cropperRef.current?.cropper.getCroppedCanvas().toDataURL()); props.setSrc(
cropperRef.current?.cropper
let block = cropperRef.current?.cropper .getCroppedCanvas({
.getCroppedCanvas({ width: props.width,
width: props.width, height: props.height,
height: props.height, maxWidth: props.width,
maxWidth: props.width, maxHeight: props.height,
maxHeight: props.height, })
}) .toDataURL("image/jpeg", props.quality)
.toDataURL("image/jpeg", props.quality) );
.split(";");
let contentType = block[0].split(":")[1];
let realData = block[1].split(",")[1];
const blob = b64toBlob(realData, contentType);
const handleFileRead = (e, fileReader) => {
const content = fileReader.result;
props.setImageData(content);
};
const handleFileText = (file) => {
const fileReader = new FileReader();
fileReader.onloadend = (e) => {
handleFileRead(e, fileReader);
};
fileReader.readAsText(file);
};
handleFileText(blob);
props.setTempSrc(""); props.setTempSrc("");
} }
}; };
@ -103,7 +81,7 @@ export const CropModal: React.FC<Props> = (props) => {
onClick={() => { onClick={() => {
props.setSrc(null); props.setSrc(null);
props.setTempSrc(null); props.setTempSrc(null);
props.setImageData(null); // props.setImageData(null);
props.setIsOpen(false); props.setIsOpen(false);
}} }}
> >

View file

@ -1,5 +1,4 @@
"use client"; "use client";
import useSWR from "swr";
import useSWRInfinite from "swr/infinite"; import useSWRInfinite from "swr/infinite";
import { useUserStore } from "#/store/auth"; import { useUserStore } from "#/store/auth";
import { useEffect, useState, useCallback } from "react"; import { useEffect, useState, useCallback } from "react";
@ -17,6 +16,7 @@ import {
} from "flowbite-react"; } from "flowbite-react";
import { ReleaseLink } from "#/components/ReleaseLink/ReleaseLink"; import { ReleaseLink } from "#/components/ReleaseLink/ReleaseLink";
import { CropModal } from "#/components/CropModal/CropModal"; import { CropModal } from "#/components/CropModal/CropModal";
import { b64toBlob } from "#/api/utils";
const fetcher = async (url: string) => { const fetcher = async (url: string) => {
const res = await fetch(url); const res = await fetch(url);
@ -39,7 +39,7 @@ export const CreateCollectionPage = () => {
const [edit, setEdit] = useState(false); const [edit, setEdit] = useState(false);
const [imageData, setImageData] = useState<string>(null); // const [imageData, setImageData] = useState<File | Blob>(null);
const [imageUrl, setImageUrl] = useState<string>(null); const [imageUrl, setImageUrl] = useState<string>(null);
const [tempImageUrl, setTempImageUrl] = useState<string>(null); const [tempImageUrl, setTempImageUrl] = useState<string>(null);
const [isPrivate, setIsPrivate] = useState(false); const [isPrivate, setIsPrivate] = useState(false);
@ -59,19 +59,59 @@ export const CreateCollectionPage = () => {
const collection_id = searchParams.get("id") || null; const collection_id = searchParams.get("id") || null;
const mode = searchParams.get("mode") || null; const mode = searchParams.get("mode") || null;
const [isSending, setIsSending] = useState(false);
useEffect(() => { useEffect(() => {
async function _checkMode() { async function _checkMode() {
if (mode === "edit" && collection_id) { if (mode === "edit" && collection_id) {
setIsSending(true);
const res = await fetch( const res = await fetch(
`${ENDPOINTS.collection.base}/${collection_id}?token=${userStore.token}` `${ENDPOINTS.collection.base}/${collection_id}?token=${userStore.token}`
); );
const data = await res.json(); const data = await res.json();
let addedReleasesIdsArray = [];
let addedReleasesArray = [];
for (let i = 0; i < 4; i++) {
const res = await fetch(
`${ENDPOINTS.collection.base}/${collection_id}/releases/${i}?token=${userStore.token}`
);
const data = await res.json();
if (data.content.length > 0) {
data.content.forEach((release) => {
if (!addedReleasesIds.includes(release.id)) {
addedReleasesIdsArray.push(release.id);
addedReleasesArray.push(release);
}
});
} else {
setAddedReleases(addedReleasesArray);
setAddedReleasesIds(addedReleasesIdsArray);
break;
}
}
if ( if (
mode === "edit" && mode === "edit" &&
userStore.user.id == data.collection.creator.id userStore.user.id == data.collection.creator.id
) { ) {
setEdit(true); setEdit(true);
setCollectionInfo({
title: data.collection.title,
description: data.collection.description,
});
setStringLength({
title: data.collection.title.length,
description: data.collection.description.length,
});
setIsPrivate(data.collection.is_private);
setImageUrl(data.collection.image);
setIsSending(false);
} }
} }
} }
@ -108,13 +148,65 @@ export const CreateCollectionPage = () => {
function submit(e) { function submit(e) {
e.preventDefault(); e.preventDefault();
console.log(collectionInfo.title.length); async function _createCollection() {
const url =
mode === "edit"
? `${ENDPOINTS.collection.edit}/${collection_id}?token=${userStore.token}`
: `${ENDPOINTS.collection.create}?token=${userStore.token}`;
console.log({ const res = await fetch(url, {
...collectionInfo, method: "POST",
private: isPrivate, body: JSON.stringify({
image: imageData, ...collectionInfo,
}); is_private: isPrivate,
private: isPrivate,
releases: addedReleasesIds,
}),
});
const data = await res.json();
if (data.code == 5) {
alert("Вы превысили допустимый еженедельный лимит создания коллекций!");
return;
}
if (imageUrl && !imageUrl.startsWith("http")) {
let block = imageUrl.split(";");
let contentType = block[0].split(":")[1];
let realData = block[1].split(",")[1];
const blob = b64toBlob(realData, contentType);
const formData = new FormData();
formData.append("image", blob, "cropped.jpg");
formData.append("name", "image");
const uploadRes = await fetch(
`${ENDPOINTS.collection.editImage}/${data.collection.id}?token=${userStore.token}`,
{
method: "POST",
body: formData,
}
);
const uploadData = await uploadRes.json();
}
router.push(`/collection/${data.collection.id}`);
}
if (
collectionInfo.title.length >= 10 &&
addedReleasesIds.length >= 1 &&
userStore.token
) {
// setIsSending(true);
_createCollection();
} else if (collectionInfo.title.length < 10) {
alert("Необходимо ввести название коллекции не менее 10 символов");
} else if (!userStore.token) {
alert("Для создания коллекции необходимо войти в аккаунт");
} else if (addedReleasesIds.length < 1) {
alert("Необходимо добавить хотя бы один релиз в коллекцию");
}
} }
function _deleteRelease(release: any) { function _deleteRelease(release: any) {
@ -233,13 +325,19 @@ export const CreateCollectionPage = () => {
<Checkbox <Checkbox
id="private" id="private"
name="private" name="private"
color="blue"
checked={isPrivate} checked={isPrivate}
onChange={(e) => setIsPrivate(e.target.checked)} onChange={(e) => setIsPrivate(e.target.checked)}
/> />
<Label htmlFor="private" value="Приватная коллекция" /> <Label htmlFor="private" value="Приватная коллекция" />
</div> </div>
</div> </div>
<Button color={"blue"} className="mt-4" type="submit"> <Button
color={"blue"}
className="mt-4"
type="submit"
disabled={isSending}
>
{edit ? "Обновить" : "Создать"} {edit ? "Обновить" : "Создать"}
</Button> </Button>
</div> </div>
@ -292,10 +390,10 @@ export const CreateCollectionPage = () => {
src={tempImageUrl} src={tempImageUrl}
setSrc={setImageUrl} setSrc={setImageUrl}
setTempSrc={setTempImageUrl} setTempSrc={setTempImageUrl}
setImageData={setImageData} // setImageData={setImageData}
aspectRatio={600 / 337} aspectRatio={600 / 337}
guides={false} guides={false}
quality={0.9} quality={100}
isOpen={cropModalOpen} isOpen={cropModalOpen}
setIsOpen={setCropModalOpen} setIsOpen={setCropModalOpen}
forceAspect={true} forceAspect={true}