Skip to content
Snippets Groups Projects
Commit 5a2b3e29 authored by Yusuf Akgül's avatar Yusuf Akgül :hatching_chick:
Browse files

Merge branch 'main' of gitlab.bht-berlin.de:s86116/project_ss23 into swaggerDocWithChanges

parents b2f929fc 1e04ab8f
No related branches found
No related tags found
1 merge request!43docs
Pipeline #39800 passed
Showing
with 271 additions and 138 deletions
......@@ -17,7 +17,7 @@ spielt, dass man in den jeweiligen Feeds nur Beiträge zu diesem Spiel zu sehen
es also vllt. Spieler die bestimmte „Guide“ Beiträge verfassen, ihre Erfolge mit den anderen teilen
wollen oder sich zum Beispiel über ihren Lieblingscharakter in einem Spiel unterhalten wollen.
Der direkte Kontakt zu anderen Personen soll in etwa so stattfinden, wie man es über Twitter
Der direkte Kontakt zu anderen Personen soll in etwa so stattfinden, wie man es über Twitter
bereits kennt. Also in Form von Beiträgen, die dann von Usern kommentiert werden können (es
ist keine Live Chat Funktion in Planung).
......
import { getApiDocs } from '@/lib/swagger';
import ReactSwagger from './react-swagger';
import { getApiDocs } from '@/lib/swagger'
import ReactSwagger from './react-swagger'
export default async function IndexPage() {
const spec = await getApiDocs();
const spec = await getApiDocs()
return (
<section className="container">
<ReactSwagger spec={spec} />
</section>
);
}
)
}
\ No newline at end of file
'use client';
'use client'
import SwaggerUI from 'swagger-ui-react';
import 'swagger-ui-react/swagger-ui.css';
import SwaggerUI from 'swagger-ui-react'
import 'swagger-ui-react/swagger-ui.css'
type Props = {
spec: Record<string, any>,
};
}
function ReactSwagger({ spec }: Props) {
return <SwaggerUI spec={spec} />;
return <SwaggerUI spec={spec} />
}
export default ReactSwagger;
export default ReactSwagger
\ No newline at end of file
export default async function Following() {
import { BackHeader } from "@/components/back-header"
import { GlobalLayout } from "@/components/global-layout"
import { UserFollows } from "@/components/profile/components/user-follows"
import { Card } from "@/components/ui/card"
import { getCurrentUser } from "@/lib/session"
export default async function Following({ params }: { params: { username: string } }) {
const session = await getCurrentUser()
return (
<div>
<h1>Following Page WIP</h1>
</div>
<GlobalLayout
mainContent={
<Card className="w-full overflow-hidden ">
<div className="p-3">
<BackHeader>
<h1 className="font-bold">Following</h1>
</BackHeader>
</div>
<div className="px-5">
<UserFollows username={params.username} session={session} />
</div>
</Card>
}
/>
)
}
\ No newline at end of file
......@@ -9,7 +9,7 @@ import { NextRequest, NextResponse } from "next/server"
* @swagger
* /api/games:
* get:
* description: Get Games through external API
* description: Get Games through external API
* content:
* application/json:
* schema:
......@@ -17,10 +17,9 @@ import { NextRequest, NextResponse } from "next/server"
* 200:
* description: list of games
* 500:
* description: error
* description: error
*/
export async function GET(req: NextRequest) {
const p = req.nextUrl.searchParams
try {
......
......@@ -23,11 +23,11 @@ import { db } from "@/lib/db"
* 200:
* description: fetched gweets!
* 400:
* description: Invalid request body!
* description: Invalid request body!
* 404:
* description: Gweet not found!
* description: Gweet not found!
* 500:
* description: Error
* description: Error
*/
export async function GET(request: Request, { params }: { params: { id: string } }) {
......
......@@ -15,8 +15,8 @@ import { db } from "@/lib/db"
* 200:
* description: fetched like!
* 500:
* description: Error
*
* description: Error
*
* post:
* description: Creates Gweet
* responses:
......@@ -29,8 +29,8 @@ import { db } from "@/lib/db"
* 404:
* description: Invalid request body !
* 500:
* description: Error
*
* description: Error
*
*/
export async function GET(request: Request) {
......
......@@ -5,7 +5,7 @@ import { db } from "@/lib/db"
/**
* @swagger
* /api/gweets/regweets:
* /api/gweets/regweets:
* post:
* description: Creates Regweet
* responses:
......@@ -14,11 +14,11 @@ import { db } from "@/lib/db"
* 201:
* description: Gweet regweeted!
* 400:
* description: Invalid request body
* description: Invalid request body
* 401:
* description: Unauthorized
* 500:
* description: Error
* description: Error
*/
export async function POST(request: Request) {
......
......@@ -16,8 +16,8 @@ import { utapi } from "uploadthing/server"
* 200:
* description: fetched gweets!
* 500:
* description: Error
*
* description: Error
*
* post:
* description: Creates Gweet
* responses:
......@@ -26,20 +26,19 @@ import { utapi } from "uploadthing/server"
* 401:
* description: Unauthorized
* 500:
* description: Error
*
* description: Error
*
* delete:
* description: Deletes Gweet
* responses:
* 200:
* description: deleted!
* 401:
* description: Unauthorized
* description: Unauthorized
* 500:
* description: Error
* description: Error
*/
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
......
......@@ -16,7 +16,7 @@ import { db } from "@/lib/db"
* description: fetched hashtags!
* 500:
* description: Error
*
*
* post:
* description: Creates Hashtag
* responses:
......@@ -26,10 +26,9 @@ import { db } from "@/lib/db"
* description: Unauthorized
* 500:
* description: Error
*
*
*/
export async function GET() {
try {
const hashtags = await db.hashtag.findMany({
......
......@@ -6,7 +6,7 @@ import { normalize } from 'normalize-diacritics'
/**
* @swagger
* /api/signup:
*
*
* post:
* description: Creates Account after Email was verified
* responses:
......@@ -16,10 +16,9 @@ import { normalize } from 'normalize-diacritics'
* description: Email already used
* 500:
* description: Error!
*
*
*/
export async function POST(req: Request) {
try {
const { username, email, password } = await req.json()
......
......@@ -12,8 +12,8 @@ import { z } from "zod"
* 200:
* description: fetched follows!
* 500:
* description: Error
*
* description: Error
*
* put:
* description: Creates Following Record from one User to another
* requestBody:
......@@ -36,18 +36,16 @@ import { z } from "zod"
* description: Unauthorized
* 500:
* description: Error
*
*
*/
// get following or followers information
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const userId = searchParams.get("userId") || undefined
const username = searchParams.get("userId") || undefined
const type = searchParams.get("type") || undefined
const userIdSchema = z.string().cuid().optional()
const zod = userIdSchema.safeParse(userId)
const userIdSchema = z.string().optional()
const zod = userIdSchema.safeParse(username)
if (!zod.success) {
return NextResponse.json(zod.error, { status: 400 })
......@@ -58,19 +56,29 @@ export async function GET(request: Request) {
const followers = await db.user
.findUnique({
where: {
id: userId,
username,
},
})
.followers({
include: {
followers: true,
following: true,
},
})
.followers()
return NextResponse.json(followers, { status: 200 })
} else if (type === "following") {
const following = await db.user
.findUnique({
where: {
id: userId,
username,
},
})
.following({
include: {
followers: true,
following: true,
},
})
.following()
return NextResponse.json(following, { status: 200 })
}
......@@ -79,7 +87,6 @@ export async function GET(request: Request) {
}
}
// follow a user
export async function PUT(request: Request) {
const session = await getCurrentUser()
const { userId } = await request.json()
......@@ -121,7 +128,6 @@ export async function PUT(request: Request) {
}
}
// unfollow a user
export async function DELETE(request: Request) {
const session = await getCurrentUser()
const { userId } = await request.json()
......
......@@ -15,10 +15,9 @@ import { NextRequest, NextResponse } from "next/server"
* description: Request handled!
* 401:
* description: Unauthorized
*
*
*/
export async function PUT(req: NextRequest) {
const sessionUser = await getCurrentUser()
......
......@@ -3,7 +3,7 @@
import { zodResolver } from "@hookform/resolvers/zod"
import { useSession } from "next-auth/react"
import Image from "next/image"
import { useRef, useState } from "react"
import { useEffect, useRef, useState } from "react"
import { useForm } from "react-hook-form"
import * as z from "zod"
......@@ -35,6 +35,10 @@ const FormSchema = z.object({
.max(240, { message: "Gweets cannot be more that 240 characters." }),
})
const ImagesArraySchema = z.array(z.custom<File>().refine((file) => file instanceof File, { message: "Expected a file" })
.refine((file) => file?.size <= 4000000, { message: "Images must be less than 4MB" }))
.optional()
export const CreateGweet = ({
parent_gweet,
quoted_gweet,
......@@ -52,12 +56,25 @@ export const CreateGweet = ({
const imageUploadRef = useRef<HTMLInputElement>(null)
const { data: session } = useSession()
const { isLoading, mutate } = useCreateGweet()
const { isLoading, isSuccess, mutate } = useCreateGweet()
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
})
let disable = true
if (!isLoading && session?.user.username !== undefined) {
// console.log("form is valid", form.formState.isValid)
if (form.formState.isDirty && form.formState.isValid) {
if (chosenImages.length !== 0) {
disable = false
} else {
disable = true
}
disable = false
}
}
async function onGweet(formData: z.infer<typeof FormSchema>) {
if (!session) return null
......@@ -69,19 +86,37 @@ export const CreateGweet = ({
quoteGweetId: quoted_gweet?.id || null,
})
toast({
description: "Your gweet was send.",
})
form.setValue('gweet', '')
setChosenImages([])
disable = true
}
useEffect(() => {
form.formState.isValid // needs to be checked to trigger validation on first load (weird behavior)
if (isSuccess) {
toast({
description: "Your gweet was send.",
})
form.reset()
form.setValue('gweet', '')
setChosenImages([])
}
}, [form, isSuccess])
const chooseImages = async (
event: React.ChangeEvent<HTMLInputElement>,
setChosenImages: (images: IChosenImages[]) => void,
) => {
const files = event.target.files
try {
const fileArray = Array.from(files?.length ? files : [])
ImagesArraySchema.parse(fileArray)
} catch (error: any) {
const err = error as z.ZodError
return toast({
variant: "destructive",
description: err.issues[0].message,
})
}
if (files && files.length > 0) {
const newImages: IChosenImages[] = []
......@@ -185,7 +220,12 @@ export const CreateGweet = ({
<FormItem>
<FormControl>
<Textarea
placeholder={placeholder || "What's on your mind?"}
placeholder={placeholder ||
chosenImages.length !== 0 ?
"Tell us something about your media."
:
"What's on your mind?"
}
className="resize-none min-h-[100px]"
disabled={isLoading || !session.user}
{...field}
......@@ -222,19 +262,20 @@ export const CreateGweet = ({
{chosenImages.map((image, i) => {
const isFirstImage = chosenImages.length === 3 && i === 0
return (
<Card key={i} className={`relative max-h-[600px] overflow-hidden ${isFirstImage ? "row-span-2" : ""}`}>
<Card key={i} className={`relative max-h-[600px] overflow-hidden ${isFirstImage ? "row-span-2" : ""} ${isLoading ? "opacity-50" : ""}`}>
<Button
type="button"
size="icon"
variant="secondary"
className="rounded-full absolute top-1 right-1 z-40"
disabled={isLoading}
onClick={() => {
setChosenImages(
chosenImages.filter((img, j) => j !== i),
)
}}
>
<Icons.close className="w-6 h-6" />
<Icons.close />
</Button>
<Image
src={image.url as string}
......@@ -252,7 +293,7 @@ export const CreateGweet = ({
</Card>
<div className="flex justify-end">
<Button type="submit" size="lg" className="w-20" disabled={isLoading || !session.user}>
<Button type="submit" size="lg" className="w-20" disabled={disable}>
{isLoading ? (
<Icons.spinner className="h-4 w-4 animate-spin" />
) : (
......
......@@ -36,43 +36,48 @@ export const FollowButton = ({
}, [router, followMutation.isSuccess, unfollowMutation.isSuccess])
return (
<Dialog open={open} onOpenChange={setOpen}>
{isFollowing ? (
<DialogTrigger asChild>
<Button variant={`${isHovered ? "destructive" : "outline"}`} size="lg"
className="w-24"
onMouseEnter={() => {
setText("Unfollow")
setIsHovered(true)
}}
onMouseOut={() => {
setText("Following")
setIsHovered(false)
}}>
{text}
<div onClick={(e) => {
e.preventDefault()
e.stopPropagation()
}}>
<Dialog open={open} onOpenChange={setOpen}>
{isFollowing ? (
<DialogTrigger asChild >
<Button variant={`${isHovered ? "destructive" : "outline"}`} size="lg"
className="w-24"
onMouseEnter={() => {
setText("Unfollow")
setIsHovered(true)
}}
onMouseOut={() => {
setText("Following")
setIsHovered(false)
}}>
{text}
</Button>
</DialogTrigger>
) : (
<Button size="lg" onClick={() => followMutation.mutate({ userId })}>
Follow
</Button>
</DialogTrigger>
) : (
<Button size="lg" onClick={() => followMutation.mutate({ userId })}>
Follow
</Button>
)}
<DialogContent>
<DialogHeader>
<DialogTitle>Unfollow @{username}?</DialogTitle>
<DialogDescription>
You can still view their profile, unless their Gweets are protected.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button type="submit" onClick={() => unfollowMutation.mutate({ userId })} disabled={unfollowMutation.isLoading}>
{unfollowMutation.isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
Unfollow
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}
<DialogContent>
<DialogHeader>
<DialogTitle>Unfollow @{username}?</DialogTitle>
<DialogDescription>
You can still view their profile, unless their Gweets are protected.
</DialogDescription>
</DialogHeader>
<DialogFooter>
<Button type="submit" onClick={() => unfollowMutation.mutate({ userId })} disabled={unfollowMutation.isLoading}>
{unfollowMutation.isLoading && (
<Icons.spinner className="mr-2 h-4 w-4 animate-spin" />
)}
Unfollow
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}
\ No newline at end of file
......@@ -10,7 +10,6 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { toast } from "@/components/ui/use-toast"
import { env } from "@/env.mjs"
import getURL from "@/lib/utils"
import { useSession } from "next-auth/react"
import { useRef, useState } from "react"
......
export const getFollows = async (id: string | undefined, type: string | undefined) => {
try {
const data = await fetch(`/api/users/follow?type=${type}&user_id=${id}`)
const data = await fetch(`/api/users/follow?type=${type}&userId=${id}`)
.then((result) => result.json())
return data
......
......@@ -36,7 +36,7 @@ const FormSchema = z.object({
})
const ImagesSchema = z.custom<File>().refine((file) => file instanceof File, { message: "Expected a file" })
.refine((file) => file?.size < 4000000, { message: "Images must be less than 4MB" })
.refine((file) => file?.size <= 4000000, { message: "Images must be less than 4MB" })
.optional()
export const EditProfileModal = ({ user }: { user: IUser }) => {
......
"use client"
import LoadingItem from "@/components/loading-item"
import { TryAgain } from "@/components/try-again"
import { User } from "next-auth"
import { usePathname } from "next/navigation"
import { useGetFollows } from "../hooks/use-get-follows"
import { UserItem } from "./user-item"
export const UserFollows = ({ username, session }: { username: string, session: User | undefined }) => {
const pathValue = usePathname().split("/")[2] || ""
const {
data: following,
isLoading,
isError,
} = useGetFollows({
id: username,
type: pathValue,
})
if (isLoading) {
return (
<LoadingItem />
)
}
if (isError || !following) {
return (
<TryAgain />
)
}
if (following?.length === 0) {
if (session?.username === username) {
return (
<div className="m-6 flex justify-center">
<div className="font-bold">
<h1>You are not following anyone.</h1>
<p>When you do, it&apos;ll show up here.</p>
</div>
</div>
)
}
return (
<div className="m-6 flex justify-center">
<div className="font-bold">
<h1><span className="text-sky-500">@{username}</span> is not following anyone yet.</h1>
<p>When they do, it&apos;ll show up here.</p>
</div>
</div>
)
}
return (
<div className="space-y-5 flex flex-col">
{following?.map((user) => {
return <UserItem key={user?.id} user={user} sessionId={session?.id} />
})}
</div>
)
}
\ No newline at end of file
......@@ -54,46 +54,52 @@ export const UserGames = async ({ username }: { username: string }) => {
<div className="p-3 space-y-12">
<div>
<h1 className="text-2xl font-bold pb-3">Favorite Games</h1>
<div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center">
{favoritegames ? favoritegames.map((game: IGame) => (
<GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} />
))
:
<p>No favorites currently...</p>}
</div>
{favoritegames ?
<div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center">
{favoritegames.map((game: IGame) => (
<GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} />
))}
</div>
:
<span>No favorites currently...</span>
}
</div>
<div>
<h1 className="text-2xl font-bold pb-3">Currently playing</h1>
<div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center">
{playingGames ? playingGames.map((game: IGame) => (
<GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} />
))
:
<p>Currently not playing any games...</p>}
</div>
{playingGames ?
<div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center">
{playingGames.map((game: IGame) => (
<GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} />
))}
</div>
:
<p>Currently not playing any games...</p>
}
</div>
<div>
<h1 className="text-2xl font-bold pb-3">Planning to play</h1>
<div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center">
{planningGames ? planningGames.map((game: IGame) => (
<GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} />
))
:
<p>Currently not planning to play any games...</p>}
</div>
{planningGames ?
<div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center">
{planningGames.map((game: IGame) => (
<GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} />
))}
</div>
:
<p>Currently not planning to play any games...</p>}
</div>
<div>
<h1 className="text-2xl font-bold pb-3">Finished Games</h1>
<div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center">
{finishedGames ? finishedGames.map((game: IGame) => (
<GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} />
))
:
<p>No finished games...</p>}
</div>
{finishedGames ?
<div className="grid grid-cols-1 ss:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-4 lg:gap-8 items-center">
{finishedGames.map((game: IGame) => (
<GameItem id={game.id} name={game.name} cover={game.cover} key={game.id} />
))}
</div>
:
<p>No finished games...</p>}
</div>
</div >
)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment