Skip to content

871-refactor: Refactor youtube api #872

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

Merged
merged 6 commits into from
May 21, 2025
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
7 changes: 5 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
API_BASE_URL=https://cdn.rs.school
YOUTUBE_API_KEY=YOUTUBE_API_KEY
NEXT_PUBLIC_API_BASE_URL=https://cdn.rs.school
LOG_QUERY=true

# YouTube
NEXT_PUBLIC_YOUTUBE_API_BASE_URL=https://www.googleapis.com/youtube/v3
NEXT_PUBLIC_YOUTUBE_API_KEY=YOUTUBE_API_KEY

# Contentful
CONTENTFUL_SPACE_ID=CONTENTFUL_SPACE_ID
CONTENTFUL_MANAGEMENT_TOKEN=CONTENTFUL_MANAGEMENT_TOKEN
5 changes: 3 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ on:

env:
NODE_VERSION: 20.x
API_BASE_URL: ${{ secrets.API_BASE_URL }}
YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY_DEVELOPMENT }}
NEXT_PUBLIC_API_BASE_URL: ${{ secrets.API_BASE_URL }}
NEXT_PUBLIC_YOUTUBE_API_BASE_URL: ${{ secrets.YOUTUBE_API_BASE_URL }}
NEXT_PUBLIC_YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY_DEVELOPMENT }}

jobs:
ci:
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/preview-create.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ env:
AWS_ACCESS_KEY_ID: ${{ secrets.DEPLOY_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DEPLOY_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'eu-central-1'
API_BASE_URL: ${{ secrets.API_BASE_URL }}
YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY_DEVELOPMENT }}
NEXT_PUBLIC_API_BASE_URL: ${{ secrets.API_BASE_URL }}
NEXT_PUBLIC_YOUTUBE_API_BASE_URL: ${{ secrets.YOUTUBE_API_BASE_URL }}
NEXT_PUBLIC_YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY_DEVELOPMENT }}

jobs:
build-rs-school:
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ env:
AWS_ACCESS_KEY_ID: ${{ secrets.DEPLOY_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.DEPLOY_AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'eu-central-1'
API_BASE_URL: ${{ secrets.API_BASE_URL }}
YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY_PRODUCTION }}
NEXT_PUBLIC_API_BASE_URL: ${{ secrets.API_BASE_URL }}
NEXT_PUBLIC_YOUTUBE_API_BASE_URL: ${{ secrets.YOUTUBE_API_BASE_URL }}
NEXT_PUBLIC_YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY_PRODUCTION }}

jobs:
rs-school:
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/visual-testing-on-comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ on:

env:
NODE_VERSION: 20.x
API_BASE_URL: ${{ secrets.API_BASE_URL }}
YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY_DEVELOPMENT }}
NEXT_PUBLIC_API_BASE_URL: ${{ secrets.API_BASE_URL }}
NEXT_PUBLIC_YOUTUBE_API_BASE_URL: ${{ secrets.YOUTUBE_API_BASE_URL }}
NEXT_PUBLIC_YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY_DEVELOPMENT }}

jobs:
run-visial-testing:
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/visual-testing-on-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ on:

env:
NODE_VERSION: 20.x
API_BASE_URL: ${{ secrets.API_BASE_URL }}
YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY_DEVELOPMENT }}
NEXT_PUBLIC_API_BASE_URL: ${{ secrets.API_BASE_URL }}
NEXT_PUBLIC_YOUTUBE_API_BASE_URL: ${{ secrets.YOUTUBE_API_BASE_URL }}
NEXT_PUBLIC_YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY_DEVELOPMENT }}

jobs:
visial-testing:
Expand Down
6 changes: 4 additions & 2 deletions env.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
declare namespace NodeJS {
interface ProcessEnv {
YOUTUBE_API_KEY: string;
API_BASE_URL: string;
API_URL: string;
NEXT_PUBLIC_API_BASE_URL: string;
NEXT_PUBLIC_YOUTUBE_API_BASE_URL: string;
NEXT_PUBLIC_YOUTUBE_API_KEY: string;
LOG_QUERY: string;
}
}
25 changes: 24 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
"remark-rehype": "^11.1.2",
"remark-remove-comments": "^1.1.1",
"remark-toc": "^9.0.0",
"swiper": "^11.2.6"
"swiper": "^11.2.6",
"swr": "^2.3.3"
},
"devDependencies": {
"@eslint/js": "^9.25.1",
Expand Down
13 changes: 11 additions & 2 deletions src/core/api/app-api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CourseApi } from '@/entities/course/api/course-api';
import { MentorApi } from '@/entities/mentor/api/mentor-api';
import { TrainerApi } from '@/entities/trainer/api/trainer-api';
import { ApiBaseClass } from '@/shared/api/api-base-class';
import { ApiServices } from '@/shared/types';
Expand All @@ -8,11 +9,19 @@ export class Api {

public readonly trainer: TrainerApi;
public readonly course: CourseApi;
public readonly mentor: MentorApi;

constructor(private readonly baseURI: string) {
this.services = { rest: new ApiBaseClass(this.baseURI) };
constructor(
private readonly baseURI: string,
private readonly youtubeBaseURI: string,
) {
this.services = {
rest: new ApiBaseClass(this.baseURI),
youtube: new ApiBaseClass(this.youtubeBaseURI),
};

this.trainer = new TrainerApi(this.services);
this.course = new CourseApi(this.services);
this.mentor = new MentorApi(this.services);
}
}
19 changes: 19 additions & 0 deletions src/entities/mentor/api/mentor-api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { MENTORSHIP_YOUTUBE_PLAYLIST_ID } from '../constants';
import { MentorPlaylistResponse } from '../types';
import { YOUTUBE_API_MAX_RESULTS_PER_PAGE } from '@/shared/constants';
import { ApiServices } from '@/shared/types/';

export class MentorApi {
constructor(private readonly services: ApiServices) {}

public queryMentorPlaylist() {
return this.services.youtube.get<MentorPlaylistResponse>(`/playlistItems`, {
params: {
part: ['snippet', 'status'],
maxResults: YOUTUBE_API_MAX_RESULTS_PER_PAGE,
key: process.env.NEXT_PUBLIC_YOUTUBE_API_KEY,
playlistId: MENTORSHIP_YOUTUBE_PLAYLIST_ID,
},
});
}
}
1 change: 1 addition & 0 deletions src/entities/mentor/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const MENTORSHIP_YOUTUBE_PLAYLIST_ID = 'PLzLiprpVuH8f7Jg8pgZUCeTN-Q6uVZNhg';
9 changes: 9 additions & 0 deletions src/entities/mentor/helpers/transform-mentor-videos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { Video } from '@/shared/types';

export function transformMentorVideos(videos: GoogleApiYouTubePlaylistItemResource[]): Video[] {
return videos.map((item) => ({
id: item.snippet.resourceId.videoId,
title: item.snippet.title,
thumbnail: item.snippet.thumbnails.medium.url,
}));
}
1 change: 1 addition & 0 deletions src/entities/mentor/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export type { MentorFeedback } from './types';
export { MentorFeedbackCard } from './ui/mentor-feedback-card/mentor-feedback-card';
export { mentorStore } from './model/store';
20 changes: 20 additions & 0 deletions src/entities/mentor/model/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { transformMentorVideos } from '@/entities/mentor/helpers/transform-mentor-videos';
import { api } from '@/shared/api/api';
import { filterYoutubePrivateVideos } from '@/shared/helpers/filter-youtube-private-videos';

export class MentorStore {
public loadYoutubePlaylist = async () => {
const res = await api.mentor.queryMentorPlaylist();

if (res.isSuccess) {
const publicVideos = filterYoutubePrivateVideos(res.result);
const videoItems = transformMentorVideos(publicVideos);

return videoItems;
}

throw new Error('Error while loading mentor playlist.');
};
}

export const mentorStore = new MentorStore();
4 changes: 4 additions & 0 deletions src/entities/mentor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@ export type MentorFeedback = {
review: string;
photo: StaticImageData;
};

export type MentorPlaylistResponse = {
items: GoogleApiYouTubePlaylistItemResource[];
};
2 changes: 1 addition & 1 deletion src/shared/api/api-base-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,6 @@ export class ApiBaseClass {
const urlWithQueryString = `${url}?${queryString}`;
const baseUrl = !isValidUrl(url) ? this.baseUrl : '';

return new URL(urlWithQueryString, baseUrl).toString();
return `${baseUrl}${urlWithQueryString}`;
}
}
5 changes: 4 additions & 1 deletion src/shared/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { Api } from '@/core/api/app-api';

export const api = new Api(process.env.API_BASE_URL);
export const api = new Api(
process.env.NEXT_PUBLIC_API_BASE_URL,
process.env.NEXT_PUBLIC_YOUTUBE_API_BASE_URL,
);
3 changes: 3 additions & 0 deletions src/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const TO_BE_DETERMINED = 'TBD';
export const REGISTRATION_WILL_OPEN_SOON = 'Registration will open soon!';
export const REGISTRATION_WILL_OPEN_SOON_RU = 'РСгистрация откроСтся скоро!';
export const UNKNOWN_API_ERROR = 'Unknown error, API request failed.';
export const YOUTUBE_API_MAX_RESULTS_PER_PAGE = 50;

/**
* https://www.contentful.com/developers/docs/references/content-preview-api/#/reference/links
Expand Down Expand Up @@ -106,3 +107,5 @@ export const ROUTES = {
DOCS_RU: 'docs/ru',
NOT_FOUND: '*',
} as const;

export const SWR_CACHE_KEY = { MENTORS_PLAYLIST: 'MENTORS_PLAYLIST' };
6 changes: 6 additions & 0 deletions src/shared/helpers/filter-youtube-private-videos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { MentorPlaylistResponse } from '@/entities/mentor/types';
import { videoPrivacyStatus } from '@/shared/ui/video-playlist-with-player/constants';

export function filterYoutubePrivateVideos(videos: MentorPlaylistResponse) {
return videos.items.filter((item) => item.status.privacyStatus === videoPrivacyStatus.public);
}
8 changes: 4 additions & 4 deletions src/shared/helpers/log-request.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import util from 'node:util';
import { inspect } from 'util';

import { stringifyJSONSafe } from '@/shared/helpers/stringify-json-safe';
import { HttpMethod } from '@/shared/types';
Expand Down Expand Up @@ -26,7 +26,7 @@ export function logRequest({
body,
error,
}: LogRequestParams) {
if (process.env.LOG_QUERY === 'false') {
if (process.env.NODE_ENV === 'production' || process.env.LOG_QUERY === 'false') {
return;
}

Expand Down Expand Up @@ -57,9 +57,9 @@ export function logRequest({
}

console.log(
util.inspect(logObject, {
inspect(logObject, {
depth: null,
colors: process.env.NODE_ENV !== 'production',
colors: true,
}),
);
}
1 change: 1 addition & 0 deletions src/shared/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export type Video = {

export type ApiServices = {
rest: ApiBaseClass;
youtube: ApiBaseClass;
};

export type HttpMethod = (typeof HTTP_METHOD)[keyof typeof HTTP_METHOD];
Expand Down
Loading
Loading