Skip to content

Step 4: domain entities & DTOs #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: step-3-api-layer-data-transformations
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/api/auth.ts → src/api/auth/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Credentials } from "@/types";
import { apiClient } from "../client";

import { apiClient } from "./client";
import { Credentials } from "./dto";

async function login(credentials: Credentials) {
await apiClient.post("/login", credentials);
Expand Down
4 changes: 4 additions & 0 deletions src/api/auth/dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface Credentials {
username: string;
password: string;
}
1 change: 1 addition & 0 deletions src/api/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./api";
17 changes: 0 additions & 17 deletions src/api/feed.ts

This file was deleted.

22 changes: 22 additions & 0 deletions src/api/feed/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
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<FeedResponse>("/feed");
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 };
}

export default { getFeed };
8 changes: 8 additions & 0 deletions src/api/feed/dto.ts
Original file line number Diff line number Diff line change
@@ -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)[];
}
1 change: 1 addition & 0 deletions src/api/feed/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./api";
14 changes: 0 additions & 14 deletions src/api/media.ts

This file was deleted.

15 changes: 15 additions & 0 deletions src/api/media/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +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 imageDto = response.data.data;
return dtoToImage(imageDto);
}

export default { uploadImage };
7 changes: 7 additions & 0 deletions src/api/media/dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export interface ImageDto {
id: string;
type: "image";
attributes: {
url: string;
};
}
1 change: 1 addition & 0 deletions src/api/media/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./api";
10 changes: 10 additions & 0 deletions src/api/media/transform.ts
Original file line number Diff line number Diff line change
@@ -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,
};
}
20 changes: 0 additions & 20 deletions src/api/shout.ts

This file was deleted.

21 changes: 21 additions & 0 deletions src/api/shout/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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 shoutDto = response.data.data;
return dtoToShout(shoutDto);
}

async function createReply({ shoutId, replyId }: CreateShoutReplyInput) {
const response = await apiClient.post<{ data: ShoutDto }>(
`/shout/${shoutId}/reply`,
{ replyId }
);
const replyDto = response.data.data;
return dtoToShout(replyDto);
}

export default { createShout, createReply };
25 changes: 25 additions & 0 deletions src/api/shout/dto.ts
Original file line number Diff line number Diff line change
@@ -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;
}
1 change: 1 addition & 0 deletions src/api/shout/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./api";
16 changes: 16 additions & 0 deletions src/api/shout/transform.ts
Original file line number Diff line number Diff line change
@@ -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,
};
}
26 changes: 0 additions & 26 deletions src/api/user.ts

This file was deleted.

29 changes: 29 additions & 0 deletions src/api/user/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
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 meDto = response.data.data;
return dtoToMe(meDto);
}

async function getUser(handle: string) {
const response = await apiClient.get<{ data: UserDto }>(`/user/${handle}`);
const userDto = response.data.data;
return dtoToUser(userDto);
}

async function getUserShouts(handle: string) {
const response = await apiClient.get<UserShoutsResponse>(
`/user/${handle}/shouts`
);
const shouts = response.data.data.map(dtoToShout);
const images = response.data.included.map(dtoToImage);
return { shouts, images };
}

export default { getMe, getUser, getUserShouts };
22 changes: 22 additions & 0 deletions src/api/user/dto.ts
Original file line number Diff line number Diff line change
@@ -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[];
}
1 change: 1 addition & 0 deletions src/api/user/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "./api";
17 changes: 17 additions & 0 deletions src/api/user/transform.ts
Original file line number Diff line number Diff line change
@@ -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);
}
15 changes: 4 additions & 11 deletions src/components/header/header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import AuthApi from "@/api/auth";
import UserApi from "@/api/user";
import { LoginDialog } from "@/components/login-dialog";
import { Button } from "@/components/ui/button";
import { Me } from "@/types";
import { Me } from "@/domain";

export function Header() {
const [isLoadingMe, setIsLoadingMe] = useState(true);
Expand Down Expand Up @@ -41,16 +41,9 @@ export function Header() {

return (
<div className="w-full flex justify-between p-2">
<Link
className="flex items-center gap-2"
to={`/user/${me.attributes.handle}`}
>
<img
className="w-8 h-8 rounded-full"
src={me.attributes.avatar}
alt={me.attributes.handle}
/>
<span className="font-semibold">{`@${me.attributes.handle}`}</span>
<Link className="flex items-center gap-2" to={`/user/${me.handle}`}>
<img className="w-8 h-8 rounded-full" src={me.avatar} alt={me.handle} />
<span className="font-semibold">{`@${me.handle}`}</span>
</Link>
<Button size="sm" onClick={logout} disabled={isLoadingLogout}>
Logout
Expand Down
10 changes: 5 additions & 5 deletions src/components/shout-list/shout-list.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Shout } from "@/components/shout";
import { User, Shout as TShout, Image } from "@/types";
import { Image, Shout as IShout, User } from "@/domain";

interface ShoutListProps {
shouts: TShout[];
shouts: IShout[];
images: Image[];
users: User[];
}
Expand All @@ -11,9 +11,9 @@ export function ShoutList({ shouts, users, images }: ShoutListProps) {
return (
<ul className="flex flex-col gap-4 items-center">
{shouts.map((shout) => {
const author = users.find((u) => u.id === shout.attributes.authorId);
const image = shout.attributes.imageId
? images.find((i) => i.id === shout.attributes.imageId)
const author = users.find((u) => u.id === shout.authorId);
const image = shout.imageId
? images.find((i) => i.id === shout.imageId)
: undefined;
return (
<li key={shout.id} className="max-w-sm w-full">
Expand Down
Loading