diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..42fcd96 --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# ID for identifying containers created by docker-compose +# This variable is valid for all projects (debug, hub, nginx, and "production") +ID_PROJECT=mean + +# For angular, the base href is the path where the app is hosted, use / if the app is hosted in the root. +# This is used to generate the index.html file with the correct base href and is only valid for production mode. +BASE_HREF=/contacts/ \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index dfe0770..ade39d2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ # Auto detect text files and perform LF normalization * text=auto +# For windows users +mongo/init-db.d/init-mongo.sh text eol=lf \ No newline at end of file diff --git a/README.md b/README.md index b83f0f7..654f83f 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ Install latest [Docker Desktop](https://www.docker.com/products/docker-desktop) docker-compose -f 'docker-compose.debug.yml' up ``` - It will run fronend `http://localhost:4200` and api on `http://localhost:3000`. you can also access mongodb on port 27017. + It will run frontend `http://localhost:4200` and api on `http://localhost:3000`. you can also access mongodb on port 27017. Also, it will automatically refresh (hot reload) your UI for code changes. That is also true for expressjs file changes. @@ -125,7 +125,7 @@ Install latest [Docker Desktop](https://www.docker.com/products/docker-desktop) ``` docker-compose up ``` - It will run fronend and api on `http://localhost:3000`. you can also access mongodb on port 27017 + It will run frontend and api on `http://localhost:3000`. you can also access mongodb on port 27017 ##### Using 4 containers (Mongo,api, angular and nginx) ``` git clone https://github.com/nitin27may/mean-docker.git @@ -133,7 +133,7 @@ Install latest [Docker Desktop](https://www.docker.com/products/docker-desktop) docker-compose -f 'docker-compose.nginx.yml' up ``` - It will run fronend and api on `http://localhost`. you can aslo access by it's invidual ports. For Frontend `http://localhost:4000` and for api `http://localhost:3000` .you can also access mongodb on port 27017 + It will run frontend and api on `http://localhost`. you can aslo access by it's invidual ports. For Frontend `http://localhost:4000` and for api `http://localhost:3000` .you can also access mongodb on port 27017 #### About Docker Compose File The main focus of this project to show case the possible way to run a real application (Mean stack) using docker. diff --git a/api/api-routes.js b/api/api-routes.js index 8d71c43..0d824f5 100644 --- a/api/api-routes.js +++ b/api/api-routes.js @@ -1,8 +1,13 @@ // Filename: api-routes.js // Initialize express router let router = require("express").Router(); +var { expressjwt: jwt } = require("express-jwt"); +const environment = require("./config/environment"); + +const jwtAuth = jwt({ secret: environment.secret, algorithms: ["HS256"] }); + // Set default API response -router.get("/", function(req, res) { +router.get("/", function (req, res) { res.json({ status: "API Its Working", message: "Welcome to RESTHub crafted with love!" @@ -11,35 +16,38 @@ router.get("/", function(req, res) { // Import user controller var userController = require("./controllers/users.controller"); + // user routes router .route("/users") - .get(userController.index) + .get(jwtAuth, userController.index) .post(userController.new); router .route("/user/:user_id") - .get(userController.view) - .patch(userController.update) - .put(userController.update) - .delete(userController.delete); -router.route("/user/authenticate").post(userController.authenticate); + .get(jwtAuth, userController.view) + .patch(jwtAuth, userController.update) + .put(jwtAuth, userController.update) + .delete(jwtAuth, userController.delete); router .route("/user/changepassword/:user_id") - .put(userController.changePassword); + .put(jwtAuth, userController.changePassword); +// Public route for user authentication (without jwtAuth) +router.route("/user/authenticate").post(userController.authenticate); // Import Contact controller var contactController = require("./controllers/contact.controller"); + // Contact routes router .route("/contacts") - .get(contactController.index) - .post(contactController.new); + .get(jwtAuth, contactController.index) + .post(jwtAuth, contactController.new); router .route("/contact/:contact_id") - .get(contactController.view) - .patch(contactController.update) - .put(contactController.update) - .delete(contactController.delete); + .get(jwtAuth, contactController.view) + .patch(jwtAuth, contactController.update) + .put(jwtAuth, contactController.update) + .delete(jwtAuth, contactController.delete); // Export API routes module.exports = router; diff --git a/api/server.js b/api/server.js index 0bb52ba..e76ffa6 100644 --- a/api/server.js +++ b/api/server.js @@ -82,19 +82,14 @@ app.use( } }).unless({ path: [ - "/api/user/authenticate", - "/api/users", - "/index.html", - "/*.js", - "/*.css" + { url: "/api/user/authenticate", methods: ["POST"] }, + { url: "/index.html", methods: ["GET"] }, + { url: /\.js$/, methods: ["GET"] }, + { url: /\.css$/, methods: ["GET"] } ] }) ); - - - - const HOST = "0.0.0.0"; const port = Number(process.env.EXPRESS_PORT) || 3000; // start server diff --git a/docker-compose.debug.yml b/docker-compose.debug.yml index 3f69f69..40815b9 100644 --- a/docker-compose.debug.yml +++ b/docker-compose.debug.yml @@ -6,10 +6,10 @@ services: build: # specify the directory of the Dockerfile context: ./frontend dockerfile: debug.dockerfile - command: ["npm", "run", "start"] - container_name: mean_angular + command: sh -c "npm install && ng serve --host 0.0.0.0 --poll=2000 --disable-host-check" + container_name: ${ID_PROJECT:-mean}_angular volumes: - - ./frontend/src:/app/src + - ./frontend:/app ports: - "4200:4200" # specify port forewarding - "49153:49153" @@ -22,8 +22,8 @@ services: build: # specify the directory of the Dockerfile context: ./api dockerfile: debug.dockerfile - container_name: mean_express - command : ["npm", "run", "dev:server"] + container_name: ${ID_PROJECT:-mean}_express + command: sh -c "npm install && npm run dev:server" volumes: - ./api:/api ports: @@ -32,6 +32,7 @@ services: environment: - SECRET=Thisismysecret - NODE_ENV=development + - MONGO_INITDB_DATABASE=admin - MONGO_DB_USERNAME=admin-user - MONGO_DB_PASSWORD=admin-password - MONGO_DB_HOST=database @@ -43,7 +44,7 @@ services: database: # name of the third service image: mongo # specify image to build container from - container_name: mean_mongo + container_name: ${ID_PROJECT:-mean}_mongo environment: - MONGO_INITDB_ROOT_USERNAME=admin-user - MONGO_INITDB_ROOT_PASSWORD=admin-password diff --git a/docker-compose.hub.yml b/docker-compose.hub.yml index 9e6e0d5..1e5b7c9 100644 --- a/docker-compose.hub.yml +++ b/docker-compose.hub.yml @@ -4,7 +4,7 @@ version: "3.8" # specify docker-compose version services: angular: # name of the first service image: nitin27may/mean-angular:latest # specify image to build container from - container_name: mean_angular + container_name: ${ID_PROJECT:-mean}_angular restart: always ports: - "4000:4000" # specify port forewarding @@ -15,7 +15,7 @@ services: express: #name of the second service image: nitin27may/mean-expressjs:latest # specify image to build container from - container_name: mean_express + container_name: ${ID_PROJECT:-mean}_express restart: always ports: - "3000:3000" #specify ports forewarding @@ -33,7 +33,7 @@ services: database: # name of the third service image: mongo:latest # specify image to build container from - container_name: mean_mongo + container_name: ${ID_PROJECT:-mean}_mongo restart: always environment: - MONGO_INITDB_ROOT_USERNAME=admin-user @@ -50,7 +50,7 @@ services: nginx: #name of the fourth service image: nitin27may/mean-nginx # specify image to build container from - container_name: mean_nginx + container_name: ${ID_PROJECT:-mean}_nginx restart: always ports: - "80:80" #specify ports forewarding diff --git a/docker-compose.nginx.yml b/docker-compose.nginx.yml index b3a9fa5..f07ed75 100644 --- a/docker-compose.nginx.yml +++ b/docker-compose.nginx.yml @@ -4,7 +4,7 @@ version: "3.8" # specify docker-compose version services: angular: # name of the first service build: frontend # specify the directory of the Dockerfile - container_name: mean_angular + container_name: ${ID_PROJECT:-mean}_angular restart: always ports: - "4000:4000" # specify port forewarding @@ -13,7 +13,7 @@ services: express: #name of the second service build: api # specify the directory of the Dockerfile - container_name: mean_express + container_name: ${ID_PROJECT:-mean}_express restart: always ports: - "3000:3000" #specify ports forewarding @@ -32,7 +32,7 @@ services: database: # name of the third service image: mongo:latest # specify image to build container from - container_name: mean_mongo + container_name: ${ID_PROJECT:-mean}_mongo restart: always environment: - MONGO_INITDB_ROOT_USERNAME=admin-user @@ -49,7 +49,7 @@ services: nginx: #name of the fourth service build: loadbalancer # specify the directory of the Dockerfile - container_name: mean_nginx + container_name: ${ID_PROJECT:-mean}_nginx restart: always ports: - "80:80" #specify ports forewarding diff --git a/docker-compose.yml b/docker-compose.yml index ef5f8a2..d331143 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,9 @@ services: build: # specify the directory of the Dockerfile context: . dockerfile: dockerfile - container_name: mean_angular_express + args: + BASE_HREF: ${BASE_HREF:-/} + container_name: ${ID_PROJECT:-mean}_angular_express ports: - "3000:3000" #specify ports forewarding # Below database enviornment variable for api is helpful when you have to use database as managed service @@ -23,7 +25,7 @@ services: database: # name of the third service image: mongo:latest # specify image to build container from - container_name: mean_mongo + container_name: ${ID_PROJECT:-mean}_mongo environment: - MONGO_INITDB_ROOT_USERNAME=admin-user - MONGO_INITDB_ROOT_PASSWORD=admin-password diff --git a/dockerfile b/dockerfile index d145b4e..a5ec716 100644 --- a/dockerfile +++ b/dockerfile @@ -11,9 +11,20 @@ WORKDIR /app COPY /frontend . +## Change apiEndpoint in environment.ts +RUN sh -c "sed -i 's|http://localhost:3000/api|/api|' src/environments/environment.ts" + +## Change production to true in environment.ts +RUN sh -c "sed -i 's|production: false|production: true|' src/environments/environment.ts" + +ARG BASE_HREF=/ + ## Build the angular app in production mode and store the artifacts in dist folder RUN npm run build:prod +## Change base href in index.html +RUN sh -c "find . -name \"index*.html\" -exec sed -i 's|||' {} +" + ### STAGE 2: Setup ### FROM node:20-alpine @@ -26,7 +37,8 @@ COPY /api/ /app/ RUN npm ci ## From ‘builder’ copy published angular bundles in app/public -COPY --from=builder /app/dist /app/public +COPY --from=builder /app/dist/contacts/browser /app/public + ## expose port for express EXPOSE 3000 diff --git a/frontend/angular.json b/frontend/angular.json index 45daa07..71e99e6 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -45,8 +45,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "500kB", - "maximumError": "1MB" + "maximumWarning": "4MB", + "maximumError": "5MB" }, { "type": "anyComponentStyle", @@ -106,4 +106,4 @@ "cli": { "analytics": "d893c76f-5030-492f-b845-f7f2d5c1a138" } -} +} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index aeae3fd..3ef8aec 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,6 +6,7 @@ "start": "ng serve", "serve": "ng serve --proxy-config proxy.conf.json", "build": "ng build", + "build:prod": "ng build --configuration production", "watch": "ng build --watch --configuration development", "test": "ng test", "serve:ssr:contacts": "node dist/contacts/server/server.mjs" @@ -51,4 +52,4 @@ "prettier-plugin-tailwindcss": "0.6.1", "typescript": "~5.4.2" } -} +} \ No newline at end of file diff --git a/frontend/src/app/@core/interceptors/jwtToken.Interceptor.ts b/frontend/src/app/@core/interceptors/jwtToken.Interceptor.ts new file mode 100644 index 0000000..a385642 --- /dev/null +++ b/frontend/src/app/@core/interceptors/jwtToken.Interceptor.ts @@ -0,0 +1,15 @@ +import { HttpInterceptorFn } from '@angular/common/http'; + +export const jwtInterceptor: HttpInterceptorFn = (req, next) => { + // Obtén el token del localStorage + const currentUser = JSON.parse(localStorage.getItem('currentUser') || '{}'); + const token = currentUser?.token; + + // Clona la solicitud y agrega el encabezado de autorización si existe el token + const authReq = token + ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }) + : req; + + // Pasa la solicitud al siguiente manejador + return next(authReq); +}; diff --git a/frontend/src/app/app.config.ts b/frontend/src/app/app.config.ts index 8ad003c..fa24877 100644 --- a/frontend/src/app/app.config.ts +++ b/frontend/src/app/app.config.ts @@ -2,36 +2,38 @@ import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; import { provideAnimations } from '@angular/platform-browser/animations'; import { provideRouter } from '@angular/router'; -import { provideHttpClient } from '@angular/common/http'; +import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { provideClientHydration } from '@angular/platform-browser'; import { provideToastr } from 'ngx-toastr'; import { routes } from './app.routes'; import { provideErrorTailorConfig } from "./@core/components/validation"; +import { jwtInterceptor } from "./@core/interceptors/jwtToken.Interceptor"; export const appConfig: ApplicationConfig = { - providers: [ - provideHttpClient(), - provideZoneChangeDetection({ eventCoalescing: true }), - provideRouter(routes), - provideClientHydration(), - provideAnimations(), // required animations providers - provideToastr(), // Toastr providers - provideErrorTailorConfig({ - errors: { - useFactory() { - return { - required: 'This field is required', - minlength: ({ requiredLength, actualLength }) => `Expect ${requiredLength} but got ${actualLength}`, - invalidEmailAddress: error => `Email Address is not valid`, - invalidMobile: error => `Invalid Mobile number`, - invalidPassword: error => `Password is weak`, - passwordMustMatch: error => `Password is not matching`, - }; - }, - deps: [] - } - //controlErrorComponent: CustomControlErrorComponent, // Uncomment to see errors being rendered using a custom component - //controlErrorComponentAnchorFn: controlErrorComponentAnchorFn // Uncomment to see errors being positioned differently - }) - ], + providers: [ + provideHttpClient(), + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter(routes), + provideClientHydration(), + provideAnimations(), // required animations providers + provideToastr(), // Toastr providers + provideErrorTailorConfig({ + errors: { + useFactory() { + return { + required: 'This field is required', + minlength: ({ requiredLength, actualLength }) => `Expect ${requiredLength} but got ${actualLength}`, + invalidEmailAddress: error => `Email Address is not valid`, + invalidMobile: error => `Invalid Mobile number`, + invalidPassword: error => `Password is weak`, + passwordMustMatch: error => `Password is not matching`, + }; + }, + deps: [] + } + //controlErrorComponent: CustomControlErrorComponent, // Uncomment to see errors being rendered using a custom component + //controlErrorComponentAnchorFn: controlErrorComponentAnchorFn // Uncomment to see errors being positioned differently + }), + provideHttpClient(withInterceptors([jwtInterceptor])), + ], }; diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts index 2996ab6..9561200 100644 --- a/frontend/src/environments/environment.ts +++ b/frontend/src/environments/environment.ts @@ -1,8 +1,8 @@ export const environment = { - production: false, - apiEndpoint: '/api', - angular: 'Angular 18', - bootstrap: 'Bootstrap 5', - expressjs: 'Express.js 4.17.1', - mongoDb : 'MongoDB 7.0', + production: false, + apiEndpoint: 'http://localhost:3000/api', + angular: 'Angular 18', + bootstrap: 'Bootstrap 5', + expressjs: 'Express.js 4.17.1', + mongoDb: 'MongoDB 7.0', }; diff --git a/mongo/init-db.d/01.Seed.sh b/mongo/init-db.d/01.Seed.no similarity index 100% rename from mongo/init-db.d/01.Seed.sh rename to mongo/init-db.d/01.Seed.no diff --git a/mongo/init-db.d/02.Users.sh b/mongo/init-db.d/02.Users.no similarity index 100% rename from mongo/init-db.d/02.Users.sh rename to mongo/init-db.d/02.Users.no diff --git a/mongo/init-db.d/init-mongo.sh b/mongo/init-db.d/init-mongo.sh index 49c6731..a5faf2e 100644 --- a/mongo/init-db.d/init-mongo.sh +++ b/mongo/init-db.d/init-mongo.sh @@ -1,12 +1,15 @@ -mongo -- "$MONGO_INITDB_DATABASE" <