From defe4311d68e169f6b9edf3eb5fb7d57a83852ee Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Fri, 5 Apr 2024 08:55:46 +0200 Subject: [PATCH 1/4] Step 4: Add domain types --- src/domain/index.ts | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 src/domain/index.ts diff --git a/src/domain/index.ts b/src/domain/index.ts new file mode 100644 index 0000000..adb161c --- /dev/null +++ b/src/domain/index.ts @@ -0,0 +1,26 @@ +export interface User { + id: string; + handle: string; + avatar: string; + info?: string; + followerIds: string[]; +} + +export interface Me extends User {} + +export interface Shout { + id: string; + createdAt: number; + authorId: string; + text: string; + likes: number; + reshouts: number; + image?: Image; + replies: string[]; + replyTo?: string; +} + +export interface Image { + id: string; + url: string; +} From 9bc0319fb8fc6d81e4174280798e3b055b6a1988 Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Fri, 5 Apr 2024 08:59:30 +0200 Subject: [PATCH 2/4] Step 4: Move existing types to dtos --- src/api/auth.ts | 9 +++++++-- src/{types/index.ts => api/dtos.ts} | 18 +++++++++--------- src/api/feed.ts | 7 +++---- src/api/media.ts | 5 ++--- src/api/shout.ts | 7 +++---- src/api/user.ts | 7 +++---- src/components/header/header.tsx | 4 ++-- src/components/shout-list/shout-list.tsx | 8 ++++---- src/components/shout/shout.tsx | 10 +++++----- src/pages/feed/feed.tsx | 8 ++++---- src/pages/user-profile/user-info.tsx | 4 ++-- src/pages/user-profile/user-profile.tsx | 8 ++++---- 12 files changed, 48 insertions(+), 47 deletions(-) rename src/{types/index.ts => api/dtos.ts} (78%) diff --git a/src/api/auth.ts b/src/api/auth.ts index 4f1b338..9aaf328 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -1,6 +1,11 @@ -import { Credentials } from "@/types"; - import { apiClient } from "./client"; +import { Credentials, MeDto } from "./dtos"; + +export async function getMe() { + const response = await apiClient.get<{ data: MeDto }>("/me"); + const me = response.data.data; + return me; +} async function login(credentials: Credentials) { await apiClient.post("/login", credentials); diff --git a/src/types/index.ts b/src/api/dtos.ts similarity index 78% rename from src/types/index.ts rename to src/api/dtos.ts index df10f45..1b12b61 100644 --- a/src/types/index.ts +++ b/src/api/dtos.ts @@ -1,5 +1,5 @@ /* DATA TYPES */ -export interface User { +export interface UserDto { id: string; type: "user"; attributes: { @@ -12,9 +12,9 @@ export interface User { }; } -export interface Me extends User {} +export interface MeDto extends UserDto {} -export interface Shout { +export interface ShoutDto { id: string; type: "shout"; createdAt: number; @@ -31,7 +31,7 @@ export interface Shout { }; } -export interface Image { +export interface ImageDto { id: string; type: "image"; attributes: { @@ -41,17 +41,17 @@ export interface Image { /* RESPONSE TYPES */ export interface FeedResponse { - data: Shout[]; - included: (User | Image)[]; + data: ShoutDto[]; + included: (UserDto | ImageDto)[]; } export interface UserResponse { - data: User; + data: UserDto; } export interface UserShoutsResponse { - data: Shout[]; - included: Image[]; + data: ShoutDto[]; + included: ImageDto[]; } /* INPUT TYPES */ diff --git a/src/api/feed.ts b/src/api/feed.ts index 6927733..48c4ba3 100644 --- a/src/api/feed.ts +++ b/src/api/feed.ts @@ -1,15 +1,14 @@ -import { FeedResponse, Image, User } from "@/types"; - import { apiClient } from "./client"; +import { FeedResponse, ImageDto, UserDto } from "./dtos"; async function getFeed() { const response = await apiClient.get("/feed"); const shouts = response.data.data; const users = response.data.included.filter( - (u): u is User => u.type === "user" + (u): u is UserDto => u.type === "user" ); const images = response.data.included.filter( - (i): i is Image => i.type === "image" + (i): i is ImageDto => i.type === "image" ); return { shouts, users, images }; } diff --git a/src/api/media.ts b/src/api/media.ts index e796bc1..f86eca9 100644 --- a/src/api/media.ts +++ b/src/api/media.ts @@ -1,12 +1,11 @@ -import { Image } from "@/types"; - import { apiClient } from "./client"; +import { ImageDto } from "./dtos"; async function uploadImage(file: File) { const formData = new FormData(); formData.append("image", file); - const response = await apiClient.post<{ data: Image }>("/image", formData); + const response = await apiClient.post<{ data: ImageDto }>("/image", formData); const image = response.data.data; return image; } diff --git a/src/api/shout.ts b/src/api/shout.ts index 80b729d..be46e89 100644 --- a/src/api/shout.ts +++ b/src/api/shout.ts @@ -1,15 +1,14 @@ -import { CreateShoutInput, CreateShoutReplyInput, Shout } from "@/types"; - import { apiClient } from "./client"; +import { ShoutDto, CreateShoutInput, CreateShoutReplyInput } from "./dtos"; async function createShout(input: CreateShoutInput) { - const response = await apiClient.post<{ data: Shout }>(`/shout`, input); + const response = await apiClient.post<{ data: ShoutDto }>(`/shout`, input); const shout = response.data.data; return shout; } async function createReply({ shoutId, replyId }: CreateShoutReplyInput) { - const response = await apiClient.post<{ data: Shout }>( + const response = await apiClient.post<{ data: ShoutDto }>( `/shout/${shoutId}/reply`, { replyId } ); diff --git a/src/api/user.ts b/src/api/user.ts index 02a487e..b4a8df6 100644 --- a/src/api/user.ts +++ b/src/api/user.ts @@ -1,15 +1,14 @@ -import { Me, User, UserShoutsResponse } from "@/types"; - import { apiClient } from "./client"; +import { MeDto, UserDto, UserShoutsResponse } from "./dtos"; async function getMe() { - const response = await apiClient.get<{ data: Me }>("/me"); + const response = await apiClient.get<{ data: MeDto }>("/me"); const me = response.data.data; return me; } async function getUser(handle: string) { - const response = await apiClient.get<{ data: User }>(`/user/${handle}`); + const response = await apiClient.get<{ data: UserDto }>(`/user/${handle}`); const user = response.data.data; return user; } diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx index 9a7be17..d4dbf47 100644 --- a/src/components/header/header.tsx +++ b/src/components/header/header.tsx @@ -2,15 +2,15 @@ import { useEffect, useState } from "react"; import { Link } from "react-router-dom"; import AuthApi from "@/api/auth"; +import { MeDto } from "@/api/dtos"; import UserApi from "@/api/user"; import { LoginDialog } from "@/components/login-dialog"; import { Button } from "@/components/ui/button"; -import { Me } from "@/types"; export function Header() { const [isLoadingMe, setIsLoadingMe] = useState(true); const [isLoadingLogout, setIsLoadingLogout] = useState(false); - const [me, setMe] = useState(); + const [me, setMe] = useState(); const [hasError, setHasError] = useState(false); useEffect(() => { diff --git a/src/components/shout-list/shout-list.tsx b/src/components/shout-list/shout-list.tsx index d2af028..ec2119c 100644 --- a/src/components/shout-list/shout-list.tsx +++ b/src/components/shout-list/shout-list.tsx @@ -1,10 +1,10 @@ +import { ImageDto, ShoutDto, UserDto } from "@/api/dtos"; import { Shout } from "@/components/shout"; -import { User, Shout as TShout, Image } from "@/types"; interface ShoutListProps { - shouts: TShout[]; - images: Image[]; - users: User[]; + shouts: ShoutDto[]; + images: ImageDto[]; + users: UserDto[]; } export function ShoutList({ shouts, users, images }: ShoutListProps) { diff --git a/src/components/shout/shout.tsx b/src/components/shout/shout.tsx index 179936f..c5f84ac 100644 --- a/src/components/shout/shout.tsx +++ b/src/components/shout/shout.tsx @@ -2,6 +2,7 @@ import { formatDistanceToNow } from "date-fns"; import { Heart, Repeat2, MessageCircle } from "lucide-react"; import { Link } from "react-router-dom"; +import { ImageDto, ShoutDto, UserDto } from "@/api/dtos"; import { Button } from "@/components/ui/button"; import { Card, @@ -10,17 +11,16 @@ import { CardFooter, CardHeader, } from "@/components/ui/card"; -import { Image as IImage, Shout as IShout, User } from "@/types"; import { ReplyDialog } from "./reply-dialog"; interface ShoutProps { - shout: IShout; - author?: User; - image?: IImage; + shout: ShoutDto; + author?: UserDto; + image?: ImageDto; } -const defaultAuthor: User = { +const defaultAuthor: UserDto = { id: "invalid", type: "user", attributes: { diff --git a/src/pages/feed/feed.tsx b/src/pages/feed/feed.tsx index e6e9fca..55ef2b7 100644 --- a/src/pages/feed/feed.tsx +++ b/src/pages/feed/feed.tsx @@ -1,15 +1,15 @@ import { useEffect, useState } from "react"; +import { ImageDto, ShoutDto, UserDto } from "@/api/dtos"; import FeedApi from "@/api/feed"; import { LoadingView } from "@/components/loading"; import { ShoutList } from "@/components/shout-list"; -import { Image, Shout, User } from "@/types"; export function Feed() { const [feed, setFeed] = useState<{ - shouts: Shout[]; - images: Image[]; - users: User[]; + shouts: ShoutDto[]; + images: ImageDto[]; + users: UserDto[]; }>(); const [hasError, setHasError] = useState(false); diff --git a/src/pages/user-profile/user-info.tsx b/src/pages/user-profile/user-info.tsx index 40db567..ea088bf 100644 --- a/src/pages/user-profile/user-info.tsx +++ b/src/pages/user-profile/user-info.tsx @@ -1,7 +1,7 @@ -import { User } from "@/types"; +import { UserDto } from "@/api/dtos"; interface UserInfoProps { - user: User; + user: UserDto; } export function UserInfo({ user }: UserInfoProps) { diff --git a/src/pages/user-profile/user-profile.tsx b/src/pages/user-profile/user-profile.tsx index 60c6500..e5774f9 100644 --- a/src/pages/user-profile/user-profile.tsx +++ b/src/pages/user-profile/user-profile.tsx @@ -1,19 +1,19 @@ import { useEffect, useState } from "react"; import { Navigate, useParams } from "react-router"; +import { ImageDto, ShoutDto, UserDto } from "@/api/dtos"; import UserApi from "@/api/user"; import { LoadingSpinner } from "@/components/loading"; import { ShoutList } from "@/components/shout-list"; -import { Image, Shout, User } from "@/types"; import { UserInfo } from "./user-info"; export function UserProfile() { const { handle } = useParams<{ handle: string }>(); - const [user, setUser] = useState(); - const [shouts, setShouts] = useState(); - const [images, setImages] = useState([]); + const [user, setUser] = useState(); + const [shouts, setShouts] = useState(); + const [images, setImages] = useState([]); const [hasError, setHasError] = useState(false); useEffect(() => { From fb665062cbbe22f807ab47fde65ee566e077ad6b Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Fri, 5 Apr 2024 09:21:51 +0200 Subject: [PATCH 3/4] Step 4: Create separate API folders --- src/api/auth.ts | 18 ------ src/api/auth/api.ts | 13 +++++ src/api/auth/dto.ts | 4 ++ src/api/auth/index.ts | 1 + src/api/dtos.ts | 71 ------------------------ src/api/{feed.ts => feed/api.ts} | 7 ++- src/api/feed/dto.ts | 8 +++ src/api/feed/index.ts | 1 + src/api/{media.ts => media/api.ts} | 5 +- src/api/media/dto.ts | 7 +++ src/api/media/index.ts | 1 + src/api/{shout.ts => shout/api.ts} | 5 +- src/api/shout/dto.ts | 25 +++++++++ src/api/shout/index.ts | 1 + src/api/{user.ts => user/api.ts} | 5 +- src/api/user/dto.ts | 22 ++++++++ src/api/user/index.ts | 1 + src/components/header/header.tsx | 2 +- src/components/shout-list/shout-list.tsx | 4 +- src/components/shout/shout.tsx | 4 +- src/pages/feed/feed.tsx | 4 +- src/pages/user-profile/user-info.tsx | 2 +- src/pages/user-profile/user-profile.tsx | 4 +- 23 files changed, 112 insertions(+), 103 deletions(-) delete mode 100644 src/api/auth.ts create mode 100644 src/api/auth/api.ts create mode 100644 src/api/auth/dto.ts create mode 100644 src/api/auth/index.ts delete mode 100644 src/api/dtos.ts rename src/api/{feed.ts => feed/api.ts} (71%) create mode 100644 src/api/feed/dto.ts create mode 100644 src/api/feed/index.ts rename src/api/{media.ts => media/api.ts} (79%) create mode 100644 src/api/media/dto.ts create mode 100644 src/api/media/index.ts rename src/api/{shout.ts => shout/api.ts} (80%) create mode 100644 src/api/shout/dto.ts create mode 100644 src/api/shout/index.ts rename src/api/{user.ts => user/api.ts} (85%) create mode 100644 src/api/user/dto.ts create mode 100644 src/api/user/index.ts diff --git a/src/api/auth.ts b/src/api/auth.ts deleted file mode 100644 index 9aaf328..0000000 --- a/src/api/auth.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { apiClient } from "./client"; -import { Credentials, MeDto } from "./dtos"; - -export async function getMe() { - const response = await apiClient.get<{ data: MeDto }>("/me"); - const me = response.data.data; - return me; -} - -async function login(credentials: Credentials) { - await apiClient.post("/login", credentials); -} - -async function logout() { - await apiClient.post("/logout"); -} - -export default { login, logout }; diff --git a/src/api/auth/api.ts b/src/api/auth/api.ts new file mode 100644 index 0000000..c5e078e --- /dev/null +++ b/src/api/auth/api.ts @@ -0,0 +1,13 @@ +import { apiClient } from "../client"; + +import { Credentials } from "./dto"; + +async function login(credentials: Credentials) { + await apiClient.post("/login", credentials); +} + +async function logout() { + await apiClient.post("/logout"); +} + +export default { login, logout }; diff --git a/src/api/auth/dto.ts b/src/api/auth/dto.ts new file mode 100644 index 0000000..279aabe --- /dev/null +++ b/src/api/auth/dto.ts @@ -0,0 +1,4 @@ +export interface Credentials { + username: string; + password: string; +} diff --git a/src/api/auth/index.ts b/src/api/auth/index.ts new file mode 100644 index 0000000..9e19686 --- /dev/null +++ b/src/api/auth/index.ts @@ -0,0 +1 @@ +export { default } from "./api"; diff --git a/src/api/dtos.ts b/src/api/dtos.ts deleted file mode 100644 index 1b12b61..0000000 --- a/src/api/dtos.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* DATA TYPES */ -export interface UserDto { - id: string; - type: "user"; - attributes: { - handle: string; - avatar: string; - info?: string; - }; - relationships: { - followerIds: string[]; - }; -} - -export interface MeDto extends UserDto {} - -export interface ShoutDto { - id: string; - type: "shout"; - createdAt: number; - attributes: { - authorId: string; - text: string; - likes: number; - reshouts: number; - imageId?: string; - }; - relationships: { - replies: string[]; - replyTo?: string; - }; -} - -export interface ImageDto { - id: string; - type: "image"; - attributes: { - url: string; - }; -} - -/* RESPONSE TYPES */ -export interface FeedResponse { - data: ShoutDto[]; - included: (UserDto | ImageDto)[]; -} - -export interface UserResponse { - data: UserDto; -} - -export interface UserShoutsResponse { - data: ShoutDto[]; - included: ImageDto[]; -} - -/* INPUT TYPES */ -export interface Credentials { - username: string; - password: string; -} - -export interface CreateShoutInput { - message: string; - imageId?: string; -} - -export interface CreateShoutReplyInput { - shoutId: string; - replyId: string; -} diff --git a/src/api/feed.ts b/src/api/feed/api.ts similarity index 71% rename from src/api/feed.ts rename to src/api/feed/api.ts index 48c4ba3..a1c91cd 100644 --- a/src/api/feed.ts +++ b/src/api/feed/api.ts @@ -1,5 +1,8 @@ -import { apiClient } from "./client"; -import { FeedResponse, ImageDto, UserDto } from "./dtos"; +import { apiClient } from "../client"; +import { ImageDto } from "../media/dto"; +import { UserDto } from "../user/dto"; + +import { FeedResponse } from "./dto"; async function getFeed() { const response = await apiClient.get("/feed"); diff --git a/src/api/feed/dto.ts b/src/api/feed/dto.ts new file mode 100644 index 0000000..c5174c8 --- /dev/null +++ b/src/api/feed/dto.ts @@ -0,0 +1,8 @@ +import { ImageDto } from "../media/dto"; +import { ShoutDto } from "../shout/dto"; +import { UserDto } from "../user/dto"; + +export interface FeedResponse { + data: ShoutDto[]; + included: (UserDto | ImageDto)[]; +} diff --git a/src/api/feed/index.ts b/src/api/feed/index.ts new file mode 100644 index 0000000..9e19686 --- /dev/null +++ b/src/api/feed/index.ts @@ -0,0 +1 @@ +export { default } from "./api"; diff --git a/src/api/media.ts b/src/api/media/api.ts similarity index 79% rename from src/api/media.ts rename to src/api/media/api.ts index f86eca9..67f6413 100644 --- a/src/api/media.ts +++ b/src/api/media/api.ts @@ -1,5 +1,6 @@ -import { apiClient } from "./client"; -import { ImageDto } from "./dtos"; +import { apiClient } from "../client"; + +import { ImageDto } from "./dto"; async function uploadImage(file: File) { const formData = new FormData(); diff --git a/src/api/media/dto.ts b/src/api/media/dto.ts new file mode 100644 index 0000000..b5b83fa --- /dev/null +++ b/src/api/media/dto.ts @@ -0,0 +1,7 @@ +export interface ImageDto { + id: string; + type: "image"; + attributes: { + url: string; + }; +} diff --git a/src/api/media/index.ts b/src/api/media/index.ts new file mode 100644 index 0000000..9e19686 --- /dev/null +++ b/src/api/media/index.ts @@ -0,0 +1 @@ +export { default } from "./api"; diff --git a/src/api/shout.ts b/src/api/shout/api.ts similarity index 80% rename from src/api/shout.ts rename to src/api/shout/api.ts index be46e89..4534f54 100644 --- a/src/api/shout.ts +++ b/src/api/shout/api.ts @@ -1,5 +1,6 @@ -import { apiClient } from "./client"; -import { ShoutDto, CreateShoutInput, CreateShoutReplyInput } from "./dtos"; +import { apiClient } from "../client"; + +import { CreateShoutInput, CreateShoutReplyInput, ShoutDto } from "./dto"; async function createShout(input: CreateShoutInput) { const response = await apiClient.post<{ data: ShoutDto }>(`/shout`, input); diff --git a/src/api/shout/dto.ts b/src/api/shout/dto.ts new file mode 100644 index 0000000..b448437 --- /dev/null +++ b/src/api/shout/dto.ts @@ -0,0 +1,25 @@ +export interface ShoutDto { + id: string; + type: "shout"; + createdAt: number; + attributes: { + authorId: string; + text: string; + likes: number; + reshouts: number; + imageId?: string; + }; + relationships: { + replies: string[]; + replyTo?: string; + }; +} +export interface CreateShoutInput { + message: string; + imageId?: string; +} + +export interface CreateShoutReplyInput { + shoutId: string; + replyId: string; +} diff --git a/src/api/shout/index.ts b/src/api/shout/index.ts new file mode 100644 index 0000000..9e19686 --- /dev/null +++ b/src/api/shout/index.ts @@ -0,0 +1 @@ +export { default } from "./api"; diff --git a/src/api/user.ts b/src/api/user/api.ts similarity index 85% rename from src/api/user.ts rename to src/api/user/api.ts index b4a8df6..efd38ef 100644 --- a/src/api/user.ts +++ b/src/api/user/api.ts @@ -1,5 +1,6 @@ -import { apiClient } from "./client"; -import { MeDto, UserDto, UserShoutsResponse } from "./dtos"; +import { apiClient } from "../client"; + +import { MeDto, UserDto, UserShoutsResponse } from "./dto"; async function getMe() { const response = await apiClient.get<{ data: MeDto }>("/me"); diff --git a/src/api/user/dto.ts b/src/api/user/dto.ts new file mode 100644 index 0000000..48d4f20 --- /dev/null +++ b/src/api/user/dto.ts @@ -0,0 +1,22 @@ +import { ImageDto } from "../media/dto"; +import { ShoutDto } from "../shout/dto"; + +export interface UserDto { + id: string; + type: "user"; + attributes: { + handle: string; + avatar: string; + info?: string; + }; + relationships: { + followerIds: string[]; + }; +} + +export interface MeDto extends UserDto {} + +export interface UserShoutsResponse { + data: ShoutDto[]; + included: ImageDto[]; +} diff --git a/src/api/user/index.ts b/src/api/user/index.ts new file mode 100644 index 0000000..9e19686 --- /dev/null +++ b/src/api/user/index.ts @@ -0,0 +1 @@ +export { default } from "./api"; diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx index d4dbf47..71a6073 100644 --- a/src/components/header/header.tsx +++ b/src/components/header/header.tsx @@ -2,8 +2,8 @@ import { useEffect, useState } from "react"; import { Link } from "react-router-dom"; import AuthApi from "@/api/auth"; -import { MeDto } from "@/api/dtos"; import UserApi from "@/api/user"; +import { MeDto } from "@/api/user/dto"; import { LoginDialog } from "@/components/login-dialog"; import { Button } from "@/components/ui/button"; diff --git a/src/components/shout-list/shout-list.tsx b/src/components/shout-list/shout-list.tsx index ec2119c..7841dc7 100644 --- a/src/components/shout-list/shout-list.tsx +++ b/src/components/shout-list/shout-list.tsx @@ -1,4 +1,6 @@ -import { ImageDto, ShoutDto, UserDto } from "@/api/dtos"; +import { ImageDto } from "@/api/media/dto"; +import { ShoutDto } from "@/api/shout/dto"; +import { UserDto } from "@/api/user/dto"; import { Shout } from "@/components/shout"; interface ShoutListProps { diff --git a/src/components/shout/shout.tsx b/src/components/shout/shout.tsx index c5f84ac..b31b302 100644 --- a/src/components/shout/shout.tsx +++ b/src/components/shout/shout.tsx @@ -2,7 +2,9 @@ import { formatDistanceToNow } from "date-fns"; import { Heart, Repeat2, MessageCircle } from "lucide-react"; import { Link } from "react-router-dom"; -import { ImageDto, ShoutDto, UserDto } from "@/api/dtos"; +import { ImageDto } from "@/api/media/dto"; +import { ShoutDto } from "@/api/shout/dto"; +import { UserDto } from "@/api/user/dto"; import { Button } from "@/components/ui/button"; import { Card, diff --git a/src/pages/feed/feed.tsx b/src/pages/feed/feed.tsx index 55ef2b7..36e4256 100644 --- a/src/pages/feed/feed.tsx +++ b/src/pages/feed/feed.tsx @@ -1,7 +1,9 @@ import { useEffect, useState } from "react"; -import { ImageDto, ShoutDto, UserDto } from "@/api/dtos"; import FeedApi from "@/api/feed"; +import { ImageDto } from "@/api/media/dto"; +import { ShoutDto } from "@/api/shout/dto"; +import { UserDto } from "@/api/user/dto"; import { LoadingView } from "@/components/loading"; import { ShoutList } from "@/components/shout-list"; diff --git a/src/pages/user-profile/user-info.tsx b/src/pages/user-profile/user-info.tsx index ea088bf..fe4b886 100644 --- a/src/pages/user-profile/user-info.tsx +++ b/src/pages/user-profile/user-info.tsx @@ -1,4 +1,4 @@ -import { UserDto } from "@/api/dtos"; +import { UserDto } from "@/api/user/dto"; interface UserInfoProps { user: UserDto; diff --git a/src/pages/user-profile/user-profile.tsx b/src/pages/user-profile/user-profile.tsx index e5774f9..13e538d 100644 --- a/src/pages/user-profile/user-profile.tsx +++ b/src/pages/user-profile/user-profile.tsx @@ -1,8 +1,10 @@ import { useEffect, useState } from "react"; import { Navigate, useParams } from "react-router"; -import { ImageDto, ShoutDto, UserDto } from "@/api/dtos"; +import { ImageDto } from "@/api/media/dto"; +import { ShoutDto } from "@/api/shout/dto"; import UserApi from "@/api/user"; +import { UserDto } from "@/api/user/dto"; import { LoadingSpinner } from "@/components/loading"; import { ShoutList } from "@/components/shout-list"; From d9959081485f5818445f20b96401d93bcce92d73 Mon Sep 17 00:00:00 2001 From: Johannes Kettmann Date: Fri, 5 Apr 2024 09:40:03 +0200 Subject: [PATCH 4/4] Step 4: Transform dtos to domain entities --- src/api/feed/api.ts | 17 +++++---- src/api/media/api.ts | 5 +-- src/api/media/transform.ts | 10 ++++++ src/api/shout/api.ts | 9 ++--- src/api/shout/transform.ts | 16 +++++++++ src/api/user/api.ts | 15 ++++---- src/api/user/transform.ts | 17 +++++++++ src/components/header/header.tsx | 17 +++------ src/components/shout-list/shout-list.tsx | 16 ++++----- src/components/shout/shout.tsx | 45 ++++++++++-------------- src/domain/index.ts | 2 +- src/pages/feed/feed.tsx | 10 +++--- src/pages/user-profile/user-info.tsx | 14 ++++---- src/pages/user-profile/user-profile.tsx | 10 +++--- 14 files changed, 116 insertions(+), 87 deletions(-) create mode 100644 src/api/media/transform.ts create mode 100644 src/api/shout/transform.ts create mode 100644 src/api/user/transform.ts diff --git a/src/api/feed/api.ts b/src/api/feed/api.ts index a1c91cd..fd95a28 100644 --- a/src/api/feed/api.ts +++ b/src/api/feed/api.ts @@ -1,18 +1,21 @@ import { apiClient } from "../client"; import { ImageDto } from "../media/dto"; +import { dtoToImage } from "../media/transform"; +import { dtoToShout } from "../shout/transform"; import { UserDto } from "../user/dto"; +import { dtoToUser } from "../user/transform"; import { FeedResponse } from "./dto"; async function getFeed() { const response = await apiClient.get("/feed"); - const shouts = response.data.data; - const users = response.data.included.filter( - (u): u is UserDto => u.type === "user" - ); - const images = response.data.included.filter( - (i): i is ImageDto => i.type === "image" - ); + const shouts = response.data.data.map(dtoToShout); + const users = response.data.included + .filter((u): u is UserDto => u.type === "user") + .map(dtoToUser); + const images = response.data.included + .filter((i): i is ImageDto => i.type === "image") + .map(dtoToImage); return { shouts, users, images }; } diff --git a/src/api/media/api.ts b/src/api/media/api.ts index 67f6413..9d1e182 100644 --- a/src/api/media/api.ts +++ b/src/api/media/api.ts @@ -1,14 +1,15 @@ import { apiClient } from "../client"; import { ImageDto } from "./dto"; +import { dtoToImage } from "./transform"; async function uploadImage(file: File) { const formData = new FormData(); formData.append("image", file); const response = await apiClient.post<{ data: ImageDto }>("/image", formData); - const image = response.data.data; - return image; + const imageDto = response.data.data; + return dtoToImage(imageDto); } export default { uploadImage }; diff --git a/src/api/media/transform.ts b/src/api/media/transform.ts new file mode 100644 index 0000000..444f756 --- /dev/null +++ b/src/api/media/transform.ts @@ -0,0 +1,10 @@ +import { Image } from "@/domain"; + +import { ImageDto } from "./dto"; + +export function dtoToImage(dto: ImageDto): Image { + return { + id: dto.id, + url: dto.attributes.url, + }; +} diff --git a/src/api/shout/api.ts b/src/api/shout/api.ts index 4534f54..d5ce43f 100644 --- a/src/api/shout/api.ts +++ b/src/api/shout/api.ts @@ -1,11 +1,12 @@ import { apiClient } from "../client"; import { CreateShoutInput, CreateShoutReplyInput, ShoutDto } from "./dto"; +import { dtoToShout } from "./transform"; async function createShout(input: CreateShoutInput) { const response = await apiClient.post<{ data: ShoutDto }>(`/shout`, input); - const shout = response.data.data; - return shout; + const shoutDto = response.data.data; + return dtoToShout(shoutDto); } async function createReply({ shoutId, replyId }: CreateShoutReplyInput) { @@ -13,8 +14,8 @@ async function createReply({ shoutId, replyId }: CreateShoutReplyInput) { `/shout/${shoutId}/reply`, { replyId } ); - const reply = response.data.data; - return reply; + const replyDto = response.data.data; + return dtoToShout(replyDto); } export default { createShout, createReply }; diff --git a/src/api/shout/transform.ts b/src/api/shout/transform.ts new file mode 100644 index 0000000..4a305d0 --- /dev/null +++ b/src/api/shout/transform.ts @@ -0,0 +1,16 @@ +import { Shout } from "@/domain"; + +import { ShoutDto } from "./dto"; + +export function dtoToShout(shoutDto: ShoutDto): Shout { + return { + id: shoutDto.id, + createdAt: shoutDto.createdAt, + authorId: shoutDto.attributes.authorId, + imageId: shoutDto.attributes.imageId, + likes: shoutDto.attributes.likes, + reshouts: shoutDto.attributes.reshouts, + text: shoutDto.attributes.text, + replies: shoutDto.relationships.replies, + }; +} diff --git a/src/api/user/api.ts b/src/api/user/api.ts index efd38ef..0e85dd9 100644 --- a/src/api/user/api.ts +++ b/src/api/user/api.ts @@ -1,25 +1,28 @@ import { apiClient } from "../client"; +import { dtoToImage } from "../media/transform"; +import { dtoToShout } from "../shout/transform"; import { MeDto, UserDto, UserShoutsResponse } from "./dto"; +import { dtoToMe, dtoToUser } from "./transform"; async function getMe() { const response = await apiClient.get<{ data: MeDto }>("/me"); - const me = response.data.data; - return me; + const meDto = response.data.data; + return dtoToMe(meDto); } async function getUser(handle: string) { const response = await apiClient.get<{ data: UserDto }>(`/user/${handle}`); - const user = response.data.data; - return user; + const userDto = response.data.data; + return dtoToUser(userDto); } async function getUserShouts(handle: string) { const response = await apiClient.get( `/user/${handle}/shouts` ); - const shouts = response.data.data; - const images = response.data.included; + const shouts = response.data.data.map(dtoToShout); + const images = response.data.included.map(dtoToImage); return { shouts, images }; } diff --git a/src/api/user/transform.ts b/src/api/user/transform.ts new file mode 100644 index 0000000..31a8f68 --- /dev/null +++ b/src/api/user/transform.ts @@ -0,0 +1,17 @@ +import { Me, User } from "@/domain"; + +import { MeDto, UserDto } from "./dto"; + +export function dtoToUser(dto: UserDto): User { + return { + id: dto.id, + avatar: dto.attributes.avatar, + handle: dto.attributes.handle, + info: dto.attributes.info, + followerIds: dto.relationships.followerIds, + }; +} + +export function dtoToMe(dto: MeDto): Me { + return dtoToUser(dto); +} diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx index 71a6073..24b0fe6 100644 --- a/src/components/header/header.tsx +++ b/src/components/header/header.tsx @@ -3,14 +3,14 @@ import { Link } from "react-router-dom"; import AuthApi from "@/api/auth"; import UserApi from "@/api/user"; -import { MeDto } from "@/api/user/dto"; import { LoginDialog } from "@/components/login-dialog"; import { Button } from "@/components/ui/button"; +import { Me } from "@/domain"; export function Header() { const [isLoadingMe, setIsLoadingMe] = useState(true); const [isLoadingLogout, setIsLoadingLogout] = useState(false); - const [me, setMe] = useState(); + const [me, setMe] = useState(); const [hasError, setHasError] = useState(false); useEffect(() => { @@ -41,16 +41,9 @@ export function Header() { return (
- - {me.attributes.handle} - {`@${me.attributes.handle}`} + + {me.handle} + {`@${me.handle}`} diff --git a/src/domain/index.ts b/src/domain/index.ts index adb161c..f157779 100644 --- a/src/domain/index.ts +++ b/src/domain/index.ts @@ -15,7 +15,7 @@ export interface Shout { text: string; likes: number; reshouts: number; - image?: Image; + imageId?: string; replies: string[]; replyTo?: string; } diff --git a/src/pages/feed/feed.tsx b/src/pages/feed/feed.tsx index 36e4256..1e03fb6 100644 --- a/src/pages/feed/feed.tsx +++ b/src/pages/feed/feed.tsx @@ -1,17 +1,15 @@ import { useEffect, useState } from "react"; import FeedApi from "@/api/feed"; -import { ImageDto } from "@/api/media/dto"; -import { ShoutDto } from "@/api/shout/dto"; -import { UserDto } from "@/api/user/dto"; import { LoadingView } from "@/components/loading"; import { ShoutList } from "@/components/shout-list"; +import { Image, Shout, User } from "@/domain"; export function Feed() { const [feed, setFeed] = useState<{ - shouts: ShoutDto[]; - images: ImageDto[]; - users: UserDto[]; + shouts: Shout[]; + images: Image[]; + users: User[]; }>(); const [hasError, setHasError] = useState(false); diff --git a/src/pages/user-profile/user-info.tsx b/src/pages/user-profile/user-info.tsx index fe4b886..8a65c23 100644 --- a/src/pages/user-profile/user-info.tsx +++ b/src/pages/user-profile/user-info.tsx @@ -1,7 +1,7 @@ -import { UserDto } from "@/api/user/dto"; +import { User } from "@/domain"; interface UserInfoProps { - user: UserDto; + user: User; } export function UserInfo({ user }: UserInfoProps) { @@ -9,17 +9,17 @@ export function UserInfo({ user }: UserInfoProps) {
{`${user.attributes.handle}'s
-

@{user.attributes.handle}

+

@{user.handle}

- ({user.relationships.followerIds.length} follower) + ({user.followerIds.length} follower)
-

{user.attributes.info || "Shush! I'm a ghost."}

+

{user.info || "Shush! I'm a ghost."}

); diff --git a/src/pages/user-profile/user-profile.tsx b/src/pages/user-profile/user-profile.tsx index 13e538d..17a5192 100644 --- a/src/pages/user-profile/user-profile.tsx +++ b/src/pages/user-profile/user-profile.tsx @@ -1,21 +1,19 @@ import { useEffect, useState } from "react"; import { Navigate, useParams } from "react-router"; -import { ImageDto } from "@/api/media/dto"; -import { ShoutDto } from "@/api/shout/dto"; import UserApi from "@/api/user"; -import { UserDto } from "@/api/user/dto"; import { LoadingSpinner } from "@/components/loading"; import { ShoutList } from "@/components/shout-list"; +import { Image, Shout, User } from "@/domain"; import { UserInfo } from "./user-info"; export function UserProfile() { const { handle } = useParams<{ handle: string }>(); - const [user, setUser] = useState(); - const [shouts, setShouts] = useState(); - const [images, setImages] = useState([]); + const [user, setUser] = useState(); + const [shouts, setShouts] = useState(); + const [images, setImages] = useState([]); const [hasError, setHasError] = useState(false); useEffect(() => {