Skip to content

Commit

Permalink
[RSN-66] Create services to use backend (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
raczu authored Jun 22, 2024
1 parent 3d0b816 commit a1b88eb
Show file tree
Hide file tree
Showing 50 changed files with 1,005 additions and 108 deletions.
2 changes: 1 addition & 1 deletion Client/reasn-client/.husky/pre-commit
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

(cd ./Client/reasn-client && yarn lint-staged)
(cd ./Server/ReasnAPI && dotnet format)
if ! git diff --quiet; then
if git diff --name-only --quiet | grep '\.cs$'; then
echo "🚫 dotnet format made changes, commit aborted."
exit 1
fi
79 changes: 79 additions & 0 deletions Client/reasn-client/apps/web/lib/request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import ApiAuthorizationError from "@reasn/common/src/errors/ApiAuthorizationError";
import ApiConnectionError from "@reasn/common/src/errors/ApiConnectionError";
import fetch from "cross-fetch";
import { getToken } from "@/lib/token";

export enum HttpMethod {
GET = "GET",
POST = "POST",
PUT = "PUT",
DELETE = "DELETE",
}

export type ProblemDetails = {
type?: string;
title: string;
status: number;
details?: string;
instance?: string;
};

export type RequestOptions = {
method: HttpMethod;
body?: Object | FormData;
authRequired?: boolean;
};

export const sendRequest = async <T>(
url: string | URL,
{ method, body = {}, authRequired = false }: RequestOptions,
): Promise<T> => {
try {
let headers: HeadersInit = new Headers();
if (authRequired) {
const token = getToken();
if (!token) {
throw new ApiAuthorizationError(
"Unauthorized access. No token found in cookies",
);
}
headers.set("Authorization", `Bearer ${token}`);
}

const fetchOptions: RequestInit = {
method: method,
headers,
};

if (method == HttpMethod.POST || method == HttpMethod.PUT) {
if (body instanceof FormData) {
fetchOptions.body = body;
} else {
headers.set("Content-Type", "application/json");
fetchOptions.body = JSON.stringify(body);
}
}

const response = await fetch(url, fetchOptions);
if (!response.ok) {
const problemDetails = (await response.json()) as ProblemDetails;
console.error(
`[HTTP ${problemDetails.instance ?? ""} ${response.status}]: ${
problemDetails.details ?? problemDetails.title
}`,
);

throw new ApiConnectionError(
response.status,
problemDetails.details ?? problemDetails.title,
);
}

return response.json().catch(() => {}) as T;
} catch (error) {
console.error(
`Error while sending request to ${url} with method ${method}: ${error}`,
);
throw Error;
}
};
51 changes: 51 additions & 0 deletions Client/reasn-client/apps/web/lib/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { jwtDecode, JwtPayload } from "jwt-decode";
import { UserRole } from "@reasn/common/src/enums/modelsEnums";
import { getToken } from "@/lib/token";

export const SESSION_DEFAULT = {
token: null,
isAuthenticated: () => false,
};

type User = {
email: string;
role: UserRole;
};

export type Session = {
token: string | null;
user?: User;
isAuthenticated: () => boolean;
};

interface ReasnJwtPayload extends JwtPayload {
email?: string;
role?: UserRole;
}

export const getSession = (): Session => {
const token = getToken();
if (!token) {
return SESSION_DEFAULT;
}

try {
const decodedToken = jwtDecode<ReasnJwtPayload>(token);
const isUserValid =
decodedToken.email !== undefined && decodedToken.role !== undefined;

return {
token: token,
user: isUserValid
? {
email: decodedToken.email as string,
role: decodedToken.role as UserRole,
}
: undefined,
isAuthenticated: () => token !== null && isUserValid,
};
} catch (error) {
console.error("[ERROR]: Failed to decode JWT token");
return SESSION_DEFAULT;
}
};
23 changes: 23 additions & 0 deletions Client/reasn-client/apps/web/lib/token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { cookies } from "next/headers";
import { TokenPayload } from "@reasn/common/src/schemas/tokenPayload";

const TOKEN_KEY = "REASN_TOKEN";

export const setToken = (tokenPayload: TokenPayload): void => {
cookies().set(TOKEN_KEY, tokenPayload.accessToken, {
maxAge: tokenPayload.expiresIn,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
httpOnly: true,
});
};

export const clearToken = (): void => {
cookies().set(TOKEN_KEY, "", { maxAge: 0 });
};

export const getToken = (): string | null => {
const token = cookies().get(TOKEN_KEY)?.value;
if (!token) return null;
return token;
};
3 changes: 3 additions & 0 deletions Client/reasn-client/apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
"lint": "next lint"
},
"dependencies": {
"@reasn/common": "*",
"@reasn/ui": "*",
"@types/url-search-params": "^1.1.2",
"jwt-decode": "^4.0.0",
"next": "^14.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
34 changes: 34 additions & 0 deletions Client/reasn-client/apps/web/services/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { sendRequest, HttpMethod } from "@/lib/request";
import {
TokenPayload,
TokenPayloadMapper,
} from "@reasn/common/src/schemas/TokenPayload";
import { LoginRequest } from "@reasn/common/src/schemas/LoginRequest";
import { RegisterRequest } from "@reasn/common/src/schemas/RegisterRequest";
import { UserDto, UserDtoMapper } from "@reasn/common/src/schemas/UserDto";

const baseUrl = `${process.env.REASN_API_URL}/api/v1/auth`;

export const login = async (
loginRequest: LoginRequest,
): Promise<TokenPayload> => {
const url = new URL(`${baseUrl}/login`);

const response = await sendRequest<TokenPayload>(url, {
method: HttpMethod.POST,
body: loginRequest,
});
return TokenPayloadMapper.fromObject(response);
};

export const register = async (
registerRequest: RegisterRequest,
): Promise<UserDto> => {
const url = new URL(`${baseUrl}/register`);

const response = await sendRequest<UserDto>(url, {
method: HttpMethod.POST,
body: registerRequest,
});
return UserDtoMapper.fromObject(response);
};
181 changes: 181 additions & 0 deletions Client/reasn-client/apps/web/services/event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import { sendRequest, HttpMethod } from "@/lib/request";
import { EventRequest } from "@reasn/common/src/schemas/EventRequest";
import {
EventResponse,
EventResponseMapper,
} from "@reasn/common/src/schemas/EventResponse";
import { ParameterDto } from "@reasn/common/src/schemas/ParameterDto";
import { TagDto } from "@reasn/common/src/schemas/TagDto";

const baseUrl = `${process.env.REASN_API_URL}/api/v1/events`;

export const getEvents = async (
params: Record<string, string> = {},
): Promise<EventResponse[]> => {
const url = new URL(baseUrl);
url.search = new URLSearchParams(params).toString();

const response = await sendRequest<EventResponse[]>(url, {
method: HttpMethod.GET,
});
return response;
};

export const createEvent = async (
eventRequest: EventRequest,
): Promise<EventResponse> => {
const url = new URL(baseUrl);

const response = await sendRequest<EventResponse>(url, {
method: HttpMethod.POST,
body: eventRequest,
authRequired: true,
});
return EventResponseMapper.fromObject(response);
};

export const getEventBySlug = async (slug: string): Promise<EventResponse> => {
const url = new URL(`${baseUrl}/${slug}`);

const response = await sendRequest<EventResponse>(url, {
method: HttpMethod.GET,
});
return EventResponseMapper.fromObject(response);
};

export const updateEvent = async (
slug: string,
event: any,
): Promise<EventResponse> => {
const url = new URL(`${baseUrl}/${slug}`);

const response = await sendRequest<EventResponse>(url, {
method: HttpMethod.PUT,
body: event,
authRequired: true,
});
return EventResponseMapper.fromObject(response);
};

export const getEventsRequests = async (): Promise<EventResponse[]> => {
const url = new URL(`${baseUrl}/requests`);

const response = await sendRequest<EventResponse[]>(url, {
method: HttpMethod.GET,
authRequired: true,
});
return response;
};

export const approveEventRequest = async (slug: string): Promise<Object> => {
const url = new URL(`${baseUrl}/requests/${slug}`);

const response = await sendRequest<Object>(url, {
method: HttpMethod.POST,
authRequired: true,
});
return response;
};

export const addEventImage = async (
slug: string,
images: Blob[],
): Promise<Object> => {
const url = new URL(`${baseUrl}/${slug}/images`);

const formData = new FormData();
images.forEach((image) => {
formData.append("images", image);
});

const response = await sendRequest<Object>(url, {
method: HttpMethod.POST,
body: formData,
authRequired: true,
});
return response;
};

export const updateEventImage = async (
slug: string,
images: Blob[],
): Promise<Object> => {
const url = new URL(`${baseUrl}/${slug}/images`);

const formData = new FormData();
images.forEach((image) => {
formData.append("images", image);
});

const response = await sendRequest<Object>(url, {
method: HttpMethod.PUT,
body: formData,
authRequired: true,
});
return response;
};

export const getEventImages = async (slug: string): Promise<string[]> => {
const url = new URL(`${baseUrl}/${slug}/images`);

const response = await sendRequest<string[]>(url, { method: HttpMethod.GET });
return response;
};

export const getEventParticipants = async (slug: string): Promise<any> => {
const url = new URL(`${baseUrl}/${slug}/participants`);

const response = await sendRequest<any>(url, { method: HttpMethod.GET });
return response;
};

export const getEventComments = async (slug: string): Promise<any> => {
const url = new URL(`${baseUrl}/${slug}/comments`);

const response = await sendRequest<any>(url, { method: HttpMethod.GET });
return response;
};

export const addEventComment = async (
slug: string,
comment: any,
): Promise<any> => {
const url = new URL(`${baseUrl}/${slug}/comments`);

const response = await sendRequest<any>(url, {
method: HttpMethod.POST,
body: comment,
authRequired: true,
});
return response;
};

export const getEventsParameters = async (): Promise<ParameterDto[]> => {
const url = new URL(`${baseUrl}/parameters`);

const response = await sendRequest<ParameterDto[]>(url, {
method: HttpMethod.GET,
authRequired: true,
});
return response;
};

export const getEventsTags = async (): Promise<TagDto[]> => {
const url = new URL(`${baseUrl}/tags`);

const response = await sendRequest<TagDto[]>(url, {
method: HttpMethod.GET,
authRequired: true,
});
return response;
};

export const deleteEventsTag = async (tagId: number): Promise<Object> => {
const url = new URL(`${baseUrl}/tags/${tagId}`);

const response = await sendRequest<Object>(url, {
method: HttpMethod.DELETE,
authRequired: true,
});
return response;
};
Loading

0 comments on commit a1b88eb

Please sign in to comment.