├── .gitignore ├── README.md ├── backend ├── nodemon.json ├── package-lock.json ├── package.json ├── src │ ├── app.controller.ts │ ├── app.module.ts │ ├── fav │ │ └── fav.controller.ts │ ├── login │ │ ├── dto │ │ │ └── user.dto.ts │ │ ├── login.controller.ts │ │ └── login.service.ts │ ├── main.ts │ ├── registration │ │ ├── registration.controller.ts │ │ └── registration.service.ts │ └── shared │ │ └── consts │ │ └── user-logins.const.ts ├── test │ ├── app.e2e-spec.ts │ └── jest-e2e.json ├── tsconfig.json ├── tslint.json └── yarn.lock └── frontend ├── .angular-cli.json ├── .editorconfig ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.e2e.json ├── jest-config ├── globalMocks.ts └── setup.ts ├── karma.conf.js ├── package-lock.json ├── package.json ├── protractor.conf.js ├── proxy.conf.json ├── src ├── app │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.ts │ ├── app.module.ts │ ├── core │ │ ├── core.module.ts │ │ ├── favourites │ │ │ ├── favourites.component.html │ │ │ ├── favourites.component.scss │ │ │ ├── favourites.component.ts │ │ │ └── favourites.module.ts │ │ ├── layout │ │ │ ├── footer │ │ │ │ ├── footer.component.html │ │ │ │ ├── footer.component.scss │ │ │ │ └── footer.component.ts │ │ │ ├── header │ │ │ │ ├── header.component.html │ │ │ │ ├── header.component.scss │ │ │ │ └── header.component.ts │ │ │ └── layout.module.ts │ │ ├── routes │ │ │ ├── app-routes.ts │ │ │ └── auth.guard.ts │ │ ├── services │ │ │ └── http.service.ts │ │ ├── store │ │ │ ├── cities-store.service.ts │ │ │ ├── current-city-store.service.ts │ │ │ └── current-user.service.ts │ │ ├── urls │ │ │ └── urls.const.ts │ │ └── user-features │ │ │ ├── login │ │ │ ├── login.component.html │ │ │ ├── login.component.scss │ │ │ └── login.component.ts │ │ │ ├── registration │ │ │ ├── registration.component.html │ │ │ ├── registration.component.scss │ │ │ └── registration.component.ts │ │ │ └── user-features.module.ts │ ├── main │ │ ├── history-info │ │ │ └── history-info.module.ts │ │ ├── main.component.html │ │ ├── main.component.scss │ │ ├── main.component.ts │ │ ├── main.module.ts │ │ ├── recommendations │ │ │ └── recommendations.module.ts │ │ ├── shared │ │ │ └── models │ │ │ │ ├── cities.model.ts │ │ │ │ └── user.model.ts │ │ └── weather-view │ │ │ ├── detail-view │ │ │ ├── detail-view.component.html │ │ │ ├── detail-view.component.scss │ │ │ ├── detail-view.component.ts │ │ │ └── detail-view.module.ts │ │ │ ├── weather-view.component.html │ │ │ ├── weather-view.component.scss │ │ │ ├── weather-view.component.ts │ │ │ ├── weather-view.module.ts │ │ │ └── week-view │ │ │ ├── week-graph │ │ │ ├── week-graph.component.html │ │ │ ├── week-graph.component.scss │ │ │ └── week-graph.component.ts │ │ │ ├── week-view-card │ │ │ ├── week-view-card.component.html │ │ │ ├── week-view-card.component.scss │ │ │ └── week-view-card.component.ts │ │ │ ├── week-view.component.html │ │ │ ├── week-view.component.scss │ │ │ ├── week-view.component.ts │ │ │ └── week-view.module.ts │ ├── not-found │ │ ├── not-found.component.html │ │ ├── not-found.component.scss │ │ ├── not-found.component.ts │ │ └── not-found.module.ts │ └── shared │ │ ├── loader │ │ ├── loader.component.html │ │ ├── loader.component.scss │ │ └── loader.component.ts │ │ ├── material │ │ └── material.module.ts │ │ ├── pipes │ │ └── convert-date.pipe.ts │ │ └── shared.module.ts ├── assets │ └── .gitkeep ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── typings.d.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /dist-server 6 | /tmp 7 | /out-tsc 8 | 9 | # dependencies 10 | /node_modules 11 | /frontend/node_modules 12 | /backend/node_modules 13 | 14 | # IDEs and editors 15 | /.idea 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # IDE - VSCode 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | 30 | # misc 31 | /.sass-cache 32 | /connect.lock 33 | /coverage 34 | /libpeerconnection.log 35 | npm-debug.log 36 | yarn-error.log 37 | testem.log 38 | /typings 39 | 40 | # e2e 41 | /e2e/*.js 42 | /e2e/*.map 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | .vscode/settings.json 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # О проекте 2 | 3 | Проект в рамках курса Codedojo - Angular in Action 4 | Angular + NestJS 5 | 6 | ## Логин для доступа 7 | 8 | login: angular_ru , password: 'angular_ru' 9 | 10 | ## TODO 11 | 12 | * [x] Регистрация 13 | * [ ] API geolocation 14 | * [x] Расширить список городов (возможно API) 15 | * [ ] Возможность добавлять избранное (интеграция с сервером) 16 | * [x] Detail Card View 17 | * [x] 404 18 | * [x] svg schedule 19 | -------------------------------------------------------------------------------- /backend/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "ts-node -r tsconfig-paths/register src/main.ts" 6 | } 7 | -------------------------------------------------------------------------------- /backend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nest-typescript-starter", 3 | "version": "1.0.0", 4 | "description": "Nest TypeScript starter repository", 5 | "license": "MIT", 6 | "scripts": { 7 | "format": "prettier --write \"**/*.ts\"", 8 | "start": "ts-node -r tsconfig-paths/register src/main.ts", 9 | "start:dev": "nodemon", 10 | "prestart:prod": "rm -rf dist && tsc", 11 | "start:prod": "node dist/main.js", 12 | "test": "jest", 13 | "test:cov": "jest --coverage", 14 | "test:e2e": "jest --config ./test/jest-e2e.json" 15 | }, 16 | "dependencies": { 17 | "@nestjs/common": "^4.5.9", 18 | "@nestjs/core": "^4.5.10", 19 | "@nestjs/microservices": "^4.5.8", 20 | "@nestjs/testing": "^4.5.5", 21 | "@nestjs/websockets": "^4.5.8", 22 | "reflect-metadata": "^0.1.12", 23 | "rxjs": "^5.5.6", 24 | "typescript": "^2.6.2" 25 | }, 26 | "devDependencies": { 27 | "@types/express": "^4.0.39", 28 | "@types/jest": "^21.1.8", 29 | "@types/node": "^9.3.0", 30 | "@types/supertest": "^2.0.4", 31 | "jest": "^21.2.1", 32 | "nodemon": "^1.17.3", 33 | "prettier": "^1.11.1", 34 | "supertest": "^3.0.0", 35 | "ts-jest": "^21.2.4", 36 | "ts-node": "^4.1.0", 37 | "tsconfig-paths": "^3.1.1", 38 | "tslint": "5.3.2" 39 | }, 40 | "jest": { 41 | "moduleFileExtensions": ["js", "json", "ts"], 42 | "rootDir": "src", 43 | "testRegex": ".spec.ts$", 44 | "transform": { 45 | "^.+\\.(t|j)s$": "ts-jest" 46 | }, 47 | "coverageDirectory": "../coverage" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /backend/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | 3 | @Controller() 4 | export class AppController { 5 | @Get() 6 | checkBackend() { 7 | return 'NestJs is Work'; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ToggleFavController } from 'fav/fav.controller'; 3 | import { LoginController } from 'login/login.controller'; 4 | import { LoginService } from 'login/login.service'; 5 | import { RegistrationController } from 'registration/registration.controller'; 6 | import { RegistrationService } from 'registration/registration.service'; 7 | import { AppController } from './app.controller'; 8 | 9 | @Module({ 10 | imports: [], 11 | controllers: [AppController, LoginController, RegistrationController, ToggleFavController], 12 | components: [LoginService, RegistrationService] 13 | }) 14 | export class AppModule {} 15 | -------------------------------------------------------------------------------- /backend/src/fav/fav.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, Post } from '@nestjs/common'; 2 | import { userLoginConst } from 'shared/consts/user-logins.const'; 3 | 4 | @Controller('toggleFav') 5 | export class ToggleFavController { 6 | constructor() {} 7 | 8 | @Get() 9 | checkFav() { 10 | return 'login is Work'; 11 | } 12 | 13 | @Post() 14 | async toggleFav(@Body() data) { 15 | const city = data.city; 16 | const user = data.user; 17 | 18 | const userData = userLoginConst.find(i => i.login === user); 19 | if (userData.favourites.find(i => i.title === city.title)) { 20 | userData.favourites = userData.favourites.filter(i => i.title != city.title); 21 | return userLoginConst; 22 | } else { 23 | userData.favourites.push(data.city); 24 | return userLoginConst; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backend/src/login/dto/user.dto.ts: -------------------------------------------------------------------------------- 1 | export class UserDto { 2 | readonly login: string; 3 | readonly password: string; 4 | favourites: favouritesDto[]; 5 | } 6 | 7 | export class favouritesDto { 8 | woeid: number; 9 | title: string; 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/login/login.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, HttpException, HttpStatus, Post } from '@nestjs/common'; 2 | import { UserDto } from './dto/user.dto'; 3 | import { LoginService } from './login.service'; 4 | 5 | @Controller('login') 6 | export class LoginController { 7 | constructor(private readonly loginService: LoginService) {} 8 | 9 | @Get() 10 | checkLogin() { 11 | return 'login is Work'; 12 | } 13 | 14 | @Post() 15 | async login(@Body() user: UserDto) { 16 | const result = this.loginService.checkUser(user); 17 | if (result) { 18 | return { 19 | user: result.login, 20 | favourites: result.favourites 21 | }; 22 | } else { 23 | throw new HttpException('Incorrect login or password', HttpStatus.UNAUTHORIZED); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/login/login.service.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@nestjs/common'; 2 | import { UserDto } from './dto/user.dto'; 3 | import { userLoginConst } from '../shared/consts/user-logins.const'; 4 | @Component() 5 | export class LoginService { 6 | public checkUser(user: UserDto) { 7 | const result = userLoginConst.find(i => i.login === user.login && i.password === user.password); 8 | return result; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | await app.listen(3000); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /backend/src/registration/registration.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Get, HttpCode, HttpStatus, Post, Res } from '@nestjs/common'; 2 | 3 | import { HttpException } from '@nestjs/common'; 4 | import { UserDto } from '../login/dto/user.dto'; 5 | import { RegistrationService } from './registration.service'; 6 | 7 | @Controller('registration') 8 | export class RegistrationController { 9 | constructor(private readonly registrationService: RegistrationService) {} 10 | 11 | @Get() 12 | checkLogin() { 13 | return 'registration is Work'; 14 | } 15 | 16 | @Post() 17 | async registration(@Body() user: UserDto) { 18 | if (this.registrationService.addUser(user)) { 19 | return user; 20 | } 21 | throw new HttpException('Incorrect login or password', HttpStatus.UNAUTHORIZED); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /backend/src/registration/registration.service.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@nestjs/common'; 2 | import { userLoginConst } from '../shared/consts/user-logins.const'; 3 | 4 | @Component() 5 | export class RegistrationService { 6 | public addUser(user): boolean { 7 | if (this.canBeRegistration(user)) { 8 | userLoginConst.push({ login: user.login, password: user.password }); 9 | return true; 10 | } 11 | return false; 12 | } 13 | 14 | private canBeRegistration(user): boolean { 15 | if (userLoginConst.find(i => i.login === user.login)) { 16 | return false; 17 | } 18 | return true; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/shared/consts/user-logins.const.ts: -------------------------------------------------------------------------------- 1 | import { UserDto } from 'login/dto/user.dto'; 2 | 3 | export const userLoginConst: UserDto[] = [ 4 | { 5 | login: 'admin', 6 | password: 'root', 7 | favourites: [ 8 | { 9 | woeid: 2122265, 10 | title: 'Moscow' 11 | }, 12 | { 13 | woeid: 2123260, 14 | title: 'St Petersburg' 15 | } 16 | ] 17 | } 18 | ]; 19 | -------------------------------------------------------------------------------- /backend/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import request from 'supertest'; 2 | import { Test } from '@nestjs/testing'; 3 | import { AppModule } from './../src/app.module'; 4 | import { INestApplication } from '@nestjs/common'; 5 | 6 | describe('AppController (e2e)', () => { 7 | let app: INestApplication; 8 | 9 | beforeAll(async () => { 10 | const moduleFixture = await Test.createTestingModule({ 11 | imports: [AppModule], 12 | }).compile(); 13 | 14 | app = moduleFixture.createNestApplication(); 15 | await app.init(); 16 | }); 17 | 18 | it('/GET /', () => { 19 | return request(app.getHttpServer()) 20 | .get('/') 21 | .expect(200) 22 | .expect('Hello World!'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /backend/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testRegex": ".e2e-spec.ts$", 5 | "transform": { 6 | "^.+\\.(t|j)s$": "ts-jest" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /backend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": false, 5 | "noImplicitAny": false, 6 | "removeComments": true, 7 | "noLib": false, 8 | "allowSyntheticDefaultImports": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es6", 12 | "sourceMap": true, 13 | "allowJs": true, 14 | "outDir": "./dist", 15 | "baseUrl": "./src" 16 | }, 17 | "include": [ 18 | "src/**/*" 19 | ], 20 | "exclude": [ 21 | "node_modules", 22 | "**/*.spec.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /backend/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["node_modules/codelyzer"], 3 | "rules": { 4 | "arrow-return-shorthand": true, 5 | "callable-types": true, 6 | "class-name": true, 7 | "comment-format": [true, "check-space"], 8 | "curly": true, 9 | "deprecation": { 10 | "severity": "warn" 11 | }, 12 | "eofline": true, 13 | "forin": true, 14 | "import-blacklist": [true, "rxjs", "rxjs/Rx"], 15 | "import-spacing": true, 16 | "indent": [true, "spaces"], 17 | "interface-over-type-literal": true, 18 | "label-position": true, 19 | "max-line-length": [true, 140], 20 | "member-access": false, 21 | "member-ordering": [ 22 | true, 23 | { 24 | "order": ["static-field", "instance-field", "static-method", "instance-method"] 25 | } 26 | ], 27 | "no-arg": true, 28 | "no-bitwise": true, 29 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 30 | "no-construct": true, 31 | "no-debugger": true, 32 | "no-duplicate-super": true, 33 | "no-empty": false, 34 | "no-empty-interface": true, 35 | "no-eval": true, 36 | "no-inferrable-types": [true, "ignore-params"], 37 | "no-misused-new": true, 38 | "no-non-null-assertion": true, 39 | "no-shadowed-variable": true, 40 | "no-string-literal": false, 41 | "no-string-throw": true, 42 | "no-switch-case-fall-through": true, 43 | "no-trailing-whitespace": true, 44 | "no-unnecessary-initializer": true, 45 | "no-unused-expression": true, 46 | "no-use-before-declare": true, 47 | "no-var-keyword": true, 48 | "object-literal-sort-keys": false, 49 | "one-line": [true, "check-open-brace", "check-catch", "check-else", "check-whitespace"], 50 | "prefer-const": true, 51 | "quotemark": [true, "single"], 52 | "radix": true, 53 | "semicolon": [true, "always"], 54 | "triple-equals": [true, "allow-null-check"], 55 | "typedef-whitespace": [ 56 | true, 57 | { 58 | "call-signature": "nospace", 59 | "index-signature": "nospace", 60 | "parameter": "nospace", 61 | "property-declaration": "nospace", 62 | "variable-declaration": "nospace" 63 | } 64 | ], 65 | "unified-signatures": true, 66 | "variable-name": false, 67 | "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"], 68 | "directive-selector": [true, "attribute", "app", "camelCase"], 69 | "component-selector": [true, "element", "app", "kebab-case"], 70 | "no-output-on-prefix": true, 71 | "use-input-property-decorator": true, 72 | "use-output-property-decorator": true, 73 | "use-host-property-decorator": true, 74 | "no-input-rename": true, 75 | "no-output-rename": true, 76 | "use-life-cycle-interface": true, 77 | "use-pipe-transform-interface": true, 78 | "component-class-suffix": true, 79 | "directive-class-suffix": true 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /frontend/.angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "project": { 4 | "name": "angular-weather-app" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": ["assets", "favicon.ico"], 11 | "index": "index.html", 12 | "main": "main.ts", 13 | "polyfills": "polyfills.ts", 14 | "test": "test.ts", 15 | "tsconfig": "tsconfig.app.json", 16 | "testTsconfig": "tsconfig.spec.json", 17 | "prefix": "app", 18 | "styles": ["styles.scss", "../node_modules/font-awesome/scss/font-awesome.scss"], 19 | "scripts": [], 20 | "environmentSource": "environments/environment.ts", 21 | "environments": { 22 | "dev": "environments/environment.ts", 23 | "prod": "environments/environment.prod.ts" 24 | } 25 | } 26 | ], 27 | "e2e": { 28 | "protractor": { 29 | "config": "./protractor.conf.js" 30 | } 31 | }, 32 | "lint": [ 33 | { 34 | "project": "src/tsconfig.app.json", 35 | "exclude": "**/node_modules/**" 36 | }, 37 | { 38 | "project": "src/tsconfig.spec.json", 39 | "exclude": "**/node_modules/**" 40 | }, 41 | { 42 | "project": "e2e/tsconfig.e2e.json", 43 | "exclude": "**/node_modules/**" 44 | } 45 | ], 46 | "test": { 47 | "karma": { 48 | "config": "./karma.conf.js" 49 | } 50 | }, 51 | "defaults": { 52 | "styleExt": "scss", 53 | "component": {} 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /frontend/e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('angular-weather-app App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /frontend/e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": [ 9 | "jasmine", 10 | "jasminewd2", 11 | "node" 12 | ] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/jest-config/globalMocks.ts: -------------------------------------------------------------------------------- 1 | const mock = () => { 2 | let storage = {}; 3 | return { 4 | getItem: key => key in storage ? storage[key] : null, 5 | setItem: (key, value) => storage[key] = value || '', 6 | removeItem: key => delete storage[key], 7 | clear: () => storage = {}, 8 | }; 9 | }; 10 | 11 | Object.defineProperty(window, 'localStorage', { 12 | value: mock() 13 | }); 14 | Object.defineProperty(window, 'sessionStorage', { 15 | value: mock() 16 | }); 17 | Object.defineProperty(window, 'getComputedStyle', { 18 | value: () => ['-webkit-appearance'] 19 | }); 20 | -------------------------------------------------------------------------------- /frontend/jest-config/setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | import './globalMocks'; 3 | -------------------------------------------------------------------------------- /frontend/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular/cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular/cli/plugins/karma') 14 | ], 15 | client:{ 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | reports: [ 'html', 'lcovonly' ], 20 | fixWebpackSourcePaths: true 21 | }, 22 | angularCli: { 23 | environment: 'dev' 24 | }, 25 | reporters: ['progress', 'kjhtml'], 26 | port: 9876, 27 | colors: true, 28 | logLevel: config.LOG_INFO, 29 | autoWatch: true, 30 | browsers: ['Chrome'], 31 | singleRun: false 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-weather-app", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve --proxy-config proxy.conf.json", 8 | "build": "ng build --prod", 9 | "test": "jest", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/animations": "^5.2.9", 16 | "@angular/cdk": "^5.2.4", 17 | "@angular/common": "^5.2.0", 18 | "@angular/compiler": "^5.2.0", 19 | "@angular/core": "^5.2.0", 20 | "@angular/forms": "^5.2.0", 21 | "@angular/http": "^5.2.0", 22 | "@angular/material": "^5.2.4", 23 | "@angular/platform-browser": "^5.2.0", 24 | "@angular/platform-browser-dynamic": "^5.2.0", 25 | "@angular/router": "^5.2.0", 26 | "core-js": "^2.4.1", 27 | "font-awesome": "^4.7.0", 28 | "moment": "^2.22.0", 29 | "rxjs": "^5.5.8", 30 | "zone.js": "^0.8.19" 31 | }, 32 | "devDependencies": { 33 | "@angular/cli": "^1.7.3", 34 | "@angular/compiler-cli": "^5.2.0", 35 | "@angular/language-service": "^5.2.0", 36 | "@types/jasmine": "~2.8.3", 37 | "@types/jasminewd2": "~2.0.2", 38 | "@types/jest": "^22.2.2", 39 | "@types/node": "~6.0.60", 40 | "codelyzer": "^4.0.1", 41 | "jasmine-core": "~2.8.0", 42 | "jasmine-spec-reporter": "~4.2.1", 43 | "jest": "^22.4.3", 44 | "jest-preset-angular": "^5.0.0", 45 | "karma": "~2.0.0", 46 | "karma-chrome-launcher": "~2.2.0", 47 | "karma-coverage-istanbul-reporter": "^1.2.1", 48 | "karma-jasmine": "~1.1.0", 49 | "karma-jasmine-html-reporter": "^0.2.2", 50 | "protractor": "~5.1.2", 51 | "ts-jest": "^22.4.2", 52 | "ts-node": "~4.1.0", 53 | "tslint": "~5.9.1", 54 | "typescript": "~2.5.3" 55 | }, 56 | "jest": { 57 | "preset": "jest-preset-angular", 58 | "setupTestFrameworkScriptFile": "./jest-config/setup.ts" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /frontend/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './e2e/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: 'e2e/tsconfig.e2e.json' 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /frontend/proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "https://www.metaweather.com/api", 4 | "secure": false, 5 | "pathRewrite": { 6 | "^/api": "" 7 | }, 8 | "changeOrigin": true 9 | }, 10 | "/backend": { 11 | "target": "http://localhost:3000", 12 | "secure": false, 13 | "pathRewrite": { 14 | "^/backend": "" 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /frontend/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | 14 |
15 | 16 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | top: 70px; 3 | height: calc(100vh - 110px); 4 | width: 100%; 5 | position: absolute; 6 | color: rgb(0, 174, 255); 7 | font-family: 'Open Sans', sans-serif; 8 | overflow: hidden; 9 | z-index: -3; 10 | opacity: 0.3; 11 | } 12 | 13 | @import url(https://fonts.googleapis.com/css?family=opensans:500); 14 | .c { 15 | text-align: center; 16 | display: block; 17 | position: relative; 18 | width: 80%; 19 | margin: 100px auto; 20 | } 21 | 22 | ._404 { 23 | font-size: 220px; 24 | position: relative; 25 | display: inline-block; 26 | z-index: 2; 27 | height: 250px; 28 | letter-spacing: 15px; 29 | } 30 | 31 | ._1 { 32 | text-align: center; 33 | display: block; 34 | position: relative; 35 | letter-spacing: 12px; 36 | font-size: 4em; 37 | line-height: 80%; 38 | } 39 | 40 | ._2 { 41 | text-align: center; 42 | display: block; 43 | position: relative; 44 | font-size: 20px; 45 | } 46 | 47 | .text { 48 | font-size: 70px; 49 | text-align: center; 50 | position: relative; 51 | display: inline-block; 52 | margin: 19px 0px 0px 0px; 53 | /* top: 256.301px; */ 54 | z-index: 3; 55 | width: 100%; 56 | line-height: 1.2em; 57 | display: inline-block; 58 | } 59 | 60 | .btn { 61 | background-color: rgb(255, 255, 255); 62 | position: relative; 63 | display: inline-block; 64 | width: 358px; 65 | padding: 5px; 66 | z-index: 5; 67 | font-size: 25px; 68 | margin: 0 auto; 69 | color: #33cc99; 70 | text-decoration: none; 71 | margin-right: 10px; 72 | } 73 | 74 | .right { 75 | float: right; 76 | width: 60%; 77 | } 78 | 79 | .cloud { 80 | width: 350px; 81 | height: 120px; 82 | background: rgb(0, 174, 255); 83 | border-radius: 100px; 84 | -webkit-border-radius: 100px; 85 | -moz-border-radius: 100px; 86 | position: absolute; 87 | margin: 120px auto 20px; 88 | z-index: -1; 89 | transition: ease 1s; 90 | } 91 | 92 | .cloud:after, 93 | .cloud:before { 94 | content: ''; 95 | position: absolute; 96 | background: rgb(0, 174, 255); 97 | z-index: -1; 98 | } 99 | 100 | .cloud:after { 101 | width: 100px; 102 | height: 100px; 103 | top: -50px; 104 | left: 50px; 105 | border-radius: 100px; 106 | -webkit-border-radius: 100px; 107 | -moz-border-radius: 100px; 108 | } 109 | 110 | .cloud:before { 111 | width: 180px; 112 | height: 180px; 113 | top: -90px; 114 | right: 50px; 115 | border-radius: 200px; 116 | -webkit-border-radius: 200px; 117 | -moz-border-radius: 200px; 118 | } 119 | 120 | .x1 { 121 | top: -50px; 122 | left: 100px; 123 | -webkit-transform: scale(0.3); 124 | -moz-transform: scale(0.3); 125 | transform: scale(0.3); 126 | opacity: 0.9; 127 | -webkit-animation: moveclouds 15s linear infinite; 128 | -moz-animation: moveclouds 15s linear infinite; 129 | -o-animation: moveclouds 15s linear infinite; 130 | } 131 | 132 | .x1_5 { 133 | top: -80px; 134 | left: 250px; 135 | -webkit-transform: scale(0.3); 136 | -moz-transform: scale(0.3); 137 | transform: scale(0.3); 138 | -webkit-animation: moveclouds 17s linear infinite; 139 | -moz-animation: moveclouds 17s linear infinite; 140 | -o-animation: moveclouds 17s linear infinite; 141 | } 142 | 143 | .x2 { 144 | left: 250px; 145 | top: 30px; 146 | -webkit-transform: scale(0.6); 147 | -moz-transform: scale(0.6); 148 | transform: scale(0.6); 149 | opacity: 0.6; 150 | -webkit-animation: moveclouds 25s linear infinite; 151 | -moz-animation: moveclouds 25s linear infinite; 152 | -o-animation: moveclouds 25s linear infinite; 153 | } 154 | 155 | .x3 { 156 | left: 250px; 157 | bottom: -70px; 158 | -webkit-transform: scale(0.6); 159 | -moz-transform: scale(0.6); 160 | transform: scale(0.6); 161 | opacity: 0.8; 162 | -webkit-animation: moveclouds 25s linear infinite; 163 | -moz-animation: moveclouds 25s linear infinite; 164 | -o-animation: moveclouds 25s linear infinite; 165 | } 166 | 167 | .x4 { 168 | left: 470px; 169 | botttom: 20px; 170 | -webkit-transform: scale(0.75); 171 | -moz-transform: scale(0.75); 172 | transform: scale(0.75); 173 | opacity: 0.75; 174 | -webkit-animation: moveclouds 18s linear infinite; 175 | -moz-animation: moveclouds 18s linear infinite; 176 | -o-animation: moveclouds 18s linear infinite; 177 | } 178 | 179 | .x5 { 180 | left: 200px; 181 | top: 300px; 182 | -webkit-transform: scale(0.5); 183 | -moz-transform: scale(0.5); 184 | transform: scale(0.5); 185 | opacity: 0.8; 186 | -webkit-animation: moveclouds 20s linear infinite; 187 | -moz-animation: moveclouds 20s linear infinite; 188 | -o-animation: moveclouds 20s linear infinite; 189 | } 190 | 191 | @-webkit-keyframes moveclouds { 192 | 0% { 193 | margin-left: 1000px; 194 | } 195 | 100% { 196 | margin-left: -1000px; 197 | } 198 | } 199 | 200 | @-moz-keyframes moveclouds { 201 | 0% { 202 | margin-left: 1000px; 203 | } 204 | 100% { 205 | margin-left: -1000px; 206 | } 207 | } 208 | 209 | @-o-keyframes moveclouds { 210 | 0% { 211 | margin-left: 1000px; 212 | } 213 | 100% { 214 | margin-left: -1000px; 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /frontend/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { CurrentUserStoreService } from './core/store/current-user.service'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.scss'] 8 | }) 9 | export class AppComponent implements OnInit { 10 | title = 'app'; 11 | 12 | constructor(private currentUserService: CurrentUserStoreService) {} 13 | 14 | ngOnInit(): void {} 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 4 | 5 | import { AppComponent } from './app.component'; 6 | import { CoreModule } from './core/core.module'; 7 | import { MainModule } from './main/main.module'; 8 | import { MainComponent } from './main/main.component'; 9 | import { SharedModule } from './shared/shared.module'; 10 | import { RouterModule, Routes } from '@angular/router'; 11 | import { WeatherViewComponent } from './main/weather-view/weather-view.component'; 12 | import { WeekViewComponent } from './main/weather-view/week-view/week-view.component'; 13 | import { DetailViewComponent } from './main/weather-view/detail-view/detail-view.component'; 14 | import { appRoutes } from './core/routes/app-routes'; 15 | import { HttpClientModule } from '@angular/common/http'; 16 | import { AuthGuard } from './core/routes/auth.guard'; 17 | import { NotFoundModule } from './not-found/not-found.module'; 18 | 19 | @NgModule({ 20 | declarations: [AppComponent], 21 | imports: [ 22 | BrowserModule, 23 | RouterModule.forRoot(appRoutes), 24 | BrowserAnimationsModule, 25 | CoreModule, 26 | MainModule, 27 | HttpClientModule, 28 | NotFoundModule 29 | ], 30 | providers: [AuthGuard], 31 | bootstrap: [AppComponent] 32 | }) 33 | export class AppModule {} 34 | -------------------------------------------------------------------------------- /frontend/src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { LayoutModule } from './layout/layout.module'; 4 | import { SharedModule } from '../shared/shared.module'; 5 | import { FavouritesModule } from './favourites/favourites.module'; 6 | import { UserFeaturesModule } from './user-features/user-features.module'; 7 | 8 | @NgModule({ 9 | imports: [CommonModule, LayoutModule, FavouritesModule, UserFeaturesModule], 10 | exports: [LayoutModule, FavouritesModule, FavouritesModule, UserFeaturesModule], 11 | declarations: [] 12 | }) 13 | export class CoreModule {} 14 | -------------------------------------------------------------------------------- /frontend/src/app/core/favourites/favourites.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 8 | 9 | 12 | {{ city.title }} 13 | 14 | 15 | 16 | 17 | 18 |

избранные города

19 | 22 | {{city.title}} 23 | 24 |
25 |
-------------------------------------------------------------------------------- /frontend/src/app/core/favourites/favourites.component.scss: -------------------------------------------------------------------------------- 1 | .input__wrapper { 2 | width: 80%; 3 | margin: auto; 4 | margin-top: 20px; 5 | box-shadow: 0px 0px 8px 0px black; 6 | height: 94%; 7 | box-sizing: border-box; 8 | padding: 30px; 9 | border-radius: 15px; 10 | text-align: center; 11 | } 12 | .input-city { 13 | width: 100%; 14 | } 15 | .list__item { 16 | cursor: pointer; 17 | } 18 | .fav__item { 19 | cursor: pointer; 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/app/core/favourites/favourites.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, OnInit, Output } from '@angular/core'; 2 | import { FormControl } from '@angular/forms'; 3 | import { Subscription } from 'rxjs/Subscription'; 4 | import { filter, switchMap } from 'rxjs/operators'; 5 | import { CitiesModel } from '../../main/shared/models/cities.model'; 6 | import { FavouritesCity } from '../../main/shared/models/user.model'; 7 | import { HttpService } from '../services/http.service'; 8 | import { CurrentUserStoreService } from '../store/current-user.service'; 9 | @Component({ 10 | selector: 'app-favourites', 11 | templateUrl: './favourites.component.html', 12 | styleUrls: ['./favourites.component.scss'] 13 | }) 14 | export class FavouritesComponent implements OnInit { 15 | selectedCity = new FormControl(); 16 | @Output() choose: EventEmitter = new EventEmitter(); 17 | subscription: Subscription; 18 | public allCities: CitiesModel[]; 19 | favs: FavouritesCity[]; 20 | constructor(private http: HttpService, private currentUserStoreService: CurrentUserStoreService) {} 21 | 22 | ngOnInit() { 23 | this.selectedCity.valueChanges 24 | .pipe(filter(i => i.length > 0), switchMap((data: string) => this.http.getAllCities(data))) 25 | .subscribe(res => (this.allCities = res)); 26 | 27 | this.currentUserStoreService.getuser().subscribe(res => { 28 | this.favs = res.favourites; 29 | }); 30 | } 31 | /** Выбрать город */ 32 | public chooseCity(city: CitiesModel): void { 33 | this.choose.emit(city); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/app/core/favourites/favourites.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FavouritesComponent } from './favourites.component'; 4 | import { SharedModule } from '../../shared/shared.module'; 5 | import { ReactiveFormsModule } from '@angular/forms'; 6 | 7 | @NgModule({ 8 | imports: [CommonModule, SharedModule, ReactiveFormsModule], 9 | declarations: [FavouritesComponent], 10 | exports: [FavouritesComponent] 11 | }) 12 | export class FavouritesModule {} 13 | -------------------------------------------------------------------------------- /frontend/src/app/core/layout/footer/footer.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/core/layout/footer/footer.component.scss: -------------------------------------------------------------------------------- 1 | .footer__wrapper { 2 | box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12); 3 | background: #3f51b5; 4 | position: fixed; 5 | bottom: 0; 6 | left: 0; 7 | right: 0; 8 | z-index: 2; 9 | color: white; 10 | font-size: 24px; 11 | padding: 10px; 12 | text-align: right; 13 | } 14 | a { 15 | text-decoration: none; 16 | color: white; 17 | font-size: 14px; 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/app/core/layout/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-footer', 5 | templateUrl: './footer.component.html', 6 | styleUrls: ['./footer.component.scss'] 7 | }) 8 | export class FooterComponent {} 9 | -------------------------------------------------------------------------------- /frontend/src/app/core/layout/header/header.component.html: -------------------------------------------------------------------------------- 1 |
2 |
Angular Weather Application
3 | 4 |
5 | 23 |
-------------------------------------------------------------------------------- /frontend/src/app/core/layout/header/header.component.scss: -------------------------------------------------------------------------------- 1 | .header__wrapper { 2 | background: #3f51b5; 3 | position: fixed; 4 | top: 0; 5 | left: 0; 6 | right: 0; 7 | z-index: 2; 8 | color: #e0e0e0; 9 | font-size: 24px; 10 | padding: 20px; 11 | text-align: center; 12 | border-radius: 0 0 5px 5px; 13 | display: flex; 14 | padding-left: 40px; 15 | height: 70px; 16 | box-sizing: border-box; 17 | } 18 | 19 | .header__title { 20 | margin-right: 15px; 21 | letter-spacing: 0.4px; 22 | color: white; 23 | } 24 | .login_feature { 25 | position: absolute; 26 | right: 40px; 27 | } 28 | .user, 29 | .not-log { 30 | display: flex; 31 | } 32 | .login { 33 | margin-right: 15px; 34 | cursor: pointer; 35 | color: white; 36 | } 37 | .logout { 38 | cursor: pointer; 39 | } 40 | .registration { 41 | margin-right: 15px; 42 | cursor: pointer; 43 | } 44 | .active-link { 45 | color: white; 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/app/core/layout/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { UserModel } from '../../../main/shared/models/user.model'; 5 | import { CurrentUserStoreService } from '../../store/current-user.service'; 6 | 7 | @Component({ 8 | selector: 'app-header', 9 | templateUrl: './header.component.html', 10 | styleUrls: ['./header.component.scss'] 11 | }) 12 | export class HeaderComponent implements OnInit { 13 | public user$: Observable; 14 | constructor(private currentUserStoreService: CurrentUserStoreService, private router: Router) {} 15 | 16 | ngOnInit() { 17 | this.user$ = this.currentUserStoreService.getuser(); 18 | } 19 | 20 | public logout(): void { 21 | this.currentUserStoreService.reset(); 22 | localStorage.clear(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/app/core/layout/layout.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HeaderComponent } from './header/header.component'; 4 | import { FooterComponent } from './footer/footer.component'; 5 | import { CoreModule } from '../core.module'; 6 | import { SharedModule } from '../../shared/shared.module'; 7 | import { CurrentUserStoreService } from '../store/current-user.service'; 8 | import { RouterModule } from '@angular/router'; 9 | 10 | @NgModule({ 11 | imports: [CommonModule, SharedModule, RouterModule], 12 | declarations: [HeaderComponent, FooterComponent], 13 | exports: [HeaderComponent, FooterComponent], 14 | providers: [CurrentUserStoreService] 15 | }) 16 | export class LayoutModule {} 17 | -------------------------------------------------------------------------------- /frontend/src/app/core/routes/app-routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | import { MainComponent } from '../../main/main.component'; 3 | import { DetailViewComponent } from '../../main/weather-view/detail-view/detail-view.component'; 4 | import { WeatherViewComponent } from '../../main/weather-view/weather-view.component'; 5 | import { WeekViewComponent } from '../../main/weather-view/week-view/week-view.component'; 6 | import { LoginComponent } from '../user-features/login/login.component'; 7 | import { AuthGuard } from './auth.guard'; 8 | import { RegistrationComponent } from '../user-features/registration/registration.component'; 9 | import { NotFoundComponent } from '../../not-found/not-found.component'; 10 | 11 | const ViewRoutes: Routes = [ 12 | { path: '', component: WeekViewComponent }, 13 | { path: 'detail/:date', component: DetailViewComponent } 14 | ]; 15 | const weatherRouters: Routes = [ 16 | { 17 | path: '', 18 | component: WeatherViewComponent, 19 | children: ViewRoutes 20 | } 21 | ]; 22 | 23 | export const appRoutes: Routes = [ 24 | { path: '', redirectTo: 'login', pathMatch: 'full' }, 25 | { path: 'login', component: LoginComponent }, 26 | { 27 | path: 'registration', 28 | component: RegistrationComponent 29 | }, 30 | { path: 'weather', component: MainComponent, children: weatherRouters, canActivate: [AuthGuard] }, 31 | { path: '**', component: NotFoundComponent } 32 | ]; 33 | -------------------------------------------------------------------------------- /frontend/src/app/core/routes/auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { CurrentUserStoreService } from '../store/current-user.service'; 5 | 6 | @Injectable() 7 | export class AuthGuard implements CanActivate { 8 | user: string; 9 | constructor(private currentUserStoreService: CurrentUserStoreService, private router: Router) {} 10 | canActivate( 11 | next: ActivatedRouteSnapshot, 12 | state: RouterStateSnapshot 13 | ): Observable | Promise | boolean { 14 | this.currentUserStoreService.getuser().subscribe(res => (this.user = res)); 15 | if (!!this.user) { 16 | return true; 17 | } 18 | this.router.navigate(['/login']); 19 | return false; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/app/core/services/http.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { UserModel } from '../../main/shared/models/user.model'; 5 | import { Urls } from '../urls/urls.const'; 6 | 7 | @Injectable() 8 | export class HttpService { 9 | constructor(private http: HttpClient) {} 10 | 11 | public getWeather(id: number): Observable { 12 | const url = Urls.getCurrentWeather(id); 13 | return this.http.get(url); 14 | } 15 | 16 | public getAllCities(query: string): Observable { 17 | const url = Urls.getAllCities(query); 18 | return this.http.get(url); 19 | } 20 | 21 | public login(data: UserModel): Observable { 22 | const url = 'http://localhost:4200/backend/login'; 23 | return this.http.post(url, data); 24 | } 25 | 26 | public registration(data: UserModel): Observable { 27 | const url = Urls.registration(); 28 | return this.http.post(url, data); 29 | } 30 | 31 | public toggleFav(user, city): Observable { 32 | const url = Urls.toggleFav(); 33 | const data = { 34 | user: user, 35 | city: city 36 | }; 37 | return this.http.post(url, data); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/app/core/store/cities-store.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { CitiesModel } from '../../main/shared/models/cities.model'; 5 | 6 | @Injectable() 7 | export class CitiesStoreService { 8 | private subject = new BehaviorSubject(null); 9 | 10 | public getCities(): Observable { 11 | return this.subject; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/app/core/store/current-city-store.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { CitiesModel } from '../../main/shared/models/cities.model'; 5 | import { HttpService } from '../services/http.service'; 6 | 7 | @Injectable() 8 | export class CurrentCityStoreService { 9 | private subject = new BehaviorSubject(null); 10 | constructor(private httpService: HttpService) {} 11 | 12 | public setCity(city: CitiesModel) { 13 | this.reset(); 14 | this.httpService.getWeather(city.woeid).subscribe(res => { 15 | this.subject.next(res); 16 | }); 17 | } 18 | public reset(): void { 19 | this.subject.next(null); 20 | } 21 | public getCity(): Observable { 22 | return this.subject; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/app/core/store/current-user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { UserModel } from '../../main/shared/models/user.model'; 5 | 6 | @Injectable() 7 | export class CurrentUserStoreService { 8 | private subject = new BehaviorSubject(null); 9 | 10 | public getuser(): Observable { 11 | return this.subject; 12 | } 13 | public reset(): void { 14 | this.subject.next(null); 15 | } 16 | public setUser(user: UserModel) { 17 | this.reset(); 18 | this.subject.next(user); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/app/core/urls/urls.const.ts: -------------------------------------------------------------------------------- 1 | export class Urls { 2 | /** Тестовый url nestJs */ 3 | public static TestNestJs(): string { 4 | const result = `/backend`; 5 | return result; 6 | } 7 | 8 | public static getCurrentWeather(id: number): string { 9 | const result = `/api/location/${id}/`; 10 | return result; 11 | } 12 | 13 | public static getAllCities(query: string): string { 14 | const result = `/api/location/search/?query=${query}`; 15 | return result; 16 | } 17 | 18 | public static registration(): string { 19 | const result = 'backend/registration'; 20 | return result; 21 | } 22 | 23 | public static login(): string { 24 | const result = '/backend/login'; 25 | return result; 26 | } 27 | 28 | public static toggleFav(): string { 29 | const result = '/backend/toggleFav'; 30 | return result; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/app/core/user-features/login/login.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/core/user-features/login/login.component.scss: -------------------------------------------------------------------------------- 1 | .login__wrapper { 2 | display: flex; 3 | position: absolute; 4 | top: 70px; 5 | height: calc(100vh - 110px); 6 | width: 100%; 7 | align-items: center; 8 | justify-content: center; 9 | } 10 | .login_card { 11 | height: 300px; 12 | text-align: center; 13 | font-size: 16px; 14 | display: flex; 15 | flex-direction: column; 16 | padding: 50px; 17 | width: 200px; 18 | } 19 | .notification { 20 | margin-top: 20px; 21 | color: red; 22 | text-align: center; 23 | font-size: 15px; 24 | } 25 | .password { 26 | margin-bottom: 40px; 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/app/core/user-features/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl, Validators } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | import { HttpService } from '../../services/http.service'; 5 | import { CurrentUserStoreService } from '../../store/current-user.service'; 6 | 7 | @Component({ 8 | selector: 'app-login', 9 | templateUrl: './login.component.html', 10 | styleUrls: ['./login.component.scss'] 11 | }) 12 | export class LoginComponent implements OnInit { 13 | public login: FormControl = new FormControl('', Validators.required); 14 | public password: FormControl = new FormControl('', Validators.required); 15 | notification: string; 16 | constructor( 17 | private httpService: HttpService, 18 | private currentUserStoreService: CurrentUserStoreService, 19 | private router: Router 20 | ) {} 21 | 22 | ngOnInit() { 23 | this.notification = null; 24 | } 25 | public auth(): void { 26 | this.httpService.login({ login: this.login.value, password: this.password.value, favourites: null }).subscribe( 27 | res => { 28 | this.currentUserStoreService.setUser(res); 29 | localStorage.setItem('user', res.user); 30 | this.router.navigate(['/weather']); 31 | }, 32 | err => { 33 | this.notification = 'Ошибка авторизации.'; 34 | } 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/app/core/user-features/registration/registration.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 9 | 10 |
11 |
12 | 13 | 18 | 19 |
20 |
21 | 25 |
26 |
{{notification}}
29 |
30 |
-------------------------------------------------------------------------------- /frontend/src/app/core/user-features/registration/registration.component.scss: -------------------------------------------------------------------------------- 1 | .registration__wrapper { 2 | display: flex; 3 | position: absolute; 4 | top: 70px; 5 | height: calc(100vh - 110px); 6 | width: 100%; 7 | align-items: center; 8 | justify-content: center; 9 | } 10 | .registration_card { 11 | height: 300px; 12 | text-align: center; 13 | font-size: 16px; 14 | display: flex; 15 | flex-direction: column; 16 | padding: 50px; 17 | width: 200px; 18 | } 19 | .notification { 20 | margin-top: 20px; 21 | text-align: center; 22 | font-size: 15px; 23 | color: red; 24 | } 25 | .notification_success { 26 | color: rgb(0, 255, 42); 27 | } 28 | .password { 29 | margin-bottom: 40px; 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/app/core/user-features/registration/registration.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl, Validators } from '@angular/forms'; 3 | import { Router } from '@angular/router'; 4 | import { HttpService } from '../../services/http.service'; 5 | 6 | @Component({ 7 | selector: 'app-registration', 8 | templateUrl: './registration.component.html', 9 | styleUrls: ['./registration.component.scss'] 10 | }) 11 | export class RegistrationComponent implements OnInit { 12 | public registration_login: FormControl = new FormControl('', Validators.required); 13 | public registration_password: FormControl = new FormControl('', Validators.required); 14 | notification: string; 15 | isButtonDisabled: boolean; 16 | registrationDone: boolean; 17 | constructor(private httpService: HttpService, private router: Router) {} 18 | 19 | ngOnInit() { 20 | this.isButtonDisabled = false; 21 | } 22 | public registration(): void { 23 | const user = { 24 | login: this.registration_login.value, 25 | password: this.registration_password.value, 26 | favourites: null 27 | }; 28 | this.httpService.registration(user).subscribe( 29 | res => { 30 | this.isButtonDisabled = true; 31 | this.registrationDone = true; 32 | this.notification = 'Вы успешно зарегистрированы'; 33 | setTimeout(() => { 34 | this.router.navigate(['login']); 35 | }, 1000); 36 | }, 37 | err => { 38 | this.notification = 'Пользователь уже существует в системе'; 39 | this.registrationDone = false; 40 | } 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/app/core/user-features/user-features.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { LoginComponent } from './login/login.component'; 4 | import { RouterModule } from '@angular/router'; 5 | import { SharedModule } from '../../shared/shared.module'; 6 | import { ReactiveFormsModule } from '@angular/forms'; 7 | import { HttpService } from '../services/http.service'; 8 | import { CurrentUserStoreService } from '../store/current-user.service'; 9 | import { RegistrationComponent } from './registration/registration.component'; 10 | 11 | @NgModule({ 12 | imports: [CommonModule, RouterModule, SharedModule, ReactiveFormsModule], 13 | declarations: [LoginComponent, RegistrationComponent], 14 | exports: [LoginComponent, RegistrationComponent], 15 | providers: [HttpService, CurrentUserStoreService] 16 | }) 17 | export class UserFeaturesModule {} 18 | -------------------------------------------------------------------------------- /frontend/src/app/main/history-info/history-info.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @NgModule({ 5 | imports: [ 6 | CommonModule 7 | ], 8 | declarations: [] 9 | }) 10 | export class HistoryInfoModule { } 11 | -------------------------------------------------------------------------------- /frontend/src/app/main/main.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
6 |
7 |
Выбранный город: {{currentCity.title}}
8 |
toggle
9 |
10 |
11 | 12 |
13 |
14 |
Выберите город
16 | 17 |
18 |
19 | 20 |
21 |
-------------------------------------------------------------------------------- /frontend/src/app/main/main.component.scss: -------------------------------------------------------------------------------- 1 | .main__wrapper { 2 | flex: 0.7; 3 | position: relative; 4 | } 5 | .wide { 6 | flex: 1; 7 | } 8 | .current__city { 9 | font-size: 24px; 10 | margin: 50px; 11 | height: 90%; 12 | } 13 | .city { 14 | display: flex; 15 | margin-bottom: 20px; 16 | align-items: center; 17 | } 18 | .view { 19 | height: 90%; 20 | } 21 | .city__title { 22 | margin-right: 20px; 23 | } 24 | .aside__wrapper { 25 | height: 100%; 26 | flex: 0.3; 27 | } 28 | .wrapper { 29 | display: flex; 30 | position: absolute; 31 | top: 70px; 32 | height: calc(100vh - 110px); 33 | width: 100%; 34 | } 35 | .hide__aside { 36 | position: absolute; 37 | top: 50%; 38 | right: 30%; 39 | width: 30px; 40 | height: 50px; 41 | background: grey; 42 | } 43 | .status { 44 | margin-right: 10px; 45 | } 46 | .weather { 47 | margin-right: 10px; 48 | } 49 | .fav { 50 | margin-right: 50px; 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/app/main/main.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl } from '@angular/forms'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { HttpService } from '../core/services/http.service'; 5 | import { CitiesStoreService } from '../core/store/cities-store.service'; 6 | import { CurrentCityStoreService } from '../core/store/current-city-store.service'; 7 | import { CurrentUserStoreService } from '../core/store/current-user.service'; 8 | import { CitiesModel } from './shared/models/cities.model'; 9 | 10 | @Component({ 11 | selector: 'app-main', 12 | templateUrl: './main.component.html', 13 | styleUrls: ['./main.component.scss'] 14 | }) 15 | export class MainComponent implements OnInit { 16 | currentCity: any; 17 | allCities$: Observable; 18 | selectedCity: FormControl = new FormControl(); 19 | allFavs: any[]; 20 | isFav: boolean; 21 | constructor( 22 | private citiesStoreService: CitiesStoreService, 23 | private currentCityStoreService: CurrentCityStoreService, 24 | private httpService: HttpService, 25 | private currentUser: CurrentUserStoreService, 26 | private currentUserStoreService: CurrentUserStoreService 27 | ) {} 28 | 29 | ngOnInit() {} 30 | 31 | public chooseCity(city: CitiesModel): void { 32 | this.currentCity = city; 33 | this.currentUserStoreService.getuser().subscribe(res => { 34 | this.allFavs = res.favourites; 35 | }); 36 | this.isFav = !!this.allFavs.find(i => i.title === city.title); 37 | this.currentCityStoreService.setCity(city); 38 | } 39 | 40 | private toggleFav(currentCity) { 41 | console.log(currentCity); 42 | 43 | const obj = { 44 | title: currentCity.title, 45 | woeid: currentCity.woeid 46 | }; 47 | 48 | this.currentUserStoreService.getuser().subscribe(res => (this.currentUser = res.user)); 49 | this.httpService.toggleFav(this.currentUser, obj).subscribe(res => console.log(res)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /frontend/src/app/main/main.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { MainComponent } from './main.component'; 5 | import { CoreModule } from '../core/core.module'; 6 | import { SharedModule } from '../shared/shared.module'; 7 | import { WeatherViewModule } from './weather-view/weather-view.module'; 8 | import { RouterModule } from '@angular/router'; 9 | import { CurrentCityStoreService } from '../core/store/current-city-store.service'; 10 | import { CitiesStoreService } from '../core/store/cities-store.service'; 11 | import { HttpService } from '../core/services/http.service'; 12 | 13 | @NgModule({ 14 | imports: [CommonModule, CoreModule, SharedModule, WeatherViewModule, RouterModule], 15 | 16 | declarations: [MainComponent], 17 | exports: [MainComponent], 18 | providers: [CitiesStoreService, CurrentCityStoreService, HttpService] 19 | }) 20 | export class MainModule {} 21 | -------------------------------------------------------------------------------- /frontend/src/app/main/recommendations/recommendations.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | @NgModule({ 5 | imports: [ 6 | CommonModule 7 | ], 8 | declarations: [] 9 | }) 10 | export class RecommendationsModule { } 11 | -------------------------------------------------------------------------------- /frontend/src/app/main/shared/models/cities.model.ts: -------------------------------------------------------------------------------- 1 | export class CitiesModel { 2 | title: string; 3 | time: string; 4 | sun_rise: string; 5 | sun_set: string; 6 | consolidated_weather: WeatherModel[]; 7 | timezone_name: string; 8 | parent: Parent; 9 | sources: Sources[]; 10 | location_type: string; 11 | woeid: number; 12 | latt_long: string; 13 | timezone: string; 14 | } 15 | export class WeatherModel { 16 | air_pressure: number; 17 | applicable_date: string; 18 | created: string; 19 | humidity: number; 20 | id: number; 21 | max_temp: number; 22 | min_temp: number; 23 | predictability: number; 24 | the_temp: number; 25 | visibility: number; 26 | weather_state_abbr: string; 27 | weather_state_name: string; 28 | wind_direction: number; 29 | wind_direction_compass: string; 30 | wind_speed: number; 31 | } 32 | export class Sources { 33 | title: string; 34 | slug: string; 35 | url: string; 36 | crawl_rate: number; 37 | } 38 | 39 | export class Parent { 40 | title: string; 41 | location_type: string; 42 | woeid: number; 43 | latt_long: string; 44 | } 45 | -------------------------------------------------------------------------------- /frontend/src/app/main/shared/models/user.model.ts: -------------------------------------------------------------------------------- 1 | export class UserModel { 2 | readonly login: string; 3 | readonly password: string; 4 | readonly favourites: FavouritesCity[]; 5 | } 6 | export class FavouritesCity { 7 | woeid: number; 8 | title: string; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/detail-view/detail-view.component.html: -------------------------------------------------------------------------------- 1 | 2 | {{currentDay.applicable_date | convertDate:"DD MMMM YYYY"}} 3 |
4 |
средняя температура
5 |
{{currentDay.the_temp | number:'.0-2'}}°C
6 |
максимальная температура
7 |
{{currentDay.max_temp | number:'.0-2'}}°C
8 |
минимальная температура
9 |
{{currentDay.min_temp | number:'.0-2'}}°C
10 |
влажность воздуха
11 |
{{currentDay.humidity}}%
12 |
скорость ветра
13 |
{{currentDay.wind_speed * 0.44704 | number:'.0-2'}} м/с 14 |
15 | 16 | 17 |
статус
18 |
19 |
22 |
23 | 24 |
27 |
-------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/detail-view/detail-view.component.scss: -------------------------------------------------------------------------------- 1 | .status { 2 | width: 30px; 3 | } 4 | .grid { 5 | display: grid; 6 | grid-template-columns: 50% 50%; 7 | font-size: 18px; 8 | margin-top: 20px; 9 | } 10 | .data { 11 | text-align: right; 12 | margin-bottom: 5px; 13 | } 14 | .item { 15 | width: 500px; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/detail-view/detail-view.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { CurrentCityStoreService } from '../../../core/store/current-city-store.service'; 4 | import { CitiesModel, WeatherModel } from '../../shared/models/cities.model'; 5 | 6 | @Component({ 7 | selector: 'app-detail-view', 8 | templateUrl: './detail-view.component.html', 9 | styleUrls: ['./detail-view.component.scss'] 10 | }) 11 | export class DetailViewComponent implements OnInit { 12 | currentDay: WeatherModel; 13 | currentCity: CitiesModel; 14 | city: string; 15 | date: string; 16 | constructor(private activatedRoute: ActivatedRoute, private currentCityStoreService: CurrentCityStoreService) {} 17 | 18 | ngOnInit() { 19 | this.findInfo(); 20 | } 21 | 22 | private findInfo(): void { 23 | this.getCurrentCity(); 24 | this.getActiveDate(); 25 | this.currentDay = this.currentCity.consolidated_weather.find(i => i.applicable_date === this.date); 26 | } 27 | 28 | private getCurrentCity(): void { 29 | this.currentCityStoreService.getCity().subscribe(res => (this.currentCity = res)); 30 | } 31 | 32 | private getActiveDate(): void { 33 | this.activatedRoute.params.subscribe(params => { 34 | this.date = params.date; 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/detail-view/detail-view.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { DetailViewComponent } from './detail-view.component'; 4 | import { SharedModule } from '../../../shared/shared.module'; 5 | import { RouterModule } from '@angular/router'; 6 | 7 | @NgModule({ 8 | imports: [CommonModule, SharedModule, RouterModule], 9 | exports: [DetailViewComponent], 10 | declarations: [DetailViewComponent] 11 | }) 12 | export class DetailViewModule {} 13 | -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/weather-view.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/weather-view.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Angular-RU/angular-weather-app/8a9e0ca5098ada0836fce1bd48b0fc8b01cd45b2/frontend/src/app/main/weather-view/weather-view.component.scss -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/weather-view.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-weather-view', 5 | templateUrl: './weather-view.component.html', 6 | styleUrls: ['./weather-view.component.scss'] 7 | }) 8 | export class WeatherViewComponent {} 9 | -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/weather-view.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { WeatherViewComponent } from './weather-view.component'; 4 | import { WeekViewComponent } from './week-view/week-view.component'; 5 | import { SharedModule } from '../../shared/shared.module'; 6 | import { WeekViewCardComponent } from './week-view/week-view-card/week-view-card.component'; 7 | import { DetailViewComponent } from './detail-view/detail-view.component'; 8 | import { RouterModule } from '@angular/router'; 9 | import { DetailViewModule } from './detail-view/detail-view.module'; 10 | import { WeekViewModule } from './week-view/week-view.module'; 11 | 12 | @NgModule({ 13 | imports: [RouterModule, CommonModule, SharedModule, DetailViewModule, WeekViewModule], 14 | exports: [DetailViewModule, WeekViewModule], 15 | declarations: [WeatherViewComponent] 16 | }) 17 | export class WeatherViewModule {} 18 | -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/week-view/week-graph/week-graph.component.html: -------------------------------------------------------------------------------- 1 | 3 | Дата 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | °C 12 | 13 | 14 | 15 | 16 | {{item.max_temp | number:'.0-1' }}°C 17 | {{item.min_temp | number:'.0-1' }}°C 18 | {{item.applicable_date | convertDate:"DD MMM" }} 19 | 20 | -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/week-view/week-graph/week-graph.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Angular-RU/angular-weather-app/8a9e0ca5098ada0836fce1bd48b0fc8b01cd45b2/frontend/src/app/main/weather-view/week-view/week-graph/week-graph.component.scss -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/week-view/week-graph/week-graph.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { WeatherModel } from '../../../shared/models/cities.model'; 3 | 4 | @Component({ 5 | selector: 'app-week-graph', 6 | templateUrl: './week-graph.component.html', 7 | styleUrls: ['./week-graph.component.scss'] 8 | }) 9 | export class WeekGraphComponent implements OnInit { 10 | @Input() weather: WeatherModel[]; 11 | 12 | ngOnInit() {} 13 | 14 | public get dotsMax(): string { 15 | return this.weatherToDts(this.weather, 'max_temp'); 16 | } 17 | 18 | public get dotsMin(): string { 19 | return this.weatherToDts(this.weather, 'min_temp'); 20 | } 21 | 22 | private weatherToDts(weather: any[], temp: string): string { 23 | return weather 24 | .map((day, i) => this.weatherToPoint(day, i, temp)) 25 | .map(this.pointToString) 26 | .reduce((a, point) => `${a}${point}`, '') 27 | .concat('1005,250 5,250 ', this.pointToString(this.weatherToPoint(weather[0], 0, temp))); 28 | } 29 | 30 | private weatherToPoint(w, index, temp: string) { 31 | return { 32 | x: index * (1000 / 5) + 5, 33 | y: 250 - w[temp] * 5 34 | }; 35 | } 36 | 37 | private pointToString(p) { 38 | return `${p.x},${p.y} `; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/week-view/week-view-card/week-view-card.component.html: -------------------------------------------------------------------------------- 1 | 3 | {{weather.applicable_date | convertDate:"DD MMM YYYY"}} 4 |
5 |
6 |
Макс. Темп.
7 |
{{weather.max_temp | number:'.0-0'}}°C
8 |
9 | 10 |
11 |
Мин. Темп.
12 |
{{weather.min_temp | number:'.0-0'}}°C
13 |
14 | 15 |
16 |
Состояние
17 |
18 | 21 |
22 |
23 | 24 | 25 |
26 |
-------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/week-view/week-view-card/week-view-card.component.scss: -------------------------------------------------------------------------------- 1 | .item { 2 | font-size: 24px; 3 | width: 100%; 4 | height: 80px; 5 | display: flex; 6 | box-sizing: border-box; 7 | cursor: pointer; 8 | } 9 | .row { 10 | font-size: 0.8em; 11 | display: flex; 12 | align-items: center; 13 | flex: 1; 14 | justify-content: space-around; 15 | } 16 | .col { 17 | display: flex; 18 | } 19 | .grid__title { 20 | margin-right: 10px; 21 | } 22 | .grid__item { 23 | margin-right: 20px; 24 | } 25 | .date { 26 | margin-right: 20px; 27 | } 28 | .status { 29 | width: 30px; 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/week-view/week-view-card/week-view-card.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | import { WeatherModel } from '../../../shared/models/cities.model'; 3 | 4 | @Component({ 5 | selector: 'app-week-view-card', 6 | templateUrl: './week-view-card.component.html', 7 | styleUrls: ['./week-view-card.component.scss'] 8 | }) 9 | export class WeekViewCardComponent { 10 | @Input() weather: WeatherModel; 11 | @Input() currentCity: string; 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/week-view/week-view.component.html: -------------------------------------------------------------------------------- 1 |
3 |
4 | 6 | Графики 7 | 8 |
9 |
11 | 14 |
15 |
17 | 18 | 19 | 20 |
21 |
22 |
24 | 25 |
-------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/week-view/week-view.component.scss: -------------------------------------------------------------------------------- 1 | .items__wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100%; 5 | } 6 | .loader__wrapper { 7 | width: 100%; 8 | height: 100%; 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | } 13 | .card { 14 | flex: 1; 15 | display: flex; 16 | 17 | flex-direction: column; 18 | 19 | justify-content: space-around; 20 | } 21 | .graph { 22 | height: 80%; 23 | } 24 | .slide { 25 | margin-bottom: 10px; 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/week-view/week-view.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormControl } from '@angular/forms'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { HttpService } from '../../../core/services/http.service'; 5 | import { CurrentCityStoreService } from '../../../core/store/current-city-store.service'; 6 | import { CitiesModel } from '../../shared/models/cities.model'; 7 | 8 | @Component({ 9 | selector: 'app-week-view', 10 | templateUrl: './week-view.component.html', 11 | styleUrls: ['./week-view.component.scss'] 12 | }) 13 | export class WeekViewComponent implements OnInit { 14 | currentCity$: Observable; 15 | toggle = new FormControl(); 16 | constructor(private currentCityStoreService: CurrentCityStoreService, private httpService: HttpService) {} 17 | 18 | ngOnInit() { 19 | this.currentCity$ = this.currentCityStoreService.getCity(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/app/main/weather-view/week-view/week-view.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { WeekViewComponent } from './week-view.component'; 4 | import { WeekViewCardComponent } from './week-view-card/week-view-card.component'; 5 | import { SharedModule } from '../../../shared/shared.module'; 6 | import { RouterModule } from '@angular/router'; 7 | import { ReactiveFormsModule } from '@angular/forms'; 8 | import { WeekGraphComponent } from './week-graph/week-graph.component'; 9 | 10 | @NgModule({ 11 | imports: [RouterModule, CommonModule, SharedModule, ReactiveFormsModule], 12 | exports: [WeekViewComponent, WeekViewCardComponent], 13 | declarations: [WeekViewComponent, WeekViewCardComponent, WeekGraphComponent] 14 | }) 15 | export class WeekViewModule {} 16 | -------------------------------------------------------------------------------- /frontend/src/app/not-found/not-found.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
страница
5 |
не найдена
6 |
7 |
-------------------------------------------------------------------------------- /frontend/src/app/not-found/not-found.component.scss: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=opensans:500); 2 | .wrapper { 3 | top: 70px; 4 | height: calc(100vh - 110px); 5 | width: 100%; 6 | position: absolute; 7 | color: rgb(0, 174, 255); 8 | font-family: 'Open Sans', sans-serif; 9 | overflow: hidden; 10 | z-index: 2; 11 | } 12 | 13 | .c { 14 | text-align: center; 15 | display: block; 16 | position: relative; 17 | width: 80%; 18 | margin: 100px auto; 19 | } 20 | 21 | ._1 { 22 | text-align: center; 23 | display: block; 24 | position: relative; 25 | letter-spacing: 12px; 26 | font-size: 4em; 27 | line-height: 80%; 28 | } 29 | 30 | ._2 { 31 | text-align: center; 32 | display: block; 33 | position: relative; 34 | font-size: 20px; 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/app/not-found/not-found.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-not-found', 5 | templateUrl: './not-found.component.html', 6 | styleUrls: ['./not-found.component.scss'] 7 | }) 8 | export class NotFoundComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit() { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/not-found/not-found.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NotFoundComponent } from './not-found.component'; 4 | 5 | @NgModule({ 6 | imports: [CommonModule], 7 | exports: [NotFoundComponent], 8 | declarations: [NotFoundComponent] 9 | }) 10 | export class NotFoundModule {} 11 | -------------------------------------------------------------------------------- /frontend/src/app/shared/loader/loader.component.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /frontend/src/app/shared/loader/loader.component.scss: -------------------------------------------------------------------------------- 1 | .loader { 2 | color: black; 3 | font-size: 90px; 4 | text-indent: -9999em; 5 | overflow: hidden; 6 | width: 1em; 7 | height: 1em; 8 | border-radius: 50%; 9 | margin: 72px auto; 10 | position: relative; 11 | -webkit-transform: translateZ(0); 12 | -ms-transform: translateZ(0); 13 | transform: translateZ(0); 14 | -webkit-animation: load6 1.7s infinite ease, round 1.7s infinite ease; 15 | animation: load6 1.7s infinite ease, round 1.7s infinite ease; 16 | } 17 | @-webkit-keyframes load6 { 18 | 0% { 19 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; 20 | } 21 | 5%, 22 | 95% { 23 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; 24 | } 25 | 10%, 26 | 59% { 27 | box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, 28 | -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em; 29 | } 30 | 20% { 31 | box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, 32 | -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em; 33 | } 34 | 38% { 35 | box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, 36 | -0.82em -0.09em 0 -0.477em; 37 | } 38 | 100% { 39 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; 40 | } 41 | } 42 | @keyframes load6 { 43 | 0% { 44 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; 45 | } 46 | 5%, 47 | 95% { 48 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; 49 | } 50 | 10%, 51 | 59% { 52 | box-shadow: 0 -0.83em 0 -0.4em, -0.087em -0.825em 0 -0.42em, -0.173em -0.812em 0 -0.44em, 53 | -0.256em -0.789em 0 -0.46em, -0.297em -0.775em 0 -0.477em; 54 | } 55 | 20% { 56 | box-shadow: 0 -0.83em 0 -0.4em, -0.338em -0.758em 0 -0.42em, -0.555em -0.617em 0 -0.44em, 57 | -0.671em -0.488em 0 -0.46em, -0.749em -0.34em 0 -0.477em; 58 | } 59 | 38% { 60 | box-shadow: 0 -0.83em 0 -0.4em, -0.377em -0.74em 0 -0.42em, -0.645em -0.522em 0 -0.44em, -0.775em -0.297em 0 -0.46em, 61 | -0.82em -0.09em 0 -0.477em; 62 | } 63 | 100% { 64 | box-shadow: 0 -0.83em 0 -0.4em, 0 -0.83em 0 -0.42em, 0 -0.83em 0 -0.44em, 0 -0.83em 0 -0.46em, 0 -0.83em 0 -0.477em; 65 | } 66 | } 67 | @-webkit-keyframes round { 68 | 0% { 69 | -webkit-transform: rotate(0deg); 70 | transform: rotate(0deg); 71 | } 72 | 100% { 73 | -webkit-transform: rotate(360deg); 74 | transform: rotate(360deg); 75 | } 76 | } 77 | @keyframes round { 78 | 0% { 79 | -webkit-transform: rotate(0deg); 80 | transform: rotate(0deg); 81 | } 82 | 100% { 83 | -webkit-transform: rotate(360deg); 84 | transform: rotate(360deg); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /frontend/src/app/shared/loader/loader.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-loader', 5 | templateUrl: './loader.component.html', 6 | styleUrls: ['./loader.component.scss'] 7 | }) 8 | export class LoaderComponent {} 9 | -------------------------------------------------------------------------------- /frontend/src/app/shared/material/material.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { MatButtonModule, MatCheckboxModule } from '@angular/material'; 4 | import { MatCardModule } from '@angular/material/card'; 5 | import { MatSelectModule } from '@angular/material/select'; 6 | import { MatListModule } from '@angular/material/list'; 7 | import { MatInputModule } from '@angular/material/input'; 8 | import { MatAutocompleteModule } from '@angular/material/autocomplete'; 9 | import { MatSlideToggleModule } from '@angular/material/slide-toggle'; 10 | import { MatFormFieldModule } from '@angular/material/form-field'; 11 | @NgModule({ 12 | imports: [ 13 | MatButtonModule, 14 | MatCheckboxModule, 15 | MatCardModule, 16 | MatSelectModule, 17 | MatListModule, 18 | MatInputModule, 19 | MatAutocompleteModule, 20 | MatSlideToggleModule, 21 | MatFormFieldModule, 22 | MatListModule 23 | ], 24 | exports: [ 25 | MatButtonModule, 26 | MatCheckboxModule, 27 | MatCardModule, 28 | MatSelectModule, 29 | MatListModule, 30 | MatInputModule, 31 | MatAutocompleteModule, 32 | MatSlideToggleModule, 33 | MatFormFieldModule, 34 | MatListModule 35 | ], 36 | declarations: [] 37 | }) 38 | export class MaterialModule {} 39 | -------------------------------------------------------------------------------- /frontend/src/app/shared/pipes/convert-date.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import * as moment from 'moment'; 3 | @Pipe({ 4 | name: 'convertDate' 5 | }) 6 | export class ConvertDatePipe implements PipeTransform { 7 | transform(value: any, args?: any): any { 8 | moment.locale('ru'); 9 | return moment(value).format(args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { MaterialModule } from './material/material.module'; 4 | 5 | import { ConvertDatePipe } from './pipes/convert-date.pipe'; 6 | import { LoaderComponent } from './loader/loader.component'; 7 | 8 | @NgModule({ 9 | imports: [CommonModule, MaterialModule], 10 | exports: [MaterialModule, ConvertDatePipe, LoaderComponent], 11 | declarations: [ConvertDatePipe, LoaderComponent] 12 | }) 13 | export class SharedModule {} 14 | -------------------------------------------------------------------------------- /frontend/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Angular-RU/angular-weather-app/8a9e0ca5098ada0836fce1bd48b0fc8b01cd45b2/frontend/src/assets/.gitkeep -------------------------------------------------------------------------------- /frontend/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /frontend/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /frontend/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Angular-RU/angular-weather-app/8a9e0ca5098ada0836fce1bd48b0fc8b01cd45b2/frontend/src/favicon.ico -------------------------------------------------------------------------------- /frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Angular Weather App 7 | 8 | 9 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /frontend/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/ 22 | // import 'core-js/es6/symbol'; 23 | // import 'core-js/es6/object'; 24 | // import 'core-js/es6/function'; 25 | // import 'core-js/es6/parse-int'; 26 | // import 'core-js/es6/parse-float'; 27 | // import 'core-js/es6/number'; 28 | // import 'core-js/es6/math'; 29 | // import 'core-js/es6/string'; 30 | // import 'core-js/es6/date'; 31 | // import 'core-js/es6/array'; 32 | // import 'core-js/es6/regexp'; 33 | // import 'core-js/es6/map'; 34 | // import 'core-js/es6/weak-map'; 35 | // import 'core-js/es6/set'; 36 | 37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 38 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 39 | 40 | /** IE10 and IE11 requires the following for the Reflect API. */ 41 | // import 'core-js/es6/reflect'; 42 | 43 | 44 | /** Evergreen browsers require these. **/ 45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove. 46 | import 'core-js/es7/reflect'; 47 | 48 | 49 | /** 50 | * Required to support Web Animations `@angular/platform-browser/animations`. 51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation 52 | **/ 53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 54 | 55 | /** 56 | * By default, zone.js will patch all possible macroTask and DomEvents 57 | * user can disable parts of macroTask/DomEvents patch by setting following flags 58 | */ 59 | 60 | // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 61 | // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 62 | // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 63 | 64 | /* 65 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 66 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 67 | */ 68 | // (window as any).__Zone_enable_cross_context_check = true; 69 | 70 | /*************************************************************************************************** 71 | * Zone JS is required by default for Angular itself. 72 | */ 73 | import 'zone.js/dist/zone'; // Included with Angular CLI. 74 | 75 | 76 | 77 | /*************************************************************************************************** 78 | * APPLICATION IMPORTS 79 | */ 80 | -------------------------------------------------------------------------------- /frontend/src/styles.scss: -------------------------------------------------------------------------------- 1 | @import '~@angular/material/prebuilt-themes/indigo-pink.css'; 2 | @import url('https://fonts.googleapis.com/css?family=Roboto'); 3 | body { 4 | background: #fafafa; 5 | position: relative; 6 | padding: 0; 7 | margin: 0; 8 | font-family: 'Roboto', sans-serif; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /frontend/src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "baseUrl": "./", 6 | "module": "es2015", 7 | "types": [] 8 | }, 9 | "exclude": [ 10 | "test.ts", 11 | "**/*.spec.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "baseUrl": "./", 6 | "module": "commonjs", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "test.ts" 14 | ], 15 | "include": [ 16 | "**/*.spec.ts", 17 | "**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* SystemJS module definition */ 2 | declare var module: NodeModule; 3 | interface NodeModule { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "target": "es5", 11 | "typeRoots": [ 12 | "node_modules/@types" 13 | ], 14 | "lib": [ 15 | "es2017", 16 | "dom" 17 | ] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /frontend/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": ["node_modules/codelyzer"], 3 | "rules": { 4 | "arrow-return-shorthand": true, 5 | "callable-types": true, 6 | "class-name": true, 7 | "comment-format": [true, "check-space"], 8 | "curly": true, 9 | "deprecation": { 10 | "severity": "warn" 11 | }, 12 | "eofline": true, 13 | "forin": true, 14 | "import-blacklist": [true, "rxjs", "rxjs/Rx"], 15 | "import-spacing": true, 16 | "indent": [true, "spaces"], 17 | "interface-over-type-literal": true, 18 | "label-position": true, 19 | "max-line-length": [true, 140], 20 | "member-access": false, 21 | "member-ordering": [ 22 | true, 23 | { 24 | "order": ["static-field", "instance-field", "static-method", "instance-method"] 25 | } 26 | ], 27 | "no-arg": true, 28 | "no-bitwise": true, 29 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 30 | "no-construct": true, 31 | "no-debugger": true, 32 | "no-duplicate-super": true, 33 | "no-empty": false, 34 | "no-empty-interface": true, 35 | "no-eval": true, 36 | "no-inferrable-types": [true, "ignore-params"], 37 | "no-misused-new": true, 38 | "no-non-null-assertion": true, 39 | "no-shadowed-variable": true, 40 | "no-string-literal": false, 41 | "no-string-throw": true, 42 | "no-switch-case-fall-through": true, 43 | "no-trailing-whitespace": true, 44 | "no-unnecessary-initializer": true, 45 | "no-unused-expression": true, 46 | "no-use-before-declare": true, 47 | "no-var-keyword": true, 48 | "object-literal-sort-keys": false, 49 | "one-line": [true, "check-open-brace", "check-catch", "check-else", "check-whitespace"], 50 | "prefer-const": true, 51 | "quotemark": [true, "single"], 52 | "radix": true, 53 | "semicolon": [true, "always"], 54 | "triple-equals": [true, "allow-null-check"], 55 | "typedef-whitespace": [ 56 | true, 57 | { 58 | "call-signature": "nospace", 59 | "index-signature": "nospace", 60 | "parameter": "nospace", 61 | "property-declaration": "nospace", 62 | "variable-declaration": "nospace" 63 | } 64 | ], 65 | "unified-signatures": true, 66 | "variable-name": false, 67 | "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"], 68 | "directive-selector": [true, "attribute", "app", "camelCase"], 69 | "component-selector": [true, "element", "app", "kebab-case"], 70 | "no-output-on-prefix": true, 71 | "use-input-property-decorator": true, 72 | "use-output-property-decorator": true, 73 | "use-host-property-decorator": true, 74 | "no-input-rename": true, 75 | "no-output-rename": true, 76 | "use-life-cycle-interface": true, 77 | "use-pipe-transform-interface": true, 78 | "component-class-suffix": true, 79 | "directive-class-suffix": true 80 | } 81 | } 82 | --------------------------------------------------------------------------------