Skip to content

Conversation

@almighty55555
Copy link
Collaborator

#️⃣연관된 이슈

ex) #이슈번호, #이슈번호
Closes #13

📝 PR 유형

해당하는 유형에 'x'로 체크해주세요.

  • 기능 추가 (Feature)
  • 버그 수정 (Bug Fix)
  • 코드 개선 (Refactoring)
  • 스타일 변경 (UI/UX)
  • 문서 작업 (Documentation)
  • 환경 설정 (Configuration)
  • 기타 (Other)

📝작업 내용

이번 PR에서 작업한 내용을 간략히 설명해주세요(이미지 첨부 가능)
api 로직 구현 완료하였습니다. 테스트 진행 결과 모두 문제 없이 잘 작동했습니다. 테스트 코드가 필요하시다면 알려주세요!

  • types 폴더에 common, alert, application, notice, shop, user로 ts 파일 각각 만들어 api 구현에 사용된 type들 저장해두었습니다. types/index.ts에서 한 번에 export하고 있습니다.

  • client/interceptors.tsxlocalStorage에 저장된 JWT 토큰을 꺼내서 Authorization 헤더에 자동으로 붙여주는 역할을 합니다.

  • client/requestor.tsxaxios.create()로 공통 baseURL, 타임아웃, 기본 헤더를 설정한 커스텀 axios 인스턴스를 생성하고 있습니다. 이를 위해 .env 파일을 작성하였습니다. 위에서 만들었던 requestorInterceptor를 연결해서 인증 헤더를 자동으로 주입합니다.

  • alertService.tsx, applicationService.tsx, authenticationService.tsx, imageService.tsx, noticeService.tsx, shopService.tsx, userService.tsx는 각각 알림, 지원, 인증, 이미지, 공고, 가게, 유저 관련 api 호출을 모아 둔 파일입니다. API 명세서를 보고 작성하였습니다.

각각의 로직함수명은 HTTP 메서드 접두사를 사용해 작성했습니다.

스크린샷 (선택)

💬리뷰 요구사항(선택)

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

api에 사용되는 타입들을 types 폴더에 저장해두었는데, apis 폴더에 types 폴더를 새로 만들어 api에 사용되는 type들은 따로 사용하는 게 좋을지 여쭙고자 합니다..!!

@netlify
Copy link

netlify bot commented Apr 23, 2025

Deploy Preview for thejulge1 ready!

Name Link
🔨 Latest commit 8e23a72
🔍 Latest deploy log https://app.netlify.com/sites/thejulge1/deploys/680baa0a68c975000816845d
😎 Deploy Preview https://deploy-preview-19--thejulge1.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Collaborator

@jeonghwanJay jeonghwanJay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굉장히 수고 많으셨습니다 !!

export const requestInterceptor = (
config: InternalAxiosRequestConfig,
): InternalAxiosRequestConfig => {
const token = localStorage.getItem("token") ?? "";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localStorage.getItem('token')은 'token'이나 null을 반환할 것 같습니다.
그래서 아래와 같이 타입을 명시적으로 표시해주어도 좋을 것 같습니다 !

const token: string | null = localStorage.getItem('token') ?? null;

});

requestor.interceptors.request.use(requestInterceptor, (error: AxiosError) =>
Promise.reject(error),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반환값이기에 return을 추가하면 좋을 것 같습니다 !

return Promise.reject(error)

class AlertService {
getAlerts(
userId: string,
offset?: number,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

알림을 위에서부터 최신순으로 정렬해서 보여줄 것 같은데, 그러면 offset은 기본값으로 0으로 설정해 주면 좋을 것 같습니다 !

offset: number = 0,


import requestor from "../client/requestor";

class AlertService {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 AlertService가 class로 정의되어 있는데요.
간단한 API 호출만 처리하고 있기 때문에 getAlerts와 putAlert를 함수로 작성해도 좋을 것 같습니다 !

export const getAlerts = () => {}
export const putAlert = () => {}


import requestor from "../client/requestor";

class ApplicationService {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ApplicationService도 AlertService와 마찬가지로 함수로 작성해도 좋을 것 같습니다 !

export const getShopApplications = () => {}
export const postApplication = () => {}
               .
               .
               .

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

각 메서드들이 어떤 기능을 하는 메서드인지 주석을 한 줄 정도 추가해도 좋을 것 같습니다 !

// 가게의 특정 공고의 지원 목록 조회
getShopApplications() {} 

export type LoginRequest = { email: string; password: string };
export type LoginResponse = ApiWrapper<LoginItem>;

class AuthenticationService {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AuthenticationService도 위와 마찬가지로 class 대신 함수로 작성해도 좋을 것 같습니다 !

export type LoginResponse = ApiWrapper<LoginItem>;

class AuthenticationService {
async postAuthentication(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

postAuthentication는 로그인 처리 및 토큰과 사용자 정보를 로컬스토리지에 저장하는 메서드인 것 같은데,
이것도 주석으로 어떤 기능인지 간단하게 적어도 좋을 것 같습니다 !

import requestor from "../client/requestor";

class NoticeService {
getNotices(params?: {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNotices 메서드는 여래개 파라미터를 관리하고 있으므로, interface로 따로 정의해서 객체로 묶어서 관리해도 좋을 것 같습니다 !

interface GetNoticesParams {
  offset?: number;
  limit?: number;
  address?: string;
  keyword?: string;
  startsAtGte?: string;
  hourlyPayGte?: number;
  sort?: SortKey;
}

getNotices(params: GetNoticesParams) ...

import type { ApiPaged, ApiWrapper } from "./common";
import type { ShopSummary } from "./shop";

export type SortKey = "time" | "pay" | "hour" | "shop";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

각 값이 어떤 기준으로 정렬되는지 주석으로 표시하면 좋을 것 같습니다 !

name: string;
category: ShopCategory;
address1: SeoulDistrict;
address2: string;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

address1은 서울 구역을 의미하는 것 같고, address2는 상세 주소를 의미하는 것 같은데요 !
키 값을 조금 더 명확하게 해서 직관적이게 나타내면 좋을 것 같습니다 !

district: SeoulDistrict;
detailAddress : string;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분 같은 경우, API 명세서에 기재되어 있는 이름을 참고하였습니다. 데이터를 받을 때 명세서와 같은 이름을 쓰는 게 안전할 것 같아, 유지하는 방향으로 가면 어떨까 합니다..!

Copy link
Collaborator

@cozy-ito cozy-ito left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생 많으셨어요! 👍

.env Outdated
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.env 파일은 GitHub 레포지토리에 포함되지 않아야 합니다 🥲
노션이나 디스코드를 통해 파일로 따로 공유해주시는 것이 좋을 것 같아요.

export * from "./shop";
export * from "./notice";
export * from "./application";
export * from "./alert";
Copy link
Collaborator

@cozy-ito cozy-ito Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export type * from ' ... '; 으로 명시적으로 타입을 export 하면 좋을 것 같습니다. 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interface도 함께 export가 필요해 모든 타입 요소를 포함해 적었습니다..!

Comment on lines 18 to 20
application: ApiWrapper<ApplicationMini>;
shop: ApiWrapper<ShopSummary>;
notice: ApiWrapper<NoticeSummary>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

정의해주신 ApiWrapper 타입의 구조는 아래와 같아요.

export interface ApiWrapper<T> {
  item: T;
  links: unknown[];
}

하지만 API 노션 문서를 확인해보면
GET /users/{user_id}/alerts의 응답 데이터 형태는 아래와 같아요.

{
  "offset": "number",
  "limit": "number",
  "items": [
    {
      "item": {
        "id": "string",
        "createdAt": "string",
        "result": "accepted" | "rejected",
        "read": "boolean",
        "application": {
          "item": { ... },
          "href": "string" // 속성명이 links가 아님
        },
        "shop": {
          "item": { ... },
          "href": "string" // 속성명이 links가 아님
        },
        "notice": {
          "item": { ... },
          "href": "string" // 속성명이 links가 아님
        },
        "links": [...]
      }
    },
    "links": [...]
  ]
}

ApiWrapper 제네릭 타입과 실제 데이터 타입이 일치하지 않는 것 같아요 🥲 (links -> href 변경 필요)

>;

export type NoticeResponse = ApiWrapper<NoticeItem>;
export type NoticeListResponse = ApiPaged<NoticeItem>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API 노션 문서를 보면
응답 데이터 유형에 keyword 속성이 optional로 포함되어 있긴 합니다만,
없어도 되려나요 🤔

@@ -0,0 +1,54 @@
export interface ApiWrapper<T> {
item: T;
links: unknown[];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

노션 API 문서 중 공고 조회 응답 데이터를 보면 links 데이터 타입을 확인할 수 있네요!

"links": [
    {
      "rel": "self",
      "description": "현재 페이지",
      "method": "GET",
      "href": "/api/0-1/the-julge/notices?offset=0&limit=10"
    },
]

Comment on lines +14 to +20
currentUserApplication?: {
item: {
id: string;
status: "pending" | "accepted" | "rejected" | "canceled";
createdAt: string;
};
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currentUserApplication 속성은 GET /shops/{shop_id}/notices/{notice_id} API에서만 응답으로 오는데
다른 속성에도 옵셔널로 응답이 오는 것으로 표현되면 실제 없는 데이터임에도 타입에서는 있다고 인식할 거에요. 🤔
(shop 속성도 일부 API 에서만 응답으로 오구요!)

이미 이 정보를 알고 있는 팀원에게는 유용한, 재사용 가능한 타입이겠지만,
처음보는 팀원에게는 혼란의 여지를 줄 수 있어보여요 🤔

NoticeItem 타입은 이렇게 정의하더라도
실제 axios를 통한 요청의 응답 타입에는 포함되지 않는 정보는 제외한 정확한 타입으로 정의하면 어떨까 싶어요. 😅

export type NoticeResponseWithoutUserApplicationInfo = ApiPaged<Omit<NoticeItem, "currentUserApplication">>

Comment on lines 26 to 36
export type ShopSummary = Pick<
ShopItem,
| "id"
| "name"
| "category"
| "address1"
| "address2"
| "description"
| "imageUrl"
| "originalHourlyPay"
>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그냥 하나의 의견입니다! 😅

ShopSummary 타입을 interface로 정의하고 ShopItem 타입을 나머지 타입과 Intersection 타입으로 두면 어떨까요?
Pick 유틸리티 타입으로 필요한 속성명을 다시 작성할 필요가 없을 것 같아서요!

export interface ShopSummary {
  id: string;
  name: string;
  category: ShopCategory;
  address1: SeoulDistrict;
  address2: string;
  description: string;
  imageUrl: string;
  originalHourlyPay: number;
}

export type ShopItem  = ShopSummary & {
  user?: ApiWrapper<UserSummary>;
}

@almighty55555 almighty55555 merged commit b6ba6fb into dev Apr 26, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature (기능 추가) 기능을 추가합니다.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

API 요청 로직 작성

4 participants