Skip to content
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

sync/update-develop-into-main #67

Merged
merged 12 commits into from
Nov 15, 2024
97 changes: 66 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,83 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
<h2 align="center">QUE FILME</h2>

## Getting Started
<div align="center">
<img alt="GitHub top language" src="https://img.shields.io/github/languages/top/werlleyg/quefilme?color=9747FF">

First, run the development server:
<a href="https://www.linkedin.com/in/werlleyg/" target="_blank" rel="noopener noreferrer">
<img alt="Made by" src="https://img.shields.io/badge/developed%20by-Werlley Ponte-9747FF">
</a>

<a href="https://github.com/werlleyg/quefilme/commits/main">
<img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/werlleyg/quefilme?color=9747FF">
</a>

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
<a href="https://github.com/werlleyg/quefilme/issues">
<img alt="Repository issues" src="https://img.shields.io/github/issues/werlleyg/quefilme?color=9747FF">
</a>

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
<a href="https://quefilme.vercel.app/" target="_blank">
<img alt="GitHub website up/down" src="https://img.shields.io/website-up-down-green-red/https/quefilme.vercel.app/.svg">
</a>
</div>
<br/>
<p align="center">
<b>Que Filme</b> aims to find the best movie and series suggestions for the user based on their previous choice of favorite titles. 🎥 🍿 ✨
</p>
<br/>

You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
## 🛠 Technologies

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
The tools below were used in the project's development:

The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
- [Next.js 14](https://nextjs.org/)
- [TypeScript](https://www.typescriptlang.org/)
- [Toastify](https://fkhadra.github.io/react-toastify/introduction)
- [@emotion](https://emotion.sh/docs/introduction)
- [React Icons](https://react-icons.github.io/react-icons)
- [@svgr](https://react-svgr.com/docs/webpack/)
- [Axios](https://axios-http.com/docs/intro)
- [G4F](https://www.npmjs.com/package/g4f)
- [Testing Library](https://testing-library.com/docs/react-testing-library/example-intro/)
- [Jest](https://jestjs.io/docs/next/testing-frameworks)

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## ⚙ Installation

## Learn More
1. Clone and set up this repository [Que Filme](https://github.com/werlleyg/quefilme)
2. Run `npm i` or `yarn` in the project folder on your computer
3. Run `npm run dev` or `yarn dev`
4. Your project is already running 🔭

To learn more about Next.js, take a look at the following resources:
<br/>

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
## 🏗️ Infrastructure Diagram

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
The project is hosted at Vercel and is accessible by the user from any device with internet browsing. The project also uses integration with two service providers, one for movies and the other for AI.

## Deploy on Vercel
<img src="docs/images/infrastructure-diagram.png" alt="Infrastructure Diagram" style="width: 100%; max-width: 650px">

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
## 🔗 Architecture Diagram

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
The project was built adapting clean arch concepts to the context of Next.js. It has its division into layers, which are below:

### to send push
- **Domain:** Concentrates important information for business rules, such as entities, enums, errors, service, providers, protocols, and usecases contracts;

```
rm -rf yarn.lock node_modules
rm -rf node_modules
yarn --frozen-lockfile
```
- **Infrastructure:** The implementation of service contracts, proviers, protocols and usecases is carried out;

- **Main:** In this layer, the factory pattern is carried out to instantiate the elements created in the infrastructure, in addition to aggregating project configurations such as environment variables and build settings;

- **Presentation + Pages:** Outermost layer of design, talking directly to the framework used, responsible for creating the components, typography and global stylizations. The Pages directory, responsible for concentrating the pages of the application, is separated by limitations of the framework itself in dealing with this resource. Consider Pages as part of Presentation.

<br/>

<img src="docs/images/architecture-diagram.png" alt="Architecture Diagram" style="width: 100%; max-width: 650px">

## 🚀 CI/CD Flow

<img src="docs/images/cicd-flow.png" alt="CICD Flow" style="width: 100%; max-width: 650px">

<br/>
<br/>

Developed by <a href="https://www.linkedin.com/in/werlleyg" target="_blank">Werlley Ponte</a>

---
Binary file added docs/images/architecture-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/cicd-flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/infrastructure-diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "quefilme",
"version": "0.0.5",
"version": "0.0.6",
"private": true,
"scripts": {
"dev": "next dev",
Expand All @@ -23,7 +23,8 @@
"jest-mock-extended": "^3.0.7",
"next": "14.2.4",
"react": "^18",
"react-dom": "^18"
"react-dom": "^18",
"react-toastify": "^10.0.6"
},
"devDependencies": {
"@faker-js/faker": "^8.4.1",
Expand Down
3 changes: 3 additions & 0 deletions src/domain/enums/snackbar.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum snackbarMessage {
SUGGEST_MOVIE_ERROR = "Ocorreu um erro na consulta. Tente novamente.",
}
Empty file removed src/domain/index.ts
Empty file.
File renamed without changes.
File renamed without changes.
2 changes: 0 additions & 2 deletions src/domain/services/ai.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { ListMoviesEntity, MovieEntity } from "../entities";

export interface AiService {
generateResponse: (prompt: AiService.Params) => Promise<AiService.Model>;
}
Expand Down
2 changes: 1 addition & 1 deletion src/infrastructure/ai/chatGPTAiClient.infra.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AiClient, AiPrompt, AiResponse } from "@/data/protocols/ai";
import { AiClient, AiPrompt, AiResponse } from "@/domain/protocols/ai";
import { chunkProcessor, G4F } from "g4f";

export class ChatGPTAiClient implements AiClient {
Expand Down
File renamed without changes.
2 changes: 1 addition & 1 deletion src/infrastructure/http/axiosHttpClient.infra.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { HttpRequest, HttpResponse, HttpClient } from "@/data/protocols/http";
import { HttpRequest, HttpResponse, HttpClient } from "@/domain/protocols/http";

import axios, { AxiosResponse } from "axios";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ListMoviesEntity, MovieEntity } from "@/domain/entities";
import { MoviesRepository } from "@/domain/repositories";
import { HttpClient, HttpStatusCode } from "../protocols/http";
import { HttpClient, HttpStatusCode } from "../../domain/protocols/http";
import { Environment } from "@/main/config";
import {
AccessDeniedError,
NotFoundError,
UnexpectedError,
} from "@/domain/errors";
import { movieFromJson, moviesFromJsonList } from "@/helpers";
import { movieFromJson, moviesFromJsonList } from "@/infrastructure/helpers";

export class MoviesRepositoryImpl implements MoviesRepository {
constructor(private readonly httpClient: HttpClient) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
NotFoundError,
UnexpectedError,
} from "@/domain/errors";
import { HttpClient, HttpStatusCode } from "../protocols/http";
import { HttpClient, HttpStatusCode } from "../../domain/protocols/http";
import { AiService } from "./../../domain/services/ai.service";

export class AiServiceImpl implements AiService {
Expand Down
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion src/main/factories/repositories/movies.factory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MoviesRepositoryImpl } from "@/data/repositories";
import { MoviesRepositoryImpl } from "@/infrastructure/repositories";
import { MoviesRepository } from "@/domain/repositories";
import { makeAxiosHttpClient } from "../http/axiosHttpClient.factory";

Expand Down
2 changes: 1 addition & 1 deletion src/main/factories/services/ai.factory.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AiServiceImpl } from "@/data/services";
import { AiService } from "@/domain/services";
import { makeAxiosHttpClient } from "../http/axiosHttpClient.factory";
import { Environment } from "@/main/config";
import { AiServiceImpl } from "@/infrastructure/services";

export const makeAiService = (): AiService =>
new AiServiceImpl(makeAxiosHttpClient(), Environment.baseUrlAi);
2 changes: 1 addition & 1 deletion src/main/factories/usecases/getMovie.factory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GetMovieUsecase } from "@/domain/usecases";
import { GetMovieUsecaseImpl } from "@/usecases";
import { makeMoviesRepository } from "../repositories/movies.factory";
import { GetMovieUsecaseImpl } from "@/infrastructure/usecases";

export const makeGetMovieUsecase = (): GetMovieUsecase =>
new GetMovieUsecaseImpl(makeMoviesRepository());
2 changes: 1 addition & 1 deletion src/main/factories/usecases/getMovieSuggestion.factory.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { GetMovieSuggestionUsecase } from "@/domain/usecases";

import { makeMoviesRepository } from "../repositories/movies.factory";
import { GetMovieSuggestionUsecaseImpl } from "@/usecases/getMovieSuggestion.usecase";
import { makeAiService } from "../services/ai.factory";
import { GetMovieSuggestionUsecaseImpl } from "@/infrastructure/usecases/getMovieSuggestion.usecase";

export const makeGetMovieSuggestionUsecase = (): GetMovieSuggestionUsecase =>
new GetMovieSuggestionUsecaseImpl(makeMoviesRepository(), makeAiService());
2 changes: 1 addition & 1 deletion src/main/factories/usecases/getMovies.factory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { GetMoviesUsecase } from "@/domain/usecases";
import { GetMoviesUsecaseImpl } from "@/usecases";
import { makeMoviesRepository } from "../repositories/movies.factory";
import { GetMoviesUsecaseImpl } from "@/infrastructure/usecases";

export const makeGetMoviesUsecase = (): GetMoviesUsecase =>
new GetMoviesUsecaseImpl(makeMoviesRepository());
9 changes: 8 additions & 1 deletion src/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import "@/presentation/styles/globals.css";
import "react-toastify/dist/ReactToastify.css";
import type { AppProps } from "next/app";
import { ToastContainer } from "react-toastify";

export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
return (
<>
<Component {...pageProps} />
<ToastContainer />
</>
);
}
1 change: 0 additions & 1 deletion src/pages/api/chat.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { ChatGPTAiClient } from "@/infrastructure/ai/chatGPTAiClient.infra";
import { makeChatGptAiClient } from "@/main/factories/ai/chatGPTAiClient.factory";
import { NextApiRequest, NextApiResponse } from "next";

Expand Down
25 changes: 20 additions & 5 deletions src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import {
import { makeGetMoviesUsecase } from "@/main/factories/usecases/getMovies.factory";
import { Environment } from "@/main/config";
import { makeGetMovieSuggestionUsecase } from "@/main/factories/usecases/getMovieSuggestion.factory";
import { toast } from "react-toastify";
import { snackbarMessage } from "@/domain/enums/snackbar.enum";
import Link from "next/link";

export function Home() {
const [value, setValue] = useState<string>();
Expand Down Expand Up @@ -67,10 +70,15 @@ export function Home() {
const handleGetSuggestMovie = useCallback(async () => {
setLoading(true);
const listMovies = new ListMoviesEntity({ movies: selectedMovies });
const result = await makeGetMovieSuggestionUsecase()
.exec(listMovies)
.finally(() => setLoading(false));
setSuggestedMovie(new MovieEntity(result));

try {
const result = await makeGetMovieSuggestionUsecase().exec(listMovies);
setSuggestedMovie(new MovieEntity(result));
} catch (e) {
toast.error(snackbarMessage.SUGGEST_MOVIE_ERROR);
} finally {
setLoading(false);
}
}, [selectedMovies]);

const handleWithGoBack = useCallback(() => {
Expand Down Expand Up @@ -152,7 +160,14 @@ export function Home() {
</DivTopContent>
<DivBottomContent>
<P style={{ fontWeight: 500, textAlign: "center" }}>
Desenvolvido por <u>Werlley Ponte</u>
Desenvolvido por{" "}
<Link
href="https://www.linkedin.com/in/werlleyg"
target="_blank"
style={{ fontWeight: 700 }}
>
Werlley Ponte
</Link>
</P>
</DivBottomContent>
<ShowMovie
Expand Down
3 changes: 0 additions & 3 deletions tests/data/mocks/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion tests/domain/mocks/index.ts

This file was deleted.

3 changes: 1 addition & 2 deletions tests/infrastructure/http/axiosHttpClient.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { AxiosHttpClient } from "@/infrastructure/http";
import { mockAxios, mockHttpResponse } from "../mocks";
import { mockHttpRequest } from "../../data/mocks";
import { mockAxios, mockHttpRequest, mockHttpResponse } from "../mocks";

import axios from "axios";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
HttpResponse,
HttpStatusCode,
HttpClient,
} from "@/data/protocols/http";
} from "@/domain/protocols/http";

import { faker } from "@faker-js/faker";

Expand Down
4 changes: 4 additions & 0 deletions tests/infrastructure/mocks/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export * from "./axios.mock";
export * from "./http.mock";
export * from "./moviesRepositoryImpl.mock";
export * from "./aiServiceImpl.mock";
export * from "./movie.mock";
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { MoviesRepositoryImpl } from "@/data/repositories/moviesImpl.repository";
import { MoviesRepositoryImpl } from "@/infrastructure/repositories/moviesImpl.repository";
import {
HttpClientSpy,
mockGetMovieRepositoryImpl,
mockGetMoviesRepositoryImpl,
} from "../mocks";
import { HttpStatusCode } from "@/data/protocols/http";
import { HttpStatusCode } from "@/domain/protocols/http";
import { Environment } from "@/main/config";
import {
AccessDeniedError,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { AiServiceImpl } from "@/data/services";
import { HttpClientSpy, mockGenerateResponse } from "../mocks";
import { Environment } from "@/main/config";
import { HttpStatusCode } from "@/data/protocols/http";
import { HttpStatusCode } from "@/domain/protocols/http";
import {
BadRequestError,
NotFoundError,
UnexpectedError,
} from "@/domain/errors";
import { AiServiceImpl } from "@/infrastructure/services";

type SutTypes = {
sut: AiServiceImpl;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { MoviesRepositoryImpl } from "@/data/repositories";
import { AiServiceImpl } from "@/data/services";
import { GetMovieSuggestionUsecaseImpl } from "@/usecases/getMovieSuggestion.usecase";
import { MoviesRepositoryImpl } from "@/infrastructure/repositories";

import { mock } from "jest-mock-extended";
import {
listMoviesMock,
mockGenerateResponse,
movieMock,
} from "./mocks/getMovieSuggestionUsecaseImpl.mock";
} from "../mocks/getMovieSuggestionUsecaseImpl.mock";
import { ListMoviesEntity } from "@/domain/entities";
import { UnexpectedError } from "@/domain/errors";
import { AiServiceImpl } from "@/infrastructure/services";
import { GetMovieSuggestionUsecaseImpl } from "@/infrastructure/usecases/getMovieSuggestion.usecase";

type SutTypes = {
repository: ReturnType<typeof mock<MoviesRepositoryImpl>>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { MoviesRepositoryImpl } from "@/data/repositories";
import { GetMovieUsecaseImpl } from "@/usecases";
import { MoviesRepositoryImpl } from "@/infrastructure/repositories";

import { mock } from "jest-mock-extended";
import { movieMock } from "./mocks/getMovieUsecaseImpl.mock";
import { movieMock } from "../mocks/getMovieUsecaseImpl.mock";
import { GetMovieUsecaseImpl } from "@/infrastructure/usecases";

type SutTypes = {
movieRepository: ReturnType<typeof mock<MoviesRepositoryImpl>>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { MoviesRepositoryImpl } from "@/data/repositories";
import { GetMoviesUsecaseImpl } from "@/usecases";
import { MoviesRepositoryImpl } from "@/infrastructure/repositories";

import { mock } from "jest-mock-extended";
import { listMoviesMock, movieMock } from "./mocks/getMovieUsecaseImpl.mock";
import { listMoviesMock, movieMock } from "../mocks/getMovieUsecaseImpl.mock";
import { GetMoviesUsecaseImpl } from "@/infrastructure/usecases";

type SutTypes = {
movieRepository: ReturnType<typeof mock<MoviesRepositoryImpl>>;
Expand Down
2 changes: 1 addition & 1 deletion tests/main/factories/repositories/movies.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MoviesRepositoryImpl } from "@/data/repositories";
import { MoviesRepositoryImpl } from "@/infrastructure/repositories";
import { MoviesRepository } from "@/domain/repositories";
import { AxiosHttpClient } from "@/infrastructure/http";

Expand Down
Loading
Loading