From 2ad5ce7d80bb448edffd63f748971413eb1e3f28 Mon Sep 17 00:00:00 2001 From: Samuel Lim Date: Fri, 27 Sep 2024 20:10:37 +0800 Subject: [PATCH 1/2] Add minimial gateway --- compose.dev.yml | 4 ++++ compose.yml | 19 +++++++++++++++---- nginx/default.conf | 22 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) create mode 100644 nginx/default.conf diff --git a/compose.dev.yml b/compose.dev.yml index c450c933a4..ca04cedcb2 100644 --- a/compose.dev.yml +++ b/compose.dev.yml @@ -6,6 +6,8 @@ services: question: command: npm run dev + ports: + - 8081:8081 volumes: - /app/node_modules - ./services/question:/app @@ -16,6 +18,8 @@ services: user: command: npm run dev + ports: + - 8082:8082 volumes: - /app/node_modules - ./services/user:/app \ No newline at end of file diff --git a/compose.yml b/compose.yml index 96db768d65..47421ce4f3 100644 --- a/compose.yml +++ b/compose.yml @@ -9,20 +9,29 @@ services: - 4200:4200 restart: always + gateway: + container_name: gateway + image: nginx:1.27 + ports: + - 8080:8080 + volumes: + - ./nginx/default.conf:/etc/nginx/conf.d/default.conf + networks: + - gateway-network + question: container_name: question image: question build: context: services/question dockerfile: Dockerfile - ports: - - 8081:8081 environment: DB_CLOUD_URI: ${QUESTION_DB_CLOUD_URI} DB_LOCAL_URI: ${QUESTION_DB_LOCAL_URI} DB_USERNAME: ${QUESTION_DB_USERNAME} DB_PASSWORD: ${QUESTION_DB_PASSWORD} networks: + - gateway-network - question-db-network restart: always @@ -46,18 +55,20 @@ services: build: context: services/user dockerfile: Dockerfile - ports: - - 8082:8082 environment: USER_SERVICE_CLOUD_URI: ${USER_SERVICE_CLOUD_URI} USER_SERVICE_LOCAL_URI: ${USER_SERVICE_LOCAL_URI} ENV: ${ENV} JWT_SECRET: ${JWT_SECRET} + networks: + - gateway-network restart: always volumes: question-db: networks: + gateway-network: + driver: bridge question-db-network: driver: bridge \ No newline at end of file diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100644 index 0000000000..89f3fc5525 --- /dev/null +++ b/nginx/default.conf @@ -0,0 +1,22 @@ +upstream question-api { + server question:8081; +} + +upstream user-api { + server user:8082; +} + +server { + listen 8080; + server_name localhost; + + location /api/question/ { + proxy_pass http://question-api/; + proxy_set_header Host $host; + } + + location /api/user/ { + proxy_pass http://user-api/; + proxy_set_header Host $host; + } +} From 278a4f85827f24a91790d0ece635a79feb30510a Mon Sep 17 00:00:00 2001 From: Samuel Lim Date: Tue, 1 Oct 2024 21:14:32 +0800 Subject: [PATCH 2/2] Configure frontend to use api gateway --- frontend/src/_services/api.service.ts | 21 ++++++++++++++++ frontend/src/_services/question.service.ts | 28 ++++++++++++---------- frontend/src/app/api.config.ts | 2 +- 3 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 frontend/src/_services/api.service.ts diff --git a/frontend/src/_services/api.service.ts b/frontend/src/_services/api.service.ts new file mode 100644 index 0000000000..a0a2319fe7 --- /dev/null +++ b/frontend/src/_services/api.service.ts @@ -0,0 +1,21 @@ +import { API_CONFIG } from '../app/api.config'; + +/** + * Abstract class that serves as a base for API services. + */ +export abstract class ApiService { + /** + * The path for the specific resource, e.g. 'user', 'question', etc. + * This property must be implemented by subclasses to specify the + * endpoint path for the API resource they represent. + */ + protected abstract apiPath: string; + + /** + * Returns the full URL for the API endpoint based on + * the specified apiPath. + */ + get apiUrl(): string { + return API_CONFIG.baseUrl + this.apiPath; + } +} diff --git a/frontend/src/_services/question.service.ts b/frontend/src/_services/question.service.ts index c289220a9d..533a88eee4 100644 --- a/frontend/src/_services/question.service.ts +++ b/frontend/src/_services/question.service.ts @@ -1,6 +1,5 @@ import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import { API_CONFIG } from '../app/api.config'; import { catchError, Observable, throwError } from 'rxjs'; import { SingleQuestionResponse, @@ -9,12 +8,13 @@ import { MessageOnlyResponse, } from '../app/questions/question.model'; import { TopicResponse } from '../app/questions/topic.model'; +import { ApiService } from './api.service'; @Injectable({ providedIn: 'root', }) -export class QuestionService { - private baseUrl = API_CONFIG.baseUrl + '/questions'; +export class QuestionService extends ApiService { + protected apiPath = 'question/questions'; private httpOptions = { headers: new HttpHeaders({ @@ -22,7 +22,9 @@ export class QuestionService { }), }; - constructor(private http: HttpClient) {} + constructor(private http: HttpClient) { + super(); + } getQuestions( title?: string, @@ -46,11 +48,11 @@ export class QuestionService { } // send request - return this.http.get(this.baseUrl, { params }); + return this.http.get(this.apiUrl, { params }); } - getQuestionByID(id: number): Observable { - return this.http.get(this.baseUrl + '/' + id); + getQuestionByID(id: number): Observable { + return this.http.get(this.apiUrl + '/' + id); } getQuestionByParam(topics: string[], difficulty: string, limit?: number): Observable { @@ -61,32 +63,32 @@ export class QuestionService { } params = params.append('topics', topics.join(',')).append('difficulty', difficulty); - return this.http.get(this.baseUrl + '/search', { params }); + return this.http.get(this.apiUrl + '/search', { params }); } getTopics(): Observable { - return this.http.get(this.baseUrl + '/topics'); + return this.http.get(this.apiUrl + '/topics'); } addQuestion(question: QuestionBody): Observable { return this.http - .post(this.baseUrl, question, this.httpOptions) + .post(this.apiUrl, question, this.httpOptions) .pipe(catchError(this.handleError)); } updateQuestion(id: number, question: QuestionBody): Observable { return this.http - .put(this.baseUrl + '/' + id, question, this.httpOptions) + .put(this.apiUrl + '/' + id, question, this.httpOptions) .pipe(catchError(this.handleError)); } deleteQuestion(id: number): Observable { - return this.http.delete(this.baseUrl + '/' + id).pipe(catchError(this.handleError)); + return this.http.delete(this.apiUrl + '/' + id).pipe(catchError(this.handleError)); } deleteQuestions(ids: number[]): Observable { return this.http - .post(this.baseUrl + '/delete', { ids }, this.httpOptions) + .post(this.apiUrl + '/delete', { ids }, this.httpOptions) .pipe(catchError(this.handleError)); } diff --git a/frontend/src/app/api.config.ts b/frontend/src/app/api.config.ts index 67e2d1a966..b390e8be93 100644 --- a/frontend/src/app/api.config.ts +++ b/frontend/src/app/api.config.ts @@ -1,3 +1,3 @@ export const API_CONFIG = { - baseUrl: 'http://localhost:8081', + baseUrl: 'http://localhost:8080/api/', };