Skip to content
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ node_modules
dist
dist-ssr
*.local
.env

# Editor directories and files
.vscode/*
Expand Down
13 changes: 13 additions & 0 deletions src/apis/client/interceptors.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { InternalAxiosRequestConfig } from "axios";

export const requestInterceptor = (
config: InternalAxiosRequestConfig,
): InternalAxiosRequestConfig => {
const token: string | null = localStorage.getItem("token");

if (token) {
config.headers.Authorization = `Bearer ${token}`;
}

return config;
};
18 changes: 18 additions & 0 deletions src/apis/client/requestor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import axios, { AxiosError, AxiosInstance } from "axios";

import { requestInterceptor } from "./interceptors";

const requestor: AxiosInstance = axios.create({
baseURL: (import.meta.env.VITE_API_ENDPOINT as string).trim(),
timeout: 60000,
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
});

requestor.interceptors.request.use(requestInterceptor, (error: AxiosError) => {
return Promise.reject(error);
});

export default requestor;
Empty file removed src/apis/index.ts
Empty file.
23 changes: 23 additions & 0 deletions src/apis/services/alertService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { AxiosResponse } from "axios";
import { AlertListResponse, AlertReadListResponse } from "src/types";

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

/* 유저의 알림 목록 조회 */
export const getAlerts = (
userId: string,
offset: number = 0,
limit?: number,
): Promise<AxiosResponse<AlertListResponse>> => {
return requestor.get(`/users/${userId}/alerts`, {
params: { offset, limit },
});
};

/* 알림 읽음 처리 */
export const putAlert = (
userId: string,
alertId: string,
): Promise<AxiosResponse<AlertReadListResponse>> => {
return requestor.put(`/users/${userId}/alerts/${alertId}`);
};
53 changes: 53 additions & 0 deletions src/apis/services/applicationService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { AxiosResponse } from "axios";
import {
ApplicationListResponse,
ApplicationResponse,
ApplicationStatus,
UserApplicationListResponse,
} from "src/types";

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

/* 가게의 특정 공고의 지원 목록 조회 */
export const getShopApplications = (
shopId: string,
noticeId: string,
offset?: number,
limit?: number,
): Promise<AxiosResponse<ApplicationListResponse>> => {
return requestor.get(`/shops/${shopId}/notices/${noticeId}/applications`, {
params: { offset, limit },
});
};

/* 가게의 특정 공고 지원 등록 */
export const postApplication = (
shopId: string,
noticeId: string,
): Promise<AxiosResponse<ApplicationResponse>> => {
return requestor.post(`/shops/${shopId}/notices/${noticeId}/applications`);
};

/* 가게의 특정 공고 지원 승인, 거절 또는 취소소 */
export const putApplication = (
shopId: string,
noticeId: string,
applicationId: string,
status: Exclude<ApplicationStatus, "pending">,
): Promise<AxiosResponse<ApplicationResponse>> => {
return requestor.put(
`/shops/${shopId}/notices/${noticeId}/applications/${applicationId}`,
{ status },
);
};

/* 유저의 지원 목록 조회 */
export const getUserApplications = (
userId: string,
offset?: number,
limit?: number,
): Promise<AxiosResponse<UserApplicationListResponse>> => {
return requestor.get(`/users/${userId}/applications`, {
params: { offset, limit },
});
};
44 changes: 44 additions & 0 deletions src/apis/services/authenticationService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { AxiosResponse } from "axios";
import type { ApiWithHref, UserItem } from "src/types";
import { ApiWrapper } from "src/types";

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

interface LoginItem {
token: string;
user: ApiWithHref<UserItem>;
}

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

/* 로그인 */
export const postAuthentication = async (
payload: LoginRequest,
): Promise<AxiosResponse<LoginResponse>> => {
const res = await requestor.post<LoginResponse>("/token", payload);

const token = res.data.item.token;
const user = res.data.item.user.item;

localStorage.setItem("token", token);
localStorage.setItem("user", JSON.stringify(user));

return res;
};

/* 로그아웃 */
export const logout = () => {
localStorage.removeItem("token");
localStorage.removeItem("user");
};

/* 토큰 반환 */
export const getToken = () => {
return localStorage.getItem("token");
};

/* 인증 여부 확인 */
export const isAuthenticated = () => {
return Boolean(getToken());
};
38 changes: 38 additions & 0 deletions src/apis/services/imageService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import axios, { AxiosResponse } from "axios";
import { ApiWrapper } from "src/types";

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

interface PresignedItem {
url: string;
}
type PresignedResponse = ApiWrapper<PresignedItem>;

/* Presigned URL 생성 */
export const postImage = async (name: string): Promise<string> => {
const res: AxiosResponse<PresignedResponse> = await requestor.post(
"/images",
{ name },
);
return res.data.item.url;
};

/* S3로 이미지 업로드 */
export const putImage = async (
presignedURL: string,
file: File | Blob,
): Promise<AxiosResponse<void>> => {
return axios.put(presignedURL, file, {
headers: { "Content-Type": file.type || "application/octet-stream" },
});
};

/* Presigned URL 조회 */
export const getPublicURL = (presignedURL: string) => {
return presignedURL.split("?")[0];
};

/* 이미지 조회 */
export const getImage = (publicURL: string): Promise<AxiosResponse<Blob>> => {
return axios.get(publicURL, { responseType: "blob" });
};
52 changes: 52 additions & 0 deletions src/apis/services/noticeService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { AxiosResponse } from "axios";
import {
NoticeResponse,
NoticePayload,
GetNoticesParams,
NoticeListResponseWithoutUserApplication,
} from "src/types";

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

/* 공고 조회 */
export const getNotices = (
params?: GetNoticesParams,
): Promise<AxiosResponse<NoticeListResponseWithoutUserApplication>> => {
return requestor.get("/notices", { params });
};

/* 가게의 공고 목록 조회 */
export const getShopNotices = (
shopId: string,
offset?: number,
limit?: number,
): Promise<AxiosResponse<NoticeListResponseWithoutUserApplication>> => {
return requestor.get(`/shops/${shopId}/notices`, {
params: { offset, limit },
});
};

/* 가게 공고 등록 */
export const postNotice = (
shopId: string,
payload: NoticePayload,
): Promise<AxiosResponse<NoticeResponse>> => {
return requestor.post(`/shops/${shopId}/notices`, payload);
};

/* 가게의 특정 공고 조회 */
export const getNotice = (
shopId: string,
noticeId: string,
): Promise<AxiosResponse<NoticeResponse>> => {
return requestor.get(`/shops/${shopId}/notices/${noticeId}`);
};

/* 가게의 특정 공고 수정 */
export const putNotice = (
shopId: string,
noticeId: string,
payload: NoticePayload,
): Promise<AxiosResponse<NoticeResponse>> => {
return requestor.put(`/shops/${shopId}/notices/${noticeId}`, payload);
};
26 changes: 26 additions & 0 deletions src/apis/services/shopService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { AxiosResponse } from "axios";
import { ShopPayload, ShopResponse } from "src/types";

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

/* 가게 등록 */
export const postShop = (
payload: ShopPayload,
): Promise<AxiosResponse<ShopResponse>> => {
return requestor.post("/shops", payload);
};

/* 가게 정보 조회 */
export const getShop = (
shopId: string,
): Promise<AxiosResponse<ShopResponse>> => {
return requestor.get(`/shops/${shopId}`);
};

/* 가게 정보 수정 */
export const putShop = (
shopId: string,
payload: ShopPayload,
): Promise<AxiosResponse<ShopResponse>> => {
return requestor.put(`/shops/${shopId}`, payload);
};
31 changes: 31 additions & 0 deletions src/apis/services/userService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { AxiosResponse } from "axios";
import {
SignupPayload,
SignupResponse,
UserResponse,
UpdateUserPayload,
} from "src/types";

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

/* 회원가입 */
export const postUser = (
payload: SignupPayload,
): Promise<AxiosResponse<SignupResponse>> => {
return requestor.post("/users", payload);
};

/* 내 정보 조회 */
export const getUser = (
userId: string,
): Promise<AxiosResponse<UserResponse>> => {
return requestor.get(`/users/${userId}`);
};

/* 내 정보 수정 */
export const putUser = (
userId: string,
payload: UpdateUserPayload,
): Promise<AxiosResponse<UserResponse>> => {
return requestor.put(`/users/${userId}`, payload);
};
30 changes: 30 additions & 0 deletions src/types/alert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { ApplicationStatus } from "./application";
import type { ApiWrapper, ApiPaged, ApiWithHref, LinkItem } from "./common";
import type { NoticeSummary } from "./notice";
import type { ShopSummary } from "./shop";

export type AlertResult = "accepted" | "rejected";

export interface ApplicationMini {
id: string;
status: Exclude<ApplicationStatus, "canceled">;
}

export interface AlertItem {
id: string;
createdAt: string;
result: AlertResult;
read: boolean;
application: ApiWithHref<ApplicationMini>;
shop: ApiWithHref<ShopSummary>;
notice: ApiWithHref<NoticeSummary>;
}

export type AlertResponse = ApiWrapper<AlertItem>;
export type AlertListResponse = ApiPaged<AlertItem>;
export interface AlertReadListResponse {
offset: number;
limit: number;
items: ApiWrapper<AlertItem>[];
links: LinkItem[];
}
26 changes: 26 additions & 0 deletions src/types/application.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { ApiWrapper, ApiPaged, ApiWithHref } from "./common";
import type { NoticeSummary } from "./notice";
import type { ShopSummary } from "./shop";
import type { UserSummary } from "./user";

export type ApplicationStatus =
| "pending"
| "accepted"
| "rejected"
| "canceled";

export interface ApplicationItem {
id: string;
status: ApplicationStatus;
createdAt: string;
user: ApiWithHref<UserSummary>;
shop: ApiWithHref<ShopSummary>;
notice: ApiWithHref<NoticeSummary>;
}

export type UserApplicationList = Omit<ApplicationItem, "user">;

export type ApplicationResponse = ApiWrapper<ApplicationItem>;
export type ApplicationListResponse = ApiPaged<ApplicationItem>;

export type UserApplicationListResponse = ApiPaged<UserApplicationList>;
Loading