Repository: linnovate/mean Branch: master Commit: 5c12e1303c91 Files: 82 Total size: 58.8 KB Directory structure: gitextract_bnkb_igt/ ├── .dockerignore ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .husky/ │ └── pre-commit ├── .prettierrc ├── CNAME ├── Dockerfile ├── README.md ├── _config.yml ├── _layouts/ │ └── default.html ├── angular.json ├── assets/ │ └── css/ │ └── style.scss ├── docker-compose.yml ├── karma.conf.js ├── lint-staged.config.mjs ├── package.json ├── protractor.conf.js ├── server/ │ ├── config/ │ │ ├── config.js │ │ ├── express.js │ │ ├── mongoose.js │ │ ├── passport.js │ │ └── swagger.json │ ├── controllers/ │ │ ├── auth.controller.js │ │ └── user.controller.js │ ├── index.js │ ├── middleware/ │ │ └── require-admin.js │ ├── models/ │ │ └── user.model.js │ └── routes/ │ ├── auth.route.js │ ├── index.route.js │ └── user.route.js ├── src/ │ ├── _settings.scss │ ├── app/ │ │ ├── admin/ │ │ │ ├── admin-routing.module.ts │ │ │ ├── admin-user-guard.ts │ │ │ ├── admin.component.html │ │ │ ├── admin.component.ts │ │ │ └── admin.module.ts │ │ ├── app-routing.module.ts │ │ ├── app.component.html │ │ ├── app.component.scss │ │ ├── app.component.ts │ │ ├── app.module.ts │ │ ├── auth/ │ │ │ ├── auth-routing.module.ts │ │ │ ├── auth.component.scss │ │ │ ├── auth.module.ts │ │ │ ├── login/ │ │ │ │ ├── login.component.html │ │ │ │ ├── login.component.spec.ts │ │ │ │ └── login.component.ts │ │ │ └── register/ │ │ │ ├── register.component.html │ │ │ ├── register.component.spec.ts │ │ │ └── register.component.ts │ │ ├── header/ │ │ │ ├── header.component.html │ │ │ ├── header.component.scss │ │ │ ├── header.component.spec.ts │ │ │ └── header.component.ts │ │ ├── home/ │ │ │ ├── home.component.html │ │ │ ├── home.component.scss │ │ │ ├── home.component.spec.ts │ │ │ └── home.component.ts │ │ ├── interceptors/ │ │ │ ├── header.interceptor.ts │ │ │ └── http-error.interceptor.ts │ │ └── shared/ │ │ ├── guards/ │ │ │ ├── auth.guard.ts │ │ │ └── index.ts │ │ ├── interfaces/ │ │ │ ├── index.ts │ │ │ └── user.interface.ts │ │ ├── services/ │ │ │ ├── auth/ │ │ │ │ ├── auth.service.spec.ts │ │ │ │ ├── auth.service.ts │ │ │ │ └── token.storage.ts │ │ │ └── index.ts │ │ └── shared.module.ts │ ├── assets/ │ │ └── .gitkeep │ ├── environments/ │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── index.html │ ├── main.ts │ ├── polyfills.ts │ ├── styles.scss │ └── test.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.spec.json └── tslint.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ .git/ dist/ examples/ node_modules/ ================================================ FILE: .editorconfig ================================================ # Editor configuration, see http://editorconfig.org root = true [*] charset = utf-8 indent_style = space indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true [*.md] max_line_length = off trim_trailing_whitespace = false ================================================ FILE: .eslintrc.json ================================================ { "root": true, "ignorePatterns": ["projects/**/*"], "overrides": [ { "files": ["*.ts"], "parserOptions": { "project": ["tsconfig.json"], "createDefaultProgram": true }, "extends": [ "plugin:@angular-eslint/recommended", "plugin:@angular-eslint/template/process-inline-templates" ], "rules": {} }, { "files": ["*.html"], "extends": ["plugin:@angular-eslint/template/recommended"], "rules": {} } ] } ================================================ FILE: .gitignore ================================================ # See http://help.github.com/ignore-files/ for more about ignoring files. # compiled output /dist /dist-server /tmp /out-tsc # dependencies /node_modules # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json # misc /.angular/cache /.sass-cache /connect.lock /coverage /libpeerconnection.log npm-debug.log yarn-error.log testem.log /typings # e2e /e2e/*.js /e2e/*.map # System Files .DS_Store Thumbs.db # Env file *.env ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" yarn lint-staged ================================================ FILE: .prettierrc ================================================ { "singleQuote": true, "endOfLine": "lf", "trailingComma": "es5", "tabWidth": 2, "arrowParens": "avoid" } ================================================ FILE: CNAME ================================================ www.mean.io ================================================ FILE: Dockerfile ================================================ FROM node:14.18-alpine WORKDIR /usr/src/app COPY . /usr/src/app ENV HUSKY_SKIP_INSTALL=true RUN yarn --pure-lockfile --non-interactive --no-progress RUN yarn build:prod EXPOSE 4040 CMD ["yarn", "serve"] ================================================ FILE: README.md ================================================ ## Welcome to the mean stack The mean stack is intended to provide a simple and fun starting point for cloud native fullstack javascript applications. MEAN is a set of Open Source components that together, provide an end-to-end framework for building dynamic web applications; starting from the top (code running in the browser) to the bottom (database). The stack is made up of: - **M**ongoDB : Document database – used by your back-end application to store its data as JSON (JavaScript Object Notation) documents - **E**xpress (sometimes referred to as Express.js): Back-end web application framework running on top of Node.js - **A**ngular (formerly Angular.js): Front-end web app framework; runs your JavaScript code in the user's browser, allowing your application UI to be dynamic - **N**ode.js : JavaScript runtime environment – lets you implement your application back-end in JavaScript ### Pre-requisites - git - [Installation guide](https://www.linode.com/docs/development/version-control/how-to-install-git-on-linux-mac-and-windows/) . - node.js - [Download page](https://nodejs.org/en/download/) . - npm - comes with node or download yarn - [Download page](https://yarnpkg.com/lang/en/docs/install) . - mongodb - [Download page](https://www.mongodb.com/download-center/community) . ### Installation ``` git clone https://github.com/linnovate/mean cd mean cp .env.example .env yarn yarn start (for development) ``` ### Docker based > ⚠️ Make sure your Docker version is 19.03.0+. ``` git clone https://github.com/linnovate/mean cd mean cp .env.example .env docker-compose up -d ``` ### Credits - The MEAN name was coined by Valeri Karpov. - Initial concept and development were done by Amos Haviv and sponsored by Linnovate. - Inspired by the great work of Madhusudhan Srinivasa. ================================================ FILE: _config.yml ================================================ theme: jekyll-theme-minimal logo: https://www.linnovate.net/sites/all/themes/linnovate/images/mean-picture.png ================================================ FILE: _layouts/default.html ================================================ {% seo %}
{% if site.logo %} {% endif %}

{{ site.description | default: site.github.project_tagline }}

{% if site.github.is_project_page %}

View the Project on GitHub {{ site.github.repository_nwo }}

{% endif %} Star {% if site.github.is_user_page %}

View My GitHub Profile

{% endif %} {% if site.show_downloads %} {% endif %}
{{ content }} {% if site.github.is_project_page %}

This project is maintained by {{ site.github.owner_name }}

{% endif %}
================================================ FILE: angular.json ================================================ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "projects", "projects": { "mean": { "root": "", "sourceRoot": "src", "projectType": "application", "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist", "index": "src/index.html", "main": "src/main.ts", "tsConfig": "./tsconfig.app.json", "polyfills": "src/polyfills.ts", "assets": ["src/assets", "src/favicon.ico"], "styles": ["src/styles.scss"], "scripts": [], "aot": false, "vendorChunk": true, "extractLicenses": false, "buildOptimizer": false, "sourceMap": true, "optimization": false, "namedChunks": true }, "configurations": { "production": { "optimization": true, "outputHashing": "all", "sourceMap": false, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "fileReplacements": [ { "replace": "src/environments/environment.ts", "with": "src/environments/environment.prod.ts" } ] } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "mean:build" }, "configurations": { "production": { "browserTarget": "mean:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "mean:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "src/test.ts", "karmaConfig": "./karma.conf.js", "polyfills": "src/polyfills.ts", "tsConfig": "./tsconfig.spec.json", "scripts": [], "styles": ["src/styles.scss"], "assets": ["src/assets", "src/favicon.ico"] } }, "lint": { "builder": "@angular-eslint/builder:lint", "options": { "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"] } } } } }, "schematics": { "@schematics/angular:component": { "prefix": "app", "style": "scss" }, "@schematics/angular:directive": { "prefix": "app" } } } ================================================ FILE: assets/css/style.scss ================================================ --- --- @import "{{ site.theme }}"; h1 { a { color:#00758f; text-size:40px; } } header { img.logo { margin-left:30px; display:block; height: auto; width: auto; max-width: 150px; max-height: 200px; margin-bottom: 17%; } img.ninja { margin-top: 20px; height: auto; width: auto; max-width: 350px; max-height: 200px; margin-bottom: 50px; } } ================================================ FILE: docker-compose.yml ================================================ version: '3.8' services: app: build: ./ image: mean ports: - 4040:4040 environment: NODE_ENV: production SERVER_PORT: 4040 JWT_SECRET: 0a6b944d-d2fb-46fc-a85e-0295c986cd9f MONGO_HOST: mongodb://mongo/mean restart: always depends_on: - mongo mongo: image: mongo:4.2 volumes: - mongo_data:/data/db volumes: mongo_data: ================================================ FILE: karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html module.exports = function (config) { config.set({ basePath: '', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma'), ], client: { clearContext: false, // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, 'coverage'), reports: ['html', 'lcovonly'], fixWebpackSourcePaths: true, }, angularCli: { environment: 'dev', }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false, }); }; ================================================ FILE: lint-staged.config.mjs ================================================ export default { '*.{js,ts,html,scss,md,json}': ['prettier --write'], }; ================================================ FILE: package.json ================================================ { "name": "mean", "version": "2.0.2", "license": "MIT", "scripts": { "ng": "ng", "serve": "node server", "start": "concurrently -c \"yellow.bold,green.bold\" -n \"SERVER,BUILD\" \"nodemon server\" \"ng build --watch\"", "build:prod": "ng build --configuration production", "test": "ng test", "lint": "ng lint", "e2e": "ng e2e", "postinstall": "husky install" }, "private": true, "dependencies": { "@angular/animations": "^15.2.9", "@angular/cdk": "^15.2.9", "@angular/common": "^15.2.9", "@angular/compiler": "^15.2.9", "@angular/core": "^15.2.9", "@angular/forms": "^15.2.9", "@angular/material": "^15.2.9", "@angular/platform-browser": "^15.2.9", "@angular/platform-browser-dynamic": "^15.2.9", "@angular/router": "^15.2.9", "bcrypt": "^5.0.1", "body-parser": "^1.18.2", "compression": "^1.7.2", "cookie-parser": "^1.4.3", "cors": "^2.8.4", "dotenv": "^10.0.0", "events": "^3.0.0", "express": "^4.16.3", "express-async-handler": "^1.1.3", "express-jwt": "^5.3.1", "express-validation": "^1.0.2", "formidable": "^3.1.3", "helmet": "^4.6.0", "http-errors": "^1.6.3", "joi": "^17.4.2", "jsonwebtoken": "^8.2.1", "method-override": "^3.0.0", "mongoose": "^6.0.13", "morgan": "^1.9.1", "nodemon": "^2.0.15", "passport": "^0.4.0", "passport-jwt": "^4.0.0", "passport-local": "^1.0.0", "rxjs": "^7.4.0", "swagger-ui-express": "^4.1.6", "zone.js": "~0.11.4" }, "devDependencies": { "@angular-devkit/build-angular": "^15.2.9", "@angular-eslint/builder": "14.0.2", "@angular-eslint/eslint-plugin": "14.0.2", "@angular-eslint/eslint-plugin-template": "14.0.2", "@angular-eslint/schematics": "14.0.2", "@angular-eslint/template-parser": "14.0.2", "@angular/cli": "^15.2.9", "@angular/compiler-cli": "^15.2.9", "@angular/language-service": "^15.2.9", "@types/jasmine": "~3.6.0", "@types/jasminewd2": "~2.0.2", "@types/node": "~15.0.0", "@typescript-eslint/eslint-plugin": "5.3.0", "@typescript-eslint/parser": "5.3.0", "concurrently": "^3.5.1", "eslint": "^8.2.0", "husky": "^7.0.2", "jasmine-core": "~3.6.0", "jasmine-spec-reporter": "~5.0.0", "karma": "~6.3.2", "karma-chrome-launcher": "~3.1.0", "karma-coverage-istanbul-reporter": "~3.0.2", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "lint-staged": "^13.0.3", "prettier": "^2.7.1", "ts-node": "~6.1.0", "typescript": "4.9.5" } } ================================================ FILE: protractor.conf.js ================================================ // Protractor configuration file, see link for more information // https://github.com/angular/protractor/blob/master/lib/config.ts const { SpecReporter } = require('jasmine-spec-reporter'); exports.config = { allScriptsTimeout: 11000, specs: [ './e2e/**/*.e2e-spec.ts' ], capabilities: { 'browserName': 'chrome' }, directConnect: true, baseUrl: 'http://localhost:4200/', framework: 'jasmine', jasmineNodeOpts: { showColors: true, defaultTimeoutInterval: 30000, print: function() {} }, onPrepare() { require('ts-node').register({ project: 'e2e/tsconfig.e2e.json' }); jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); } }; ================================================ FILE: server/config/config.js ================================================ const Joi = require('joi'); // require and configure dotenv, will load vars in .env in PROCESS.ENV require('dotenv').config(); // define validation for all the env vars const envVarsSchema = Joi.object({ NODE_ENV: Joi.string() .allow('development', 'production', 'test', 'provision') .default('development'), SERVER_PORT: Joi.number().default(4040), MONGOOSE_DEBUG: Joi.boolean().when('NODE_ENV', { is: Joi.string().equal('development'), then: Joi.boolean().default(true), otherwise: Joi.boolean().default(false), }), JWT_SECRET: Joi.string() .required() .description('JWT Secret required to sign'), MONGO_HOST: Joi.string().required().description('Mongo DB host url'), MONGO_PORT: Joi.number().default(27017), }) .unknown() .required(); const { error, value: envVars } = envVarsSchema.validate(process.env); if (error) { throw new Error(`Config validation error: ${error.message}`); } const config = { env: envVars.NODE_ENV, port: envVars.SERVER_PORT, mongooseDebug: envVars.MONGOOSE_DEBUG, jwtSecret: envVars.JWT_SECRET, frontend: envVars.MEAN_FRONTEND || 'angular', mongo: { host: envVars.MONGO_HOST, port: envVars.MONGO_PORT, }, }; module.exports = config; ================================================ FILE: server/config/express.js ================================================ const path = require('path'); const express = require('express'); const httpError = require('http-errors'); const logger = require('morgan'); const bodyParser = require('body-parser'); const cookieParser = require('cookie-parser'); const compress = require('compression'); const methodOverride = require('method-override'); const cors = require('cors'); const helmet = require('helmet'); const swaggerUi = require('swagger-ui-express'); const swaggerDocument = require('./swagger.json'); const routes = require('../routes/index.route'); const config = require('./config'); const passport = require('./passport'); const app = express(); if (config.env === 'development') { app.use(logger('dev')); } // Choose what fronten framework to serve the dist from var distDir = '../../dist/'; if (config.frontend == 'react') { distDir = '../../node_modules/material-dashboard-react/dist'; } else { distDir = '../../dist/'; } // app.use(express.static(path.join(__dirname, distDir))); app.use(/^((?!(api)).)*/, (req, res) => { res.sendFile(path.join(__dirname, distDir + '/index.html')); }); console.log(distDir); //React server app.use( express.static( path.join(__dirname, '../../node_modules/material-dashboard-react/dist') ) ); app.use(/^((?!(api)).)*/, (req, res) => { res.sendFile(path.join(__dirname, '../../dist/index.html')); }); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(cookieParser()); app.use(compress()); app.use(methodOverride()); // secure apps by setting various HTTP headers app.use(helmet()); // enable CORS - Cross Origin Resource Sharing app.use(cors()); app.use(passport.initialize()); app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); // API router app.use('/api/', routes); // catch 404 and forward to error handler app.use((req, res, next) => { const err = new httpError(404); return next(err); }); // error handler, send stacktrace only during development app.use((err, req, res, next) => { // customize Joi validation errors if (err.isJoi) { err.message = err.details.map(e => e.message).join('; '); err.status = 400; } res.status(err.status || 500).json({ message: err.message, }); next(err); }); module.exports = app; ================================================ FILE: server/config/mongoose.js ================================================ const mongoose = require('mongoose'); const util = require('util'); const debug = require('debug')('express-mongoose-es6-rest-api:index'); const config = require('./config'); // connect to mongo db const mongoUri = config.mongo.host; mongoose.connect(mongoUri, { keepAlive: 1 }); mongoose.connection.on('error', () => { throw new Error(`unable to connect to database: ${mongoUri}`); }); // print mongoose logs in dev env if (config.MONGOOSE_DEBUG) { mongoose.set('debug', (collectionName, method, query, doc) => { debug(`${collectionName}.${method}`, util.inspect(query, false, 20), doc); }); } ================================================ FILE: server/config/passport.js ================================================ const passport = require('passport'); const LocalStrategy = require('passport-local'); const JwtStrategy = require('passport-jwt').Strategy; const ExtractJwt = require('passport-jwt').ExtractJwt; const bcrypt = require('bcrypt'); const User = require('../models/user.model'); const config = require('./config'); const localLogin = new LocalStrategy( { usernameField: 'email', }, async (email, password, done) => { let user = await User.findOne({ email }); if (!user || !bcrypt.compareSync(password, user.hashedPassword)) { return done(null, false, { error: 'Your login details could not be verified. Please try again.', }); } user = user.toObject(); delete user.hashedPassword; done(null, user); } ); const jwtLogin = new JwtStrategy( { jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), secretOrKey: config.jwtSecret, }, async (payload, done) => { let user = await User.findById(payload._id); if (!user) { return done(null, false); } user = user.toObject(); delete user.hashedPassword; done(null, user); } ); passport.use(jwtLogin); passport.use(localLogin); module.exports = passport; ================================================ FILE: server/config/swagger.json ================================================ { "swagger": "2.0", "info": { "version": "1.0.0", "title": "Mean Application API", "description": "Mean Application API", "license": { "name": "MIT", "url": "https://opensource.org/licenses/MIT" } }, "host": "localhost:4040", "basePath": "/api/", "tags": [ { "name": "Users", "description": "API for users in the system" }, { "name": "Auth", "description": "API for auth in the system" } ], "schemes": [ "http" ], "consumes": [ "application/json" ], "produces": [ "application/json" ], "securityDefinitions": { "AuthHeader": { "type": "apiKey", "in": "header", "name": "Authorization" } }, "paths": { "/auth/login": { "post": { "tags": ["Auth"], "description": "Login to the system", "parameters": [{ "name": "auth", "in": "body", "description": "User auth details", "schema": { "type": "object", "required": ["email", "password"], "properties": { "email": { "type": "string" }, "password": { "type": "string" } } } }], "produces": [ "application/json" ], "responses": { "200": { "description": "User is loggedin", "schema": { "$ref": "#/definitions/User" } } } } } }, "definitions": { "User": { "required": [ "email", "fullname" ], "properties": { "_id": { "type": "string", "uniqueItems": true }, "email": { "type": "string", "uniqueItems": true }, "fullname": { "type": "string" }, "createdAt": { "type": "string" }, "roles": { "type": "array", "items": { "type": "string" } } } }, "Users": { "type": "array", "$ref": "#/definitions/User" }, "Auth": { "type": "object", "properties": [{ "token": { "type": "string" }, "user": { "$ref": "#/definitions/User" } }] } } } ================================================ FILE: server/controllers/auth.controller.js ================================================ const jwt = require('jsonwebtoken'); const config = require('../config/config'); module.exports = { generateToken, }; function generateToken(user) { const payload = JSON.stringify(user); return jwt.sign(payload, config.jwtSecret); } ================================================ FILE: server/controllers/user.controller.js ================================================ const bcrypt = require('bcrypt'); const Joi = require('joi'); const User = require('../models/user.model'); const userSchema = Joi.object({ fullname: Joi.string().required(), email: Joi.string().email(), mobileNumber: Joi.string().regex(/^[1-9][0-9]{9}$/), password: Joi.string().required(), repeatPassword: Joi.string().required().valid(Joi.ref('password')), }); module.exports = { insert, }; async function insert(user) { user = await userSchema.validateAsync(user, { abortEarly: false }); user.hashedPassword = bcrypt.hashSync(user.password, 10); delete user.password; return await new User(user).save(); } ================================================ FILE: server/index.js ================================================ // config should be imported before importing any other file const config = require('./config/config'); const app = require('./config/express'); require('./config/mongoose'); // module.parent check is required to support mocha watch // src: https://github.com/mochajs/mocha/issues/1912 if (!module.parent) { app.listen(config.port, () => { console.info(`server started on port ${config.port} (${config.env})`); }); } module.exports = app; ================================================ FILE: server/middleware/require-admin.js ================================================ const httpError = require('http-errors'); const requireAdmin = function (req, res, next) { if (req.user && req.user.roles.indexOf('admin') > -1) return next(); const err = new httpError(401); return next(err); }; module.exports = requireAdmin; ================================================ FILE: server/models/user.model.js ================================================ const mongoose = require('mongoose'); const UserSchema = new mongoose.Schema( { fullname: { type: String, required: true, }, email: { type: String, required: true, unique: true, // Regexp to validate emails with more strict rules as added in tests/users.js which also conforms mostly with RFC2822 guide lines match: [ /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, 'Please enter a valid email', ], }, hashedPassword: { type: String, required: true, }, createdAt: { type: Date, default: Date.now, }, roles: [ { type: String, }, ], }, { versionKey: false, } ); module.exports = mongoose.model('User', UserSchema); ================================================ FILE: server/routes/auth.route.js ================================================ const express = require('express'); const asyncHandler = require('express-async-handler'); const passport = require('passport'); const userCtrl = require('../controllers/user.controller'); const authCtrl = require('../controllers/auth.controller'); const config = require('../config/config'); const router = express.Router(); module.exports = router; router.post('/register', asyncHandler(register), login); router.post( '/login', passport.authenticate('local', { session: false }), login ); router.get('/me', passport.authenticate('jwt', { session: false }), login); async function register(req, res, next) { let user = await userCtrl.insert(req.body); user = user.toObject(); delete user.hashedPassword; req.user = user; next(); } function login(req, res) { let user = req.user; let token = authCtrl.generateToken(user); res.json({ user, token }); } ================================================ FILE: server/routes/index.route.js ================================================ const express = require('express'); const userRoutes = require('./user.route'); const authRoutes = require('./auth.route'); const router = express.Router(); // eslint-disable-line new-cap /** GET /health-check - Check service health */ router.get('/health-check', (req, res) => res.send('OK')); router.use('/auth', authRoutes); router.use('/user', userRoutes); module.exports = router; ================================================ FILE: server/routes/user.route.js ================================================ const express = require('express'); const passport = require('passport'); const asyncHandler = require('express-async-handler'); const userCtrl = require('../controllers/user.controller'); const router = express.Router(); module.exports = router; router.use(passport.authenticate('jwt', { session: false })); router.route('/').post(asyncHandler(insert)); async function insert(req, res) { let user = await userCtrl.insert(req.body); res.json(user); } ================================================ FILE: src/_settings.scss ================================================ /*----------------------------------------------- Variables -----------------------------------------------*/ $linesColor: #dbdbdb; $categoryTitleColor: #686868; $categoryEntityColor: #3f3f3f; ================================================ FILE: src/app/admin/admin-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AdminComponent } from './admin.component'; import { OnlyAdminUsersGuard } from './admin-user-guard'; const routes: Routes = [ { path: 'admin', canActivate: [OnlyAdminUsersGuard], children: [ { path: '', component: AdminComponent, }, ], }, ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], }) export class AdminRoutingModule {} ================================================ FILE: src/app/admin/admin-user-guard.ts ================================================ import { Injectable } from '@angular/core'; import { CanActivate } from '@angular/router'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { AuthService } from '@app/shared/services'; @Injectable() export class OnlyAdminUsersGuard implements CanActivate { constructor(private authService: AuthService) {} canActivate(): Observable { return this.authService.getUser().pipe(map(user => !!user?.isAdmin)); } } ================================================ FILE: src/app/admin/admin.component.html ================================================

HELLO FROM ADMIN PAGE

================================================ FILE: src/app/admin/admin.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'app-admin', templateUrl: './admin.component.html', }) export class AdminComponent {} ================================================ FILE: src/app/admin/admin.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AdminRoutingModule } from './admin-routing.module'; import { AdminComponent } from './admin.component'; import { OnlyAdminUsersGuard } from './admin-user-guard'; @NgModule({ declarations: [AdminComponent], imports: [CommonModule, AdminRoutingModule], providers: [OnlyAdminUsersGuard], }) export class AdminModule {} ================================================ FILE: src/app/app-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { AuthGuard } from './shared/guards'; import { HomeComponent } from './home/home.component'; const routes: Routes = [ { path: '', component: HomeComponent, canActivate: [AuthGuard], }, { path: 'auth', loadChildren: () => import('./auth/auth.module').then(m => m.AuthModule), }, { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule), }, ]; @NgModule({ imports: [RouterModule.forRoot(routes, {})], exports: [RouterModule], }) export class AppRoutingModule {} ================================================ FILE: src/app/app.component.html ================================================
================================================ FILE: src/app/app.component.scss ================================================ .wrapper-app { } ================================================ FILE: src/app/app.component.ts ================================================ import { Component } from '@angular/core'; import { MatIconRegistry } from '@angular/material/icon'; import { DomSanitizer } from '@angular/platform-browser'; import { merge, Observable } from 'rxjs'; import { User } from './shared/interfaces'; import { AuthService } from './shared/services'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], }) export class AppComponent { user$: Observable = merge( // Init on startup this.authService.me(), // Update after login/register/logout this.authService.getUser() ); constructor( private domSanitizer: DomSanitizer, private matIconRegistry: MatIconRegistry, private authService: AuthService ) { this.registerSvgIcons(); } registerSvgIcons() { [ 'close', 'add', 'add-blue', 'airplane-front-view', 'air-station', 'balloon', 'boat', 'cargo-ship', 'car', 'catamaran', 'clone', 'convertible', 'delete', 'drone', 'fighter-plane', 'fire-truck', 'horseback-riding', 'motorcycle', 'railcar', 'railroad-train', 'rocket-boot', 'sailing-boat', 'segway', 'shuttle', 'space-shuttle', 'steam-engine', 'suv', 'tour-bus', 'tow-truck', 'transportation', 'trolleybus', 'water-transportation', ].forEach(icon => { this.matIconRegistry.addSvgIcon( icon, this.domSanitizer.bypassSecurityTrustResourceUrl( `assets/icons/${icon}.svg` ) ); }); } } ================================================ FILE: src/app/app.module.ts ================================================ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NgModule, APP_INITIALIZER } from '@angular/core'; import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { SharedModule } from './shared/shared.module'; import { AppComponent } from './app.component'; import { AuthHeaderInterceptor } from './interceptors/header.interceptor'; import { CatchErrorInterceptor } from './interceptors/http-error.interceptor'; import { AppRoutingModule } from './app-routing.module'; import { HeaderComponent } from './header/header.component'; import { HomeComponent } from './home/home.component'; import { AuthService } from './shared/services'; export function appInitializerFactory(authService: AuthService) { return () => authService.checkTheUserOnTheFirstLoad(); } @NgModule({ imports: [ BrowserAnimationsModule, HttpClientModule, SharedModule, AppRoutingModule, ], declarations: [AppComponent, HeaderComponent, HomeComponent], providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthHeaderInterceptor, multi: true, }, { provide: HTTP_INTERCEPTORS, useClass: CatchErrorInterceptor, multi: true, }, { provide: APP_INITIALIZER, useFactory: appInitializerFactory, multi: true, deps: [AuthService], }, ], bootstrap: [AppComponent], }) export class AppModule {} ================================================ FILE: src/app/auth/auth-routing.module.ts ================================================ import { Routes, RouterModule } from '@angular/router'; import { LoginComponent } from './login/login.component'; import { RegisterComponent } from './register/register.component'; const routes: Routes = [ { path: '', children: [ { path: '', redirectTo: '/auth/login', pathMatch: 'full', }, { path: 'login', component: LoginComponent, }, { path: 'register', component: RegisterComponent, }, ], }, ]; export const AuthRoutingModule = RouterModule.forChild(routes); ================================================ FILE: src/app/auth/auth.component.scss ================================================ .example-icon { padding: 0 14px; } .example-spacer { flex: 1 1 auto; } .example-card { width: 400px; margin: 10% auto; } .mat-card-title { font-size: 16px; } ================================================ FILE: src/app/auth/auth.module.ts ================================================ import { NgModule } from '@angular/core'; import { SharedModule } from '../shared/shared.module'; import { LoginComponent } from './login/login.component'; import { RegisterComponent } from './register/register.component'; import { AuthRoutingModule } from './auth-routing.module'; @NgModule({ imports: [SharedModule, AuthRoutingModule], declarations: [LoginComponent, RegisterComponent], }) export class AuthModule {} ================================================ FILE: src/app/auth/login/login.component.html ================================================ Login
Don't have an account ? register here
================================================ FILE: src/app/auth/login/login.component.spec.ts ================================================ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { LoginComponent } from './login.component'; describe('LoginComponent', () => { let component: LoginComponent; let fixture: ComponentFixture; beforeEach( waitForAsync(() => { TestBed.configureTestingModule({ declarations: [LoginComponent], }).compileComponents(); }) ); beforeEach(() => { fixture = TestBed.createComponent(LoginComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: src/app/auth/login/login.component.ts ================================================ import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { AuthService } from '@app/shared/services'; @Component({ selector: 'app-login', templateUrl: './login.component.html', styleUrls: ['../auth.component.scss'], }) export class LoginComponent { email: string | null = null; password: string | null = null; constructor(private router: Router, private authService: AuthService) {} login(): void { this.authService.login(this.email!, this.password!).subscribe(() => { this.router.navigateByUrl('/'); }); } } ================================================ FILE: src/app/auth/register/register.component.html ================================================ Register
Invalid email address
Password mismatch
Already have an account ? login here
================================================ FILE: src/app/auth/register/register.component.spec.ts ================================================ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { RegisterComponent } from './register.component'; describe('RegisterComponent', () => { let component: RegisterComponent; let fixture: ComponentFixture; beforeEach( waitForAsync(() => { TestBed.configureTestingModule({ declarations: [RegisterComponent], }).compileComponents(); }) ); beforeEach(() => { fixture = TestBed.createComponent(RegisterComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: src/app/auth/register/register.component.ts ================================================ import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { UntypedFormGroup, UntypedFormControl, Validators, ValidationErrors, AbstractControl, } from '@angular/forms'; import { AuthService } from '@app/shared/services'; @Component({ selector: 'app-register', templateUrl: './register.component.html', styleUrls: ['../auth.component.scss'], }) export class RegisterComponent { constructor(private router: Router, private authService: AuthService) {} passwordsMatchValidator( control: UntypedFormControl ): ValidationErrors | null { const password = control.root.get('password'); return password && control.value !== password.value ? { passwordMatch: true, } : null; } userForm = new UntypedFormGroup({ fullname: new UntypedFormControl('', [Validators.required]), email: new UntypedFormControl('', [Validators.required, Validators.email]), password: new UntypedFormControl('', [Validators.required]), repeatPassword: new UntypedFormControl('', [ Validators.required, this.passwordsMatchValidator, ]), }); get fullname(): AbstractControl { return this.userForm.get('fullname')!; } get email(): AbstractControl { return this.userForm.get('email')!; } get password(): AbstractControl { return this.userForm.get('password')!; } get repeatPassword(): AbstractControl { return this.userForm.get('repeatPassword')!; } register(): void { if (this.userForm.invalid) { return; } const { fullname, email, password, repeatPassword } = this.userForm.getRawValue(); this.authService .register(fullname, email, password, repeatPassword) .subscribe(() => { this.router.navigate(['']); }); } } ================================================ FILE: src/app/header/header.component.html ================================================
Login
================================================ FILE: src/app/header/header.component.scss ================================================ header { width: 100%; .logo { background-image: url('../../assets/logo.png'); width: 50px; height: 50px; background-size: contain; background-repeat: no-repeat; } .example-spacer { flex: 1 1 auto; } .links { color: white; font-family: 'Helvetica Neue', sans-serif; font-size: 15px; font-weight: initial; letter-spacing: -1px; line-height: 1; text-align: center; padding: 15px; &.side { padding: 0 14px; } } .mat-toolbar { background: black; } .mat-icon { vertical-align: middle; margin: 0 5px; } a { cursor: pointer; } } ================================================ FILE: src/app/header/header.component.spec.ts ================================================ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { HeaderComponent } from './header.component'; describe('HeaderComponent', () => { let component: HeaderComponent; let fixture: ComponentFixture; beforeEach( waitForAsync(() => { TestBed.configureTestingModule({ declarations: [HeaderComponent], }).compileComponents(); }) ); beforeEach(() => { fixture = TestBed.createComponent(HeaderComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: src/app/header/header.component.ts ================================================ import { Component, Input } from '@angular/core'; import { Router } from '@angular/router'; import { User } from '@app/shared/interfaces'; import { AuthService } from '@app/shared/services'; @Component({ selector: 'app-header', templateUrl: './header.component.html', styleUrls: ['./header.component.scss'], }) export class HeaderComponent { @Input() user: User | null = null; constructor(private router: Router, private authService: AuthService) {} logout(): void { this.authService.signOut(); this.router.navigateByUrl('/auth/login'); } } ================================================ FILE: src/app/home/home.component.html ================================================

home works!

================================================ FILE: src/app/home/home.component.scss ================================================ ================================================ FILE: src/app/home/home.component.spec.ts ================================================ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { HomeComponent } from './home.component'; describe('HomeComponent', () => { let component: HomeComponent; let fixture: ComponentFixture; beforeEach( waitForAsync(() => { TestBed.configureTestingModule({ declarations: [HomeComponent], }).compileComponents(); }) ); beforeEach(() => { fixture = TestBed.createComponent(HomeComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: src/app/home/home.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'app-home', templateUrl: './home.component.html', styleUrls: ['./home.component.scss'], }) export class HomeComponent {} ================================================ FILE: src/app/interceptors/header.interceptor.ts ================================================ import { Injectable } from '@angular/core'; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, } from '@angular/common/http'; import { Observable } from 'rxjs'; import { AuthService } from '@app/shared/services'; @Injectable() export class AuthHeaderInterceptor implements HttpInterceptor { constructor(private authService: AuthService) {} intercept( req: HttpRequest, next: HttpHandler ): Observable> { req = req.clone({ setHeaders: this.authService.getAuthorizationHeaders(), }); return next.handle(req); } } ================================================ FILE: src/app/interceptors/http-error.interceptor.ts ================================================ import { Injectable } from '@angular/core'; import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse, } from '@angular/common/http'; import { MatLegacySnackBar as MatSnackBar } from '@angular/material/legacy-snack-bar'; import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; @Injectable() export class CatchErrorInterceptor implements HttpInterceptor { constructor(private snackBar: MatSnackBar) {} intercept( request: HttpRequest, next: HttpHandler ): Observable> { return next.handle(request).pipe(catchError(this.showSnackBar)); } private showSnackBar = (response: HttpErrorResponse): Observable => { const text: string | undefined = response.error?.message ?? response.error.statusText; if (text) { this.snackBar.open(text, 'Close', { duration: 2000, }); } return throwError(() => response); }; } ================================================ FILE: src/app/shared/guards/auth.guard.ts ================================================ import { Injectable } from '@angular/core'; import { CanActivate, Router } from '@angular/router'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { AuthService } from '../services'; @Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { constructor(private router: Router, private authService: AuthService) {} canActivate(): Observable { return this.authService.getUser().pipe( map(user => { if (user !== null) { return true; } this.router.navigateByUrl('/auth/login'); return false; }) ); } } ================================================ FILE: src/app/shared/guards/index.ts ================================================ export * from './auth.guard'; ================================================ FILE: src/app/shared/interfaces/index.ts ================================================ export * from './user.interface'; ================================================ FILE: src/app/shared/interfaces/user.interface.ts ================================================ export interface User { _id: string; fullname: string; createdAt: string; roles: string[]; isAdmin: boolean; } ================================================ FILE: src/app/shared/services/auth/auth.service.spec.ts ================================================ import { TestBed, inject } from '@angular/core/testing'; import { AuthService } from './auth.service'; describe('AuthService', () => { beforeEach(() => { TestBed.configureTestingModule({ providers: [AuthService], }); }); it('should be created', inject([AuthService], (service: AuthService) => { expect(service).toBeTruthy(); })); }); ================================================ FILE: src/app/shared/services/auth/auth.service.ts ================================================ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable, BehaviorSubject, firstValueFrom, of } from 'rxjs'; import { tap, pluck, catchError } from 'rxjs/operators'; import { User } from '@app/shared/interfaces'; import { TokenStorage } from './token.storage'; interface AuthResponse { token: string; user: User; } @Injectable({ providedIn: 'root' }) export class AuthService { private user$ = new BehaviorSubject(null); constructor(private http: HttpClient, private tokenStorage: TokenStorage) {} login(email: string, password: string): Observable { return this.http .post('/api/auth/login', { email, password }) .pipe( tap(({ token, user }) => { this.setUser(user); this.tokenStorage.saveToken(token); }), pluck('user') ); } register( fullname: string, email: string, password: string, repeatPassword: string ): Observable { return this.http .post('/api/auth/register', { fullname, email, password, repeatPassword, }) .pipe( tap(({ token, user }) => { this.setUser(user); this.tokenStorage.saveToken(token); }), pluck('user') ); } setUser(user: User | null): void { if (user) { user.isAdmin = user.roles.includes('admin'); } this.user$.next(user); } getUser(): Observable { return this.user$.asObservable(); } me(): Observable { return this.http.get('/api/auth/me').pipe( tap(({ user }) => this.setUser(user)), pluck('user'), catchError(() => of(null)) ); } signOut(): void { this.tokenStorage.signOut(); this.setUser(null); } getAuthorizationHeaders() { const token: string | null = this.tokenStorage.getToken() || ''; return { Authorization: `Bearer ${token}` }; } /** * Let's try to get user's information if he was logged in previously, * thus we can ensure that the user is able to access the `/` (home) page. */ checkTheUserOnTheFirstLoad(): Promise { return firstValueFrom(this.me()); } } ================================================ FILE: src/app/shared/services/auth/token.storage.ts ================================================ import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class TokenStorage { private tokenKey = 'authToken'; signOut(): void { localStorage.removeItem(this.tokenKey); localStorage.clear(); } saveToken(token?: string): void { if (!token) return; localStorage.setItem(this.tokenKey, token); } getToken(): string | null { return localStorage.getItem(this.tokenKey); } } ================================================ FILE: src/app/shared/services/index.ts ================================================ export * from './auth/auth.service'; ================================================ FILE: src/app/shared/shared.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatLegacyMenuModule as MatMenuModule } from '@angular/material/legacy-menu'; import { MatLegacyTabsModule as MatTabsModule } from '@angular/material/legacy-tabs'; import { MatLegacyCardModule as MatCardModule } from '@angular/material/legacy-card'; import { MatLegacyListModule as MatListModule } from '@angular/material/legacy-list'; import { MatIconModule } from '@angular/material/icon'; import { MatTreeModule } from '@angular/material/tree'; import { MatLegacyInputModule as MatInputModule } from '@angular/material/legacy-input'; import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select'; import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/legacy-dialog'; import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button'; import { MatDividerModule } from '@angular/material/divider'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatSidenavModule } from '@angular/material/sidenav'; import { MatLegacySnackBarModule as MatSnackBarModule } from '@angular/material/legacy-snack-bar'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatLegacyFormFieldModule as MatFormFieldModule } from '@angular/material/legacy-form-field'; import { MatLegacyProgressBarModule as MatProgressBarModule } from '@angular/material/legacy-progress-bar'; @NgModule({ exports: [ FormsModule, ReactiveFormsModule, CommonModule, MatMenuModule, MatTabsModule, MatCardModule, MatListModule, MatIconModule, MatTreeModule, MatInputModule, MatSelectModule, MatDialogModule, MatButtonModule, MatDividerModule, MatToolbarModule, MatSidenavModule, MatSnackBarModule, MatExpansionModule, MatFormFieldModule, MatProgressBarModule, ], }) export class SharedModule {} ================================================ FILE: src/assets/.gitkeep ================================================ ================================================ FILE: src/environments/environment.prod.ts ================================================ export const environment = { production: true, }; ================================================ FILE: src/environments/environment.ts ================================================ export const environment = { production: false, }; ================================================ FILE: src/index.html ================================================ Mean ================================================ FILE: src/main.ts ================================================ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => console.log(err)); ================================================ FILE: src/polyfills.ts ================================================ /** * This file includes polyfills needed by Angular and is loaded before the app. * You can add your own extra polyfills to this file. * * This file is divided into 2 sections: * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. * 2. Application imports. Files imported after ZoneJS that should be loaded before your main * file. * * The current setup is for so-called "evergreen" browsers; the last versions of browsers that * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. * * Learn more in https://angular.io/guide/browser-support */ /*************************************************************************************************** * BROWSER POLYFILLS */ /** * By default, zone.js will patch all possible macroTask and DomEvents * user can disable parts of macroTask/DomEvents patch by setting following flags * because those flags need to be set before `zone.js` being loaded, and webpack * will put import in the top of bundle, so user need to create a separate file * in this directory (for example: zone-flags.ts), and put the following flags * into that file, and then add the following code before importing zone.js. * import './zone-flags.ts'; * * The flags allowed in zone-flags.ts are listed here. * * The following flags will work for all browsers. * * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames * * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js * with the following flag, it will bypass `zone.js` patch for IE/Edge * * (window as any).__Zone_enable_cross_context_check = true; * */ /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ import 'zone.js'; // Included with Angular CLI. ================================================ FILE: src/styles.scss ================================================ /* You can add global styles to this file, and also import other style files */ @use '@angular/material' as mat; @import '@angular/material/prebuilt-themes/deeppurple-amber.css'; $custom-typography: mat.define-legacy-typography-config( $font-family: 'Exo', ); // TODO(v15): As of v15 mat.legacy-core no longer includes default typography styles. // The following line adds: // 1. Default typography styles for all components // 2. Styles for typography hierarchy classes (e.g. .mat-headline-1) // If you specify typography styles for the components you use elsewhere, you should delete this line. // If you don't need the default component typographies but still want the hierarchy styles, // you can delete this line and instead use: // `@include mat.legacy-typography-hierarchy($custom-typography);` @include mat.all-legacy-component-typographies($custom-typography); @include mat.legacy-core(); body { background-color: #f4f3f3; margin: 0; app-root * { font-family: 'Exo', sans-serif; } } ================================================ FILE: src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting, } from '@angular/platform-browser-dynamic/testing'; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting(), { teardown: { destroyAfterEach: true }, } ); ================================================ FILE: tsconfig.app.json ================================================ { "extends": "./tsconfig.json", "files": [ "src/polyfills.ts", "src/main.ts" ], "include": [ "src/**/*.d.ts" ], "angularCompilerOptions": { "fullTemplateTypeCheck": true, "strictInjectionParameters": true } } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "baseUrl": "./", "paths": { "@app/*": ["src/app/*"] }, "target": "ES2022", "typeRoots": ["node_modules/@types"], "lib": ["ESNext", "DOM"], "module": "es2020", "moduleResolution": "node", "strict": true, "sourceMap": true, "declaration": false, "importHelpers": true, "experimentalDecorators": true, "suppressImplicitAnyIndexErrors": true, "useDefineForClassFields": false } } ================================================ FILE: tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "baseUrl": "./", "module": "commonjs", "types": [ "jasmine", "node" ] }, "files": [ "src/test.ts", "src/polyfills.ts" ], "include": [ "src/**/*.spec.ts", "src/**/*.d.ts" ] } ================================================ FILE: tslint.json ================================================ { "rulesDirectory": [ "node_modules/codelyzer" ], "rules": { "arrow-return-shorthand": true, "callable-types": true, "class-name": true, "comment-format": [ true, "check-space" ], "curly": true, "deprecation": { "severity": "warn" }, "eofline": true, "forin": true, "import-blacklist": [ true, "rxjs/Rx" ], "import-spacing": true, "indent": [ true, "spaces" ], "interface-over-type-literal": true, "label-position": true, "max-line-length": [ true, 140 ], "member-access": false, "member-ordering": [ true, { "order": [ "static-field", "instance-field", "static-method", "instance-method" ] } ], "no-arg": true, "no-bitwise": true, "no-console": [ true, "debug", "info", "time", "timeEnd", "trace" ], "no-construct": true, "no-debugger": true, "no-duplicate-super": true, "no-empty": false, "no-empty-interface": true, "no-eval": true, "no-inferrable-types": [ true, "ignore-params" ], "no-misused-new": true, "no-non-null-assertion": true, "no-shadowed-variable": true, "no-string-literal": false, "no-string-throw": true, "no-switch-case-fall-through": true, "no-trailing-whitespace": true, "no-unnecessary-initializer": true, "no-unused-expression": true, "no-var-keyword": true, "object-literal-sort-keys": false, "one-line": [ true, "check-open-brace", "check-catch", "check-else", "check-whitespace" ], "prefer-const": true, "quotemark": [ true, "single" ], "radix": true, "semicolon": [ true, "always" ], "triple-equals": [ true, "allow-null-check" ], "typedef-whitespace": [ true, { "call-signature": "nospace", "index-signature": "nospace", "parameter": "nospace", "property-declaration": "nospace", "variable-declaration": "nospace" } ], "unified-signatures": true, "variable-name": false, "whitespace": [ true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type" ], "directive-selector": [ true, "attribute", "app", "camelCase" ], "component-selector": [ true, "element", "app", "kebab-case" ], "no-output-on-prefix": true, "no-inputs-metadata-property": true, "no-outputs-metadata-property": true, "no-host-metadata-property": true, "no-input-rename": true, "no-output-rename": true, "use-lifecycle-interface": true, "use-pipe-transform-interface": true, "component-class-suffix": true, "directive-class-suffix": true } }