diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 0000000..427441d --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,17 @@ +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries + +# For the full list of supported browsers by the Angular framework, please see: +# https://angular.io/guide/browser-support + +# You can see what browsers were selected by your queries by running: +# npx browserslist + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major versions +last 2 iOS major versions +Firefox ESR +not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..59d9a3a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..323a898 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc +# Only exists if Bazel was run +/bazel-out + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events*.json +speed-measure-plugin*.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings +*.env + +# System Files +.DS_Store +Thumbs.db + +/backend/database/ +/backend/node_modules/ + +package-lock.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..55a30a2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM node:current-slim +WORKDIR /app-frontend +COPY ["package.json", "package-lock.json*", "./"] +RUN npm install --NODE_ENV +RUN npm install -g @angular/cli +COPY . . diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..063b78f --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: npm start diff --git a/README.md b/README.md index 9ab8554..13b2e84 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,27 @@ -# PolyNotFound +# Frontend -Le projet est séparé en 3 parties : +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 11.2.7. -- 2 Frontend : - - 1 partie Administrateur - - 1 partie pour les utilisateurs et les publicitaires -- 1 Backend +## Development server -Nous avons décidé que chaque partie du projet a sa propre branche git et non leur propre répertoire git. +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. -En effet, les branches concernées par le projet est : -- Frontend partie Administrateur : `front-admin` -- Frontend partie pour les utilisateurs et les publicitaires : `front-user-advertiser` -- Backend : `backend` +## Code scaffolding +à +Run `ng generate component component-title` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. -Nous pouvons récupérer une branche git avec `git checkout `. +## Build -# Lancer le projet en Local +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. -Dans un premier temps, il est obligatoire d'avoir NodeJS (version >12 au minimum), [téléchargeable sur cette page](https://nodejs.org/en/download/). +## Running unit tests -Pour lancer le projet en local dans son ensemble, il faut impérativement avoir les branches concernées dans leur propre dossier. -Il faudra donc __clone__ le projet 3 fois. +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). -Dans un dossier nommé par exemple `Polynotfound`: -- frontend-admin : `git clone --branch frontend-admin https://github.com/NyxiumYuuki/PolyNotFound.git polynotfound-frontend-admin` -- front-user-advertiser : `git clone --branch front-user-advertiser https://github.com/NyxiumYuuki/PolyNotFound.git polynotfound-front-user-advertiser` -- backend : `git clone --branch backend https://github.com/NyxiumYuuki/PolyNotFound.git polynotfound-backend` +## Running end-to-end tests -Un README est disponible pour chaque branche pour lancer le projet en local soit en mode **production** soit en mode **développement**. +Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). -# Lancer le projet en ligne avec Heroku +## Further help -Nous avons déployé le projet en ligne avec Heroku. - -Le projet est disponible sur ces URL : -- Partie Utilisateurs et Publicitaires : https://polynotfound.herokuapp.com/ -- Partie Administrateur : https://admin-polynotfound.herokuapp.com/ -- API : https://api-polynotfound.herokuapp.com/ \ No newline at end of file +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. diff --git a/admin/.browserslistrc b/admin/.browserslistrc new file mode 100644 index 0000000..427441d --- /dev/null +++ b/admin/.browserslistrc @@ -0,0 +1,17 @@ +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries + +# For the full list of supported browsers by the Angular framework, please see: +# https://angular.io/guide/browser-support + +# You can see what browsers were selected by your queries by running: +# npx browserslist + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major versions +last 2 iOS major versions +Firefox ESR +not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. diff --git a/admin/.editorconfig b/admin/.editorconfig new file mode 100644 index 0000000..59d9a3a --- /dev/null +++ b/admin/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/admin/.gitignore b/admin/.gitignore new file mode 100644 index 0000000..de51f68 --- /dev/null +++ b/admin/.gitignore @@ -0,0 +1,45 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc +# Only exists if Bazel was run +/bazel-out + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events*.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db diff --git a/admin/README.md b/admin/README.md new file mode 100644 index 0000000..a653293 --- /dev/null +++ b/admin/README.md @@ -0,0 +1,27 @@ +# Admin + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.10. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. diff --git a/admin/angular.json b/admin/angular.json new file mode 100644 index 0000000..62b77eb --- /dev/null +++ b/admin/angular.json @@ -0,0 +1,113 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "admin": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + }, + "@schematics/angular:application": { + "strict": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/admin", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss", + "node_modules/bootstrap/scss/bootstrap.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "admin:build:production" + }, + "development": { + "browserTarget": "admin:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "admin:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "src/styles.scss" + ], + "scripts": [] + } + } + } + } + }, + "defaultProject": "admin" +} diff --git a/admin/karma.conf.js b/admin/karma.conf.js new file mode 100644 index 0000000..2023ed1 --- /dev/null +++ b/admin/karma.conf.js @@ -0,0 +1,44 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + jasmine: { + // you can add configuration options for Jasmine here + // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html + // for example, you can disable the random execution with `random: false` + // or set a specific seed with `seed: 4321` + }, + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + jasmineHtmlReporter: { + suppressAll: true // removes the duplicated traces + }, + coverageReporter: { + dir: require('path').join(__dirname, './coverage/admin'), + subdir: '.', + reporters: [ + { type: 'html' }, + { type: 'text-summary' } + ] + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true + }); +}; diff --git a/admin/package.json b/admin/package.json new file mode 100644 index 0000000..1e0e314 --- /dev/null +++ b/admin/package.json @@ -0,0 +1,44 @@ +{ + "name": "admin", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/animations": "~12.2.0", + "@angular/cdk": "^13.1.1", + "@angular/common": "~12.2.0", + "@angular/compiler": "~12.2.0", + "@angular/core": "~12.2.0", + "@angular/forms": "~12.2.0", + "@angular/material": "^13.1.1", + "@angular/platform-browser": "~12.2.0", + "@angular/platform-browser-dynamic": "~12.2.0", + "@angular/router": "~12.2.0", + "bootstrap": "^5.1.3", + "jquery": "^3.6.0", + "popper": "^1.0.1", + "rxjs": "~6.6.0", + "tslib": "^2.3.0", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "~12.2.10", + "@angular/cli": "~12.2.10", + "@angular/compiler-cli": "~12.2.0", + "@types/jasmine": "~3.8.0", + "@types/node": "^12.11.1", + "jasmine-core": "~3.8.0", + "karma": "~6.3.0", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage": "~2.0.3", + "karma-jasmine": "~4.0.0", + "karma-jasmine-html-reporter": "~1.7.0", + "typescript": "~4.3.5" + } +} diff --git a/admin/src/app/admin/adList/page-ad-list-admin/page-ad-list-admin.component.html b/admin/src/app/admin/adList/page-ad-list-admin/page-ad-list-admin.component.html new file mode 100644 index 0000000..76bc154 --- /dev/null +++ b/admin/src/app/admin/adList/page-ad-list-admin/page-ad-list-admin.component.html @@ -0,0 +1,167 @@ +
+
+ + +

+ + + + +
+ + +
+ Filtre +
+ + + + +
+ +
+ + +
+ + +
+ visible
+ non visible +
+ + +
+ + Sujets + + + {{formControlInterests.value ? formControlInterests.value[0] : ''}} + + (+{{formControlInterests.value.length - 1}} {{formControlInterests.value?.length === 2 ? 'autre' : 'autres'}}) + + + {{topping}} + + + +
+ + +
+ Période de création:   + + Date de début + + +   -   + + Date de fin + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Titre + {{advert.title}} + Entreprise + {{advert.company}} + Sujets + + {{objectInterest.interest}}, + {{objectInterest.interest}} + + Date de création + {{ advert.createdAt | date:'dd/LL/YYYY à HH:mm:ss' }} + Dernière modification + {{ advert.updatedAt | date:'dd/LL/YYYY à HH:mm:ss' }} + Vues + {{advert.countViews}} + Visible + check + + Actions + + +
Aucune vidéo ne correspond au filtre: "{{input.value}}"
+
+ +
+

+ +
+
diff --git a/admin/src/app/admin/adList/page-ad-list-admin/page-ad-list-admin.component.scss b/admin/src/app/admin/adList/page-ad-list-admin/page-ad-list-admin.component.scss new file mode 100644 index 0000000..954f3d0 --- /dev/null +++ b/admin/src/app/admin/adList/page-ad-list-admin/page-ad-list-admin.component.scss @@ -0,0 +1,74 @@ +.myContainer { + max-width: 100vw; + height: 100vh; + overflow-x: hidden; + font-size: small; +} + +// ---------------------------------------------------------- + + +.filtersContainer { + width: 80%; + background-color: white; + padding: 10px 10px 10px 10px; + margin: 20px 3% 20px 3% +} + +.myRow { + margin-left: 1%; +} + +.textFilter { + width: 50%; + font-size: medium; + border-radius: 5px; +} + +// ---------------------------------------------------------- + + +table { + margin: 0 auto; + width: 94%; + font-size: small; +} +.darkTheme table { border: solid 2px white; } + +th.mat-sort-header-sorted { + color: black; +} + +td { + font-size: small; +} + +input { + width: 30%; + font-size: large; + border-radius: 5px; +} + + +// ------------------------------------------------------------------------- + + +// aura +::ng-deep .mat-checkbox-ripple .mat-ripple-element { + background-color: grey !important; +} + +// contenu coche +::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background { + background-color: black !important; +} + +// indeterminate +::ng-deep .mat-checkbox .mat-checkbox-frame { + border: solid 1px black !important; + background-color: white !important; +} + +::ng-deep .mat-pseudo-checkbox-checked { + background-color: black !important; +} diff --git a/admin/src/app/admin/adList/page-ad-list-admin/page-ad-list-admin.component.spec.ts b/admin/src/app/admin/adList/page-ad-list-admin/page-ad-list-admin.component.spec.ts new file mode 100644 index 0000000..5b77dff --- /dev/null +++ b/admin/src/app/admin/adList/page-ad-list-admin/page-ad-list-admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageAdListAdminComponent } from './page-ad-list-admin.component'; + +describe('PageAdListAdminComponent', () => { + let component: PageAdListAdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageAdListAdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageAdListAdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/admin/adList/page-ad-list-admin/page-ad-list-admin.component.ts b/admin/src/app/admin/adList/page-ad-list-admin/page-ad-list-admin.component.ts new file mode 100644 index 0000000..a0fa87f --- /dev/null +++ b/admin/src/app/admin/adList/page-ad-list-admin/page-ad-list-admin.component.ts @@ -0,0 +1,260 @@ +import {AfterViewInit, Component, ViewChild} from '@angular/core'; +import {MatSort} from "@angular/material/sort"; +import {MatPaginator} from "@angular/material/paginator"; +import {MatDialog} from "@angular/material/dialog"; +import {MatSnackBar} from "@angular/material/snack-bar"; +import {MatTableDataSource} from "@angular/material/table"; +import {PopupDeleteAdAdminComponent} from "../popup-delete-ad-admin/popup-delete-ad-admin.component"; +import {PopupVisualizeImagesAdminComponent} from "../popup-visualize-images-admin/popup-visualize-images-admin.component"; +import {FormControl} from "@angular/forms"; +import {HttpParams} from "@angular/common/http"; +import {ThemeService} from "../../../utils/theme/theme.service"; +import {MessageService} from "../../../utils/message/message.service"; + + + +export interface AdvertWithCountViewsAndCompany { + id: string, + userId: string, + company: string, + title: string, + url: string, + images: { + url: string, + description: string, + }[], + interests: string[], + comment: string, + views: Date[], + countViews: number, + isVisible: boolean, + isActive: boolean, + createdAt: Date, + updatedAt: Date, +} + + +@Component({ + selector: 'app-page-ad-list-admin', + templateUrl: './page-ad-list-admin.component.html', + styleUrls: ['./page-ad-list-admin.component.scss'] +}) +export class PageAdListAdminComponent implements AfterViewInit +{ + tabAdvertWithCountViews: AdvertWithCountViewsAndCompany[] = []; + tabAdvertiser: any[]; + displayedColumns: string[] = [ 'title', 'company', 'interests', 'createdAt', 'updatedAt', 'countViews', 'isVisible', 'actions' ]; + dataSource ; + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + + visible: boolean = true; + noVisible: boolean = true; + startDate: Date = null; + endDate: Date = null; + formControlInterests = new FormControl(); + allInterests: string[] = []; + + + constructor( public themeService: ThemeService, + public dialog: MatDialog, + private snackBar: MatSnackBar, + private messageService: MessageService) { } + + + ngAfterViewInit(): void + { + // Ask for ads and then for advertiser + let params = new HttpParams(); + params = params.append("isActive", true); + this.messageService + .get("ad/findAll", params) + .subscribe(ret => this.afterReceivingAds(ret), err => this.afterReceivingAds(err) ); + + // Ask for interest + this.messageService + .get("misc/getInterests") + .subscribe(ret => this.afterReceivingInterests(ret), err => this.afterReceivingInterests(err) ); + } + + + afterReceivingAds(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + const tabAdvert = retour.data; + this.messageService + .get("user/findAll") + .subscribe(ret => this.afterReceivingAdvertiser(ret, tabAdvert), err => this.afterReceivingAdvertiser(err, tabAdvert) ); + } + } + + + afterReceivingAdvertiser(retour: any, tabAdvert): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + this.tabAdvertiser = retour.data.filter(x => x.role.name === "advertiser"); + for(let advert of tabAdvert) this.tabAdvertWithCountViews.push(this.advertToAdvertWithCountViewsAndCompany(advert)); + this.dataSource = new MatTableDataSource(); + this.onFilter(); + } + } + + + afterReceivingInterests(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + this.allInterests = retour.data.map(x => x.interest); + this.allInterests.sort(); + } + } + + + applyFilter(event: Event): void + { + const filterValue = (event.target as HTMLInputElement).value; + this.dataSource.filter = filterValue.trim().toLowerCase(); + } + + + onVisualizeImages(advert: AdvertWithCountViewsAndCompany) + { + if(advert.images.length !== 0) + { + const config = { + width: '30%', + height: '90%', + data: { images: advert.images } + }; + this.dialog + .open(PopupVisualizeImagesAdminComponent, config) + .afterClosed() + .subscribe(retour => {}); + } + else { + const config = { duration: 2000, panelClass: "custom-class" }; + const message = "Cette annonce ne contient aucune image" ; + this.snackBar.open( message, "", config); + } + } + + + onDelete(advert: AdvertWithCountViewsAndCompany): void + { + const config = { + data: { advert: advert } + }; + this.dialog + .open(PopupDeleteAdAdminComponent, config) + .afterClosed() + .subscribe( retour => { + + const config = { duration: 1000, panelClass: "custom-class" }; + let message = "" ; + if((retour === undefined) || (retour === null)) { + message = "Opération annulée" ; + } + else { + const index = this.dataSource.data.findIndex( elt => (elt.id === advert.id)); + this.dataSource.data.splice(index, 1); + this.dataSource.data = this.dataSource.data; + this.dataSource = this.dataSource; + message = advert.title + " a bien été supprimée ✔" ; + } + this.snackBar.open( message, "", config); + }); + } + + + onFilter(): void + { + this.dataSource.data = []; + for(let advert of this.tabAdvertWithCountViews) + { + let valide: boolean = true; + + if(advert.isVisible && this.visible) valide = true; + else if((!advert.isVisible) && this.noVisible) valide = true; + else valide = false; + + if(valide) + { + if ((advert.createdAt === null) && (this.startDate !== null)) valide = false; + else if ((advert.createdAt === null) && (this.endDate !== null)) valide = false; + else if (this.startDate !== null) + { + if(this.startDate.getTime() > advert.createdAt.getTime()) valide = false; + else if (this.endDate !== null) + { + if(this.endDate.getTime() < advert.createdAt.getTime()) valide = false; + } + } + } + + if(valide) { + if(this.formControlInterests.value !== null) { + for (let interest of this.formControlInterests.value) { + if (advert.interests.indexOf(interest) === -1) { + valide = false; + break; + } + } + } + } + + if(valide) this.dataSource.data.push(advert); + } + + this.dataSource = new MatTableDataSource(this.dataSource.data); + this.dataSource.sort = this.sort; + this.dataSource.paginator = this.paginator; + } + + + onNewStartDate(event): void { + this.startDate = new Date(event); + } + + onNewEndDate(event): void { + this.endDate = new Date(event); + } + + + advertToAdvertWithCountViewsAndCompany(advert): AdvertWithCountViewsAndCompany + { + let company0 = "company" ; + for(let advertiser of this.tabAdvertiser) + { + if(advert.userId === advertiser.id) { + company0 = advertiser.company; + break; + } + } + + return { + id: advert.id, + userId: advert.userId, + title: advert.title, + company: company0, + url: advert.url, + images: advert.images, + interests: advert.interests, + comment: advert.comment, + views: advert.views, + countViews: advert.views.length, + isVisible: advert.isVisible, + isActive: advert.isActive, + createdAt: advert.createdAt, + updatedAt: advert.updatedAt, + } + } + +} diff --git a/admin/src/app/admin/adList/popup-delete-ad-admin/popup-delete-ad-admin.component.html b/admin/src/app/admin/adList/popup-delete-ad-admin/popup-delete-ad-admin.component.html new file mode 100644 index 0000000..d92e686 --- /dev/null +++ b/admin/src/app/admin/adList/popup-delete-ad-admin/popup-delete-ad-admin.component.html @@ -0,0 +1,8 @@ + + Êtes-vous sûr de vouloir supprimer l'annonce {{advert.title}} ? + + + + + + diff --git a/admin/src/app/admin/adList/popup-delete-ad-admin/popup-delete-ad-admin.component.scss b/admin/src/app/admin/adList/popup-delete-ad-admin/popup-delete-ad-admin.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/admin/src/app/admin/adList/popup-delete-ad-admin/popup-delete-ad-admin.component.spec.ts b/admin/src/app/admin/adList/popup-delete-ad-admin/popup-delete-ad-admin.component.spec.ts new file mode 100644 index 0000000..811eee8 --- /dev/null +++ b/admin/src/app/admin/adList/popup-delete-ad-admin/popup-delete-ad-admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupDeleteAdAdminComponent } from './popup-delete-ad-admin.component'; + +describe('PopupDeleteAdAdminComponent', () => { + let component: PopupDeleteAdAdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupDeleteAdAdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupDeleteAdAdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/admin/adList/popup-delete-ad-admin/popup-delete-ad-admin.component.ts b/admin/src/app/admin/adList/popup-delete-ad-admin/popup-delete-ad-admin.component.ts new file mode 100644 index 0000000..8470921 --- /dev/null +++ b/admin/src/app/admin/adList/popup-delete-ad-admin/popup-delete-ad-admin.component.ts @@ -0,0 +1,47 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {MessageService} from "../../../utils/message/message.service"; + +@Component({ + selector: 'app-popup-delete-ad-admin', + templateUrl: './popup-delete-ad-admin.component.html', + styleUrls: ['./popup-delete-ad-admin.component.scss'] +}) +export class PopupDeleteAdAdminComponent implements OnInit +{ + advert: any; + + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data, + private messageService: MessageService) { } + + + ngOnInit(): void + { + this.advert = this.data.advert; + } + + + onValidate(): void + { + this.messageService + .delete("ad/delete/"+this.advert.id) + .subscribe(ret => this.onValidateCallback(ret), err => this.onValidateCallback(err)); + } + + + onValidateCallback(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + this.dialogRef.close(); + } + else { + console.log("suppr"); + console.log(retour); + this.dialogRef.close(true); + } + } + +} diff --git a/admin/src/app/admin/adList/popup-visualize-images-admin/popup-visualize-images-admin.component.html b/admin/src/app/admin/adList/popup-visualize-images-admin/popup-visualize-images-admin.component.html new file mode 100644 index 0000000..dfbc2fe --- /dev/null +++ b/admin/src/app/admin/adList/popup-visualize-images-admin/popup-visualize-images-admin.component.html @@ -0,0 +1,20 @@ +
+

+ +
+ + + + + + + + + + + + + + diff --git a/admin/src/app/admin/adList/popup-visualize-images-admin/popup-visualize-images-admin.component.scss b/admin/src/app/admin/adList/popup-visualize-images-admin/popup-visualize-images-admin.component.scss new file mode 100644 index 0000000..eb60d48 --- /dev/null +++ b/admin/src/app/admin/adList/popup-visualize-images-admin/popup-visualize-images-admin.component.scss @@ -0,0 +1,14 @@ +carousel { + width: 100%; + margin: 0 auto; + text-align: center; + justify-content: center +} + + + +.dialog-title { + display: flex; + justify-content: space-between; + align-items: center; +} diff --git a/admin/src/app/admin/adList/popup-visualize-images-admin/popup-visualize-images-admin.component.spec.ts b/admin/src/app/admin/adList/popup-visualize-images-admin/popup-visualize-images-admin.component.spec.ts new file mode 100644 index 0000000..24f276f --- /dev/null +++ b/admin/src/app/admin/adList/popup-visualize-images-admin/popup-visualize-images-admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupVisualizeImagesAdminComponent } from './popup-visualize-images-admin.component'; + +describe('PopupVisualizeImagesAdminComponent', () => { + let component: PopupVisualizeImagesAdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupVisualizeImagesAdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupVisualizeImagesAdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/admin/adList/popup-visualize-images-admin/popup-visualize-images-admin.component.ts b/admin/src/app/admin/adList/popup-visualize-images-admin/popup-visualize-images-admin.component.ts new file mode 100644 index 0000000..634d051 --- /dev/null +++ b/admin/src/app/admin/adList/popup-visualize-images-admin/popup-visualize-images-admin.component.ts @@ -0,0 +1,35 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; + +@Component({ + selector: 'app-popup-visualize-images-admin', + templateUrl: './popup-visualize-images-admin.component.html', + styleUrls: ['./popup-visualize-images-admin.component.scss'] +}) +export class PopupVisualizeImagesAdminComponent implements OnInit { + tabImages = []; + index: number = 0; + nbImage: number = 0; + + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data ) { } + + + ngOnInit(): void + { + this.tabImages = this.data.images; + this.nbImage = this.tabImages.length; + } + + onPrecedent(): void + { + if(this.index !== 0) this.index -= 1; + } + + onSuivant(): void + { + if(this.index !== (this.nbImage-1)) this.index += 1; + } + +} diff --git a/admin/src/app/admin/myProfil/page-profil-admin/page-profil-admin.component.html b/admin/src/app/admin/myProfil/page-profil-admin/page-profil-admin.component.html new file mode 100644 index 0000000..7e025c5 --- /dev/null +++ b/admin/src/app/admin/myProfil/page-profil-admin/page-profil-admin.component.html @@ -0,0 +1,43 @@ +
+
+ + + + + +
+ + +
+ +
+ + +
+
Pseudo:
+
{{admin.login}}
+
+ + +
+
Mail:
+
{{admin.email}}
+
+ + +
+
Date de création:
+
{{admin.createdAt | date:'dd/LL/YYYY'}}
+
+ + +
+ +
+ +
+ + +
+
diff --git a/admin/src/app/admin/myProfil/page-profil-admin/page-profil-admin.component.scss b/admin/src/app/admin/myProfil/page-profil-admin/page-profil-admin.component.scss new file mode 100644 index 0000000..966c9a2 --- /dev/null +++ b/admin/src/app/admin/myProfil/page-profil-admin/page-profil-admin.component.scss @@ -0,0 +1,61 @@ +.myContainer { + max-width: 100vw; + height: 100vh; + overflow-x: hidden; +} + + +.boite { + margin-left: auto; + margin-right: auto; + width: 25%; + margin-top: 10vh; + border: solid 3px; + border-radius: 10px; + padding: 20px 40px 20px 40px; + background-color: #ffffff; + text-align: center; + box-shadow: 10px 5px 5px black; +} +.lightTheme .boite { + border-color: black; +} +.darkTheme .boite { + border-color: white; +} + + +img { + margin: 0px 0px 10px 0px; + width: 5vw; + height: 5vw; + border: solid 2px black; + border-radius: 50%; + font-size: xxx-large; +} + + +.myRow { + margin: 15px 0px 15px 0px; +} +.myLabel { + text-align: right; + padding: 0px 5px 0px 0px; + margin: 0px; + font-weight: bold; +} +.myValue { + text-align: left; + padding: 0px 0px 0px 5px; + margin: 0px; +} + + +.btnContainer { + text-align: center; + margin-top: 40px; +} +.myBtn { + border: solid 1px black; + background-color: white; +} diff --git a/admin/src/app/admin/myProfil/page-profil-admin/page-profil-admin.component.spec.ts b/admin/src/app/admin/myProfil/page-profil-admin/page-profil-admin.component.spec.ts new file mode 100644 index 0000000..39fbdd9 --- /dev/null +++ b/admin/src/app/admin/myProfil/page-profil-admin/page-profil-admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageProfilAdminComponent } from './page-profil-admin.component'; + +describe('PageProfilAdminComponent', () => { + let component: PageProfilAdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageProfilAdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageProfilAdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/admin/myProfil/page-profil-admin/page-profil-admin.component.ts b/admin/src/app/admin/myProfil/page-profil-admin/page-profil-admin.component.ts new file mode 100644 index 0000000..a282706 --- /dev/null +++ b/admin/src/app/admin/myProfil/page-profil-admin/page-profil-admin.component.ts @@ -0,0 +1,87 @@ +import { Component, OnInit } from '@angular/core'; +import {ThemeService} from "../../../utils/theme/theme.service"; +import {MatDialog} from "@angular/material/dialog"; +import {MatSnackBar} from "@angular/material/snack-bar"; +import {MessageService} from "../../../utils/message/message.service"; +import {ProfilService} from "../../../utils/profil/profil.service"; +import {PopupUpdateAdminComponent} from "../popup-update-admin/popup-update-admin.component"; + +@Component({ + selector: 'app-page-profil-admin', + templateUrl: './page-profil-admin.component.html', + styleUrls: ['./page-profil-admin.component.scss'] +}) +export class PageProfilAdminComponent implements OnInit +{ + admin = { + _id: "", + login: "", + hashPass: "", + email: "", + role: { + name: "admin", + permission: 10, + isAccepted: true, + }, + profileImageUrl: "", + dateOfBirth: null, + gender: "man", + interests: [], + company: "", + isActive: true, + createdAt: new Date(), + updatedAt: new Date(), + lastConnexion: null + }; + + + constructor( public themeService: ThemeService, + public dialog: MatDialog, + private snackBar: MatSnackBar, + private messageService: MessageService, + private profilService: ProfilService ) { } + + + ngOnInit(): void + { + this.messageService + .get( "user/findOne/"+this.profilService.getId()) + .subscribe( retour => this.ngOnInitCallback(retour), err => this.ngOnInitCallback(err) ) + } + + + ngOnInitCallback(retour: any) + { + if(retour.status !== "success") { + console.log(retour); + } + else { + this.admin = retour.data; + } + } + + + onModifier() + { + const config = { + width: '25%', + data: { admin: this.admin } + }; + this.dialog + .open(PopupUpdateAdminComponent, config) + .afterClosed() + .subscribe(retour => { + + if((retour === null) || (retour === undefined)) + { + const config = { duration: 1000, panelClass: "custom-class" }; + this.snackBar.open( "Opération annulé", "", config); + } + else + { + this.admin = retour; + } + }); + } + +} diff --git a/admin/src/app/admin/myProfil/popup-update-admin/popup-update-admin.component.html b/admin/src/app/admin/myProfil/popup-update-admin/popup-update-admin.component.html new file mode 100644 index 0000000..38cf7e5 --- /dev/null +++ b/admin/src/app/admin/myProfil/popup-update-admin/popup-update-admin.component.html @@ -0,0 +1,59 @@ +
+
+ + +
+
+ +
+ + +

+ + + + Pseudo + +
+ + +
+ + +
+ Modifier mot de passe: + +
+ + +
+ + + Nouveau mot de passe + + +
+ + + Confirmation nouveau mot de passe + + +
+

+ + +
+ + +
+ {{errorMessage}} +
+ + +
+ + +
+ +
+
diff --git a/admin/src/app/admin/myProfil/popup-update-admin/popup-update-admin.component.scss b/admin/src/app/admin/myProfil/popup-update-admin/popup-update-admin.component.scss new file mode 100644 index 0000000..1968e90 --- /dev/null +++ b/admin/src/app/admin/myProfil/popup-update-admin/popup-update-admin.component.scss @@ -0,0 +1,33 @@ +.boite { + font-size: small; +} + +button { + font-size: small; +} + +img { + margin: 0px 0px 10px 0px; + width: 5vw; + height: 5vw; + border: solid 2px black; + border-radius: 50%; + font-size: xxx-large; +} + +// ------------------------------------------------------------------------- + +// aura +::ng-deep .mat-checkbox-ripple .mat-ripple-element { + background-color: grey !important; +} + +// contenu coche +::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background { + background-color: black !important; +} + +// indeterminate +::ng-deep .mat-checkbox .mat-checkbox-frame { + background-color: white !important; +} diff --git a/admin/src/app/admin/myProfil/popup-update-admin/popup-update-admin.component.spec.ts b/admin/src/app/admin/myProfil/popup-update-admin/popup-update-admin.component.spec.ts new file mode 100644 index 0000000..9f1a0f5 --- /dev/null +++ b/admin/src/app/admin/myProfil/popup-update-admin/popup-update-admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupUpdateAdminComponent } from './popup-update-admin.component'; + +describe('PopupUpdateAdminComponent', () => { + let component: PopupUpdateAdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupUpdateAdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupUpdateAdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/admin/myProfil/popup-update-admin/popup-update-admin.component.ts b/admin/src/app/admin/myProfil/popup-update-admin/popup-update-admin.component.ts new file mode 100644 index 0000000..2e11b68 --- /dev/null +++ b/admin/src/app/admin/myProfil/popup-update-admin/popup-update-admin.component.ts @@ -0,0 +1,122 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {MessageService} from "../../../utils/message/message.service"; +import {ProfilService} from "../../../utils/profil/profil.service"; + + +@Component({ + selector: 'app-popup-update-admin', + templateUrl: './popup-update-admin.component.html', + styleUrls: ['./popup-update-admin.component.scss'] +}) +export class PopupUpdateAdminComponent implements OnInit +{ + adminCopy; + newPassword: string = ""; + confirmNewPassword: string = "" ; + changePassword: boolean = false ; + hasError: boolean = false; + errorMessage: string = "" ; + + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data, + private messageService: MessageService, + private profilService: ProfilService ) { } + + + ngOnInit(): void + { + const admin0 = this.data.admin; + this.adminCopy = { + _id: admin0._id, + login: admin0.login, + hashPass: admin0.hashPass, + email: admin0.email, + role: { + name: admin0.role.name, + permission: admin0.role.permission, + isAccepted: admin0.role.isAccepted, + }, + profileImageUrl: admin0.profileImageUrl, + dateOfBirth: admin0.dateOfBirth, + gender: admin0.gender, + interests: [], + company: "", + isActive: admin0.isActive, + createdAt: admin0.createdAt, + updatedAt: admin0.updatedAt, + lastConnexion: admin0.lastConnexion + }; + for(let interest of admin0.interests) this.adminCopy.interests.push(interest); + } + + + onValider() + { + this.checkField(); + if(!this.hasError) + { + if(this.changePassword) this.adminCopy.hashPass = this.newPassword; + const data = { + login: this.adminCopy.login, + hashPass: this.adminCopy.hashPass, + email: this.adminCopy.email, + profileImageUrl: this.adminCopy.profileImageUrl, + }; + this.messageService + .put("user/update/"+this.profilService.getId(), data) + .subscribe( ret => this.onValiderCallback(ret), err => this.onValiderCallback(err) ); + } + } + + + onValiderCallback(retour: any) + { + if(retour.status !== "success") { + console.log(retour); + this.dialogRef.close(null); + } + else { + this.profilService.setProfileImageUrl(this.adminCopy.profileImageUrl); + this.dialogRef.close(this.adminCopy); + } + } + + + checkField() + { + if(this.adminCopy.login.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'pseudo'" ; + this.hasError = true; + } + else if(this.adminCopy.email.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'email'" ; + this.hasError = true; + } + else if(!this.isValidEmail(this.adminCopy.email)) { + this.errorMessage = "Email invalide" ; + this.hasError = true; + } + else if((this.changePassword) && (this.newPassword.length === 0)) { + this.errorMessage = "Veuillez remplir le champ 'mot de passe'" ; + this.hasError = true; + } + else if((this.changePassword) && (this.newPassword !== this.confirmNewPassword)) { + this.errorMessage = "Le mot de passe est différent de sa confirmation" ; + this.hasError = true; + } + else { + this.errorMessage = "" ; + this.hasError = false; + } + } + + + isValidEmail(email) + { + let re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); + } + +} diff --git a/admin/src/app/admin/userList/input-interests-admin/input-interests-admin.component.html b/admin/src/app/admin/userList/input-interests-admin/input-interests-admin.component.html new file mode 100644 index 0000000..2a7c484 --- /dev/null +++ b/admin/src/app/admin/userList/input-interests-admin/input-interests-admin.component.html @@ -0,0 +1,43 @@ + + + + + Centres d'intérêt + + + + + + + {{interest}} + + + + + + + + + + + + {{interest}} + + + + + + diff --git a/admin/src/app/admin/userList/input-interests-admin/input-interests-admin.component.scss b/admin/src/app/admin/userList/input-interests-admin/input-interests-admin.component.scss new file mode 100644 index 0000000..c7acb4b --- /dev/null +++ b/admin/src/app/admin/userList/input-interests-admin/input-interests-admin.component.scss @@ -0,0 +1,3 @@ +mat-form-field { + width: 100%; +} diff --git a/admin/src/app/admin/userList/input-interests-admin/input-interests-admin.component.spec.ts b/admin/src/app/admin/userList/input-interests-admin/input-interests-admin.component.spec.ts new file mode 100644 index 0000000..62f9051 --- /dev/null +++ b/admin/src/app/admin/userList/input-interests-admin/input-interests-admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InputInterestsAdminComponent } from './input-interests-admin.component'; + +describe('InputInterestsAdminComponent', () => { + let component: InputInterestsAdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ InputInterestsAdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InputInterestsAdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/admin/userList/input-interests-admin/input-interests-admin.component.ts b/admin/src/app/admin/userList/input-interests-admin/input-interests-admin.component.ts new file mode 100644 index 0000000..c0b3560 --- /dev/null +++ b/admin/src/app/admin/userList/input-interests-admin/input-interests-admin.component.ts @@ -0,0 +1,121 @@ +import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; +import {COMMA, ENTER} from "@angular/cdk/keycodes"; +import {FormControl} from "@angular/forms"; +import {Observable} from "rxjs"; +import {map, startWith} from "rxjs/operators"; +import {MatChipInputEvent} from "@angular/material/chips"; +import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete"; +import {MessageService} from "../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-input-interests-admin', + templateUrl: './input-interests-admin.component.html', + styleUrls: ['./input-interests-admin.component.scss'] +}) +export class InputInterestsAdminComponent implements OnInit +{ + selectable = true; + removable = true; + separatorKeysCodes: number[] = [ENTER, COMMA]; + formControl = new FormControl(); + filteredInterests: Observable; + @Input() myInterests: string[] = []; + allInterests: string[] = []; + @Output() eventEmitter = new EventEmitter(); + @ViewChild('tagInput') tagInput: ElementRef; + interestsNotSelected: string[] = []; + + + constructor( private messageService: MessageService ) {} + + + ngOnInit(): void + { + this.filteredInterests = this.formControl.valueChanges.pipe( + startWith(null), + map((fruit: string | null) => fruit ? this._filter(fruit) : this.interestsNotSelected.slice())); + + this.messageService + .get("misc/getInterests") + .subscribe( retour => { + + if(retour.status !== "success") { + console.log(retour); + } + else { + this.allInterests = []; + for(let elt of retour.data) + { + this.allInterests.push(elt.interest); + this.interestsNotSelected.push(elt.interest); + } + } + }); + } + + + add(event: MatChipInputEvent): void + { + const value = (event.value || '').trim(); + const index = this.interestsNotSelected.indexOf(value); + if (value && (index !== -1) && (!this.myInterests.includes(value))) + { + this.myInterests.push(value); + event.chipInput!.clear(); + this.formControl.setValue(null); + this.eventEmitter.emit(this.myInterests); + this.interestsNotSelected.splice(index, 1); + } + } + + + remove(interest: string): void + { + // supprimer 'interest' de 'myInterest' + const index = this.myInterests.indexOf(interest); + if (index >= 0) this.myInterests.splice(index, 1); + this.eventEmitter.emit(this.myInterests); + + // remmettre 'interest' dans 'interestsNotSelected' + if(!this.interestsNotSelected.includes(interest)) + { + const indexOfAutres = this.interestsNotSelected.indexOf("Autres"); + if(indexOfAutres !== -1) + { + this.interestsNotSelected.splice(indexOfAutres, 1); + if(interest !== "Autres") this.interestsNotSelected.push(interest); + this.interestsNotSelected.sort(); + this.interestsNotSelected.push("Autres"); + } + else { + this.interestsNotSelected.push(interest); + if(interest !== "Autres") this.interestsNotSelected.sort(); + } + } + } + + + selected(event: MatAutocompleteSelectedEvent): void + { + const value = event.option.viewValue; + if(!this.myInterests.includes(value)) + { + this.myInterests.push(value); + const index = this.interestsNotSelected.indexOf(value); + this.interestsNotSelected.splice(index, 1); + } + this.tagInput.nativeElement.value = ''; + this.formControl.setValue(null); + this.eventEmitter.emit(this.myInterests); + } + + + private _filter(value: string): string[] + { + const filterValue = value.toLowerCase(); + return this.interestsNotSelected.filter(fruit => fruit.toLowerCase().includes(filterValue)); + } + +} diff --git a/admin/src/app/admin/userList/page-user-list/page-user-list.component.html b/admin/src/app/admin/userList/page-user-list/page-user-list.component.html new file mode 100644 index 0000000..25fe6f5 --- /dev/null +++ b/admin/src/app/admin/userList/page-user-list/page-user-list.component.html @@ -0,0 +1,193 @@ +
+
+ + + +

+ + + + +
+ + +
+
+ + +
+ Filtre +
+ + + + +
+ +
+ + +
+ + +
+ + + Utilisateur +
+ + Annonceur +
+ + Admin + +
+
+ + +
+ actif
+ non actif +
+ + +
+ Période de dernière connexion:   + + Date de début + + +   -   + + Date de fin + + +
+ +
+ +
+
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ power_settings_new + + + + Pseudo + {{user.login}} + Email + {{user.email}} + Date de naissance + + {{ user.dateOfBirth | date:'dd/LL/YYYY' }} + Âge + + {{user.age}} + Sexe + H + F + Centres d'intérêt + + {{interest}}, + {{interest}} + + Date de création + {{ user.createdAt | date:'dd/LL/YYYY à HH:mm:ss' }} + Dernière connexion + {{ user.lastConnexion | date:'dd/LL/YYYY à HH:mm:ss' }} + Accepté + +
Aucune vidéo ne correspond au filtre: "{{input.value}}"
+
+ +
+

+ +
+
diff --git a/admin/src/app/admin/userList/page-user-list/page-user-list.component.scss b/admin/src/app/admin/userList/page-user-list/page-user-list.component.scss new file mode 100644 index 0000000..bbeac05 --- /dev/null +++ b/admin/src/app/admin/userList/page-user-list/page-user-list.component.scss @@ -0,0 +1,99 @@ +.myContainer { + min-height: 100vh; + font-size: small; +} + +// ---------------------------------------------------------- + +.filtersContainer { + width: 90%; + background-color: white; + padding: 10px 10px 10px 10px; +} + +.myRow { + margin-left: 1%; +} + +.textFilter { + width: 50%; + font-size: medium; + border-radius: 5px; +} + +.btnAjouter { + background-color: white; + border: solid 1px black; +} + +// ---------------------------------------------------------- + +table { + margin: 0 auto; + width: 94%; + font-size: small; +} +.darkTheme table { border: solid 2px white; } + +th.mat-sort-header-sorted { + color: black; +} + +td { + font-size: small; +} + +// ------------------------------------------------------------------------- + +::ng-deep .mat-radio-inner-circle { + color: black !important; + background-color: black !important; +} + +::ng-deep .mat-radio-outer-circle{ + color: black !important; + border: solid 1px gray !important; +} + +// ------------------------------------------------------------------------- + + +// aura +::ng-deep .mat-checkbox-ripple .mat-ripple-element { + background-color: grey !important; +} + +// contenu coche +::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background { + background-color: black !important; +} + +// indeterminate +::ng-deep .mat-checkbox .mat-checkbox-frame { + border: solid 1px black !important; + background-color: white !important; +} + + +// -------------------------------------------------------------------- + + +// rong gauche +::ng-deep .mat-slide-toggle-thumb { + background-color: white !important; +} + +// trait droite +::ng-deep .mat-slide-toggle-bar { + background-color: gray !important; +} + +// rond droite +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-thumb { + background-color: white !important; +} + +// trait gauche +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-bar { + background-color: cornflowerblue !important; +} diff --git a/admin/src/app/admin/userList/page-user-list/page-user-list.component.spec.ts b/admin/src/app/admin/userList/page-user-list/page-user-list.component.spec.ts new file mode 100644 index 0000000..edbbffe --- /dev/null +++ b/admin/src/app/admin/userList/page-user-list/page-user-list.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageUserListComponent } from './page-user-list.component'; + +describe('PageUserListComponent', () => { + let component: PageUserListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageUserListComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageUserListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/admin/userList/page-user-list/page-user-list.component.ts b/admin/src/app/admin/userList/page-user-list/page-user-list.component.ts new file mode 100644 index 0000000..995b053 --- /dev/null +++ b/admin/src/app/admin/userList/page-user-list/page-user-list.component.ts @@ -0,0 +1,232 @@ +import {AfterViewInit, Component, ViewChild} from '@angular/core'; +import {MatSort} from "@angular/material/sort"; +import {MatPaginator} from "@angular/material/paginator"; +import {MatDialog} from "@angular/material/dialog"; +import {MatSnackBar} from "@angular/material/snack-bar"; +import {MatTableDataSource} from "@angular/material/table"; +import {PopupDeleteUserComponent} from "../popup-delete-user/popup-delete-user.component"; +import {PopupCreateUserComponent} from "../popup-create-user/popup-create-user.component"; +import {ThemeService} from "../../../utils/theme/theme.service"; +import {MessageService} from "../../../utils/message/message.service"; + + +@Component({ + selector: 'app-page-user-list', + templateUrl: './page-user-list.component.html', + styleUrls: ['./page-user-list.component.scss'] +}) +export class PageUserListComponent implements AfterViewInit +{ + displayedColumns: string[]; + displayedColumnsUser: string[] = [ 'isActive', 'login', 'email', 'dateOfBirth', 'age', 'sexe', 'interests', 'createdAt', 'lastConnexion' ]; + displayedColumnsAdvertiser: string[] = [ 'isActive', 'login', 'email', 'createdAt', 'lastConnexion', 'isAccepted' ]; + displayedColumnsAdmin: string[] = [ 'isActive', 'login', 'email', 'createdAt', 'lastConnexion' ]; + + tabUser: any[] = []; + tabAdvertiser: any[] = []; + tabAdmin: any[] = []; + + roleName: string = "user" ; + dataSource ; + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + + active: boolean = true; + noActive: boolean = false; + startDate: Date = null; + endDate: Date = null; + + + constructor( public themeService: ThemeService, + public dialog: MatDialog, + private snackBar: MatSnackBar, + private messageService: MessageService ) { } + + + ngAfterViewInit(): void + { + this.messageService + .get("user/findAll") + .subscribe(ret => this.ngAfterViewInitCallback(ret), err => this.ngAfterViewInitCallback(err)); + } + + + ngAfterViewInitCallback(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + for(let person of retour.data) + { + if(person.role.name === "user") { + person["age"] = this.getAge(person.dateOfBirth); + this.tabUser.push(person); + } + else if(person.role.name === "advertiser") this.tabAdvertiser.push(person); + else this.tabAdmin.push(person); + } + this.onFilter(); + } + } + + + applyFilter(event: Event): void + { + const filterValue = (event.target as HTMLInputElement).value; + this.dataSource.filter = filterValue.trim().toLowerCase(); + } + + + onDelete(user: any): void + { + const config = { + data: { user: user } + }; + this.dialog + .open(PopupDeleteUserComponent, config) + .afterClosed() + .subscribe( retour => { + + const config = { duration: 1000, panelClass: "custom-class" }; + let message = "" ; + if((retour === undefined) || (retour === null)) { + message = "Opération annulée" ; + } + else { + const index = this.dataSource.data.findIndex( elt => (elt.id === user.id)); + this.dataSource.data.splice(index, 1); + this.dataSource.data = this.dataSource.data; + this.dataSource = this.dataSource; + message = user.login + " a bien été supprimée ✔" ; + } + this.snackBar.open(message, "", config); + }); + } + + + onCreateUser(): void + { + const config = { width: '50%' }; + this.dialog + .open(PopupCreateUserComponent, config) + .afterClosed() + .subscribe( retour => { + + const config = { duration: 1000, panelClass: "custom-class" }; + if((retour === null) || (retour === undefined)) { + this.snackBar.open( "Opération annulée", "", config); + } + else { + this.snackBar.open( "L'utilisateur a bien été créé", "", config); + if(retour.role.name === "user") this.tabUser.push(retour); + else if(retour.role.name === "advertiser") this.tabAdvertiser.push(retour); + else if(retour.role.name === "admin") this.tabAdmin.push(retour); + this.onFilter(); + } + }); + } + + + onSliderIsActive(user: any): void + { + // il faut envoyer la négation de user.isActive + this.messageService + .put("user/update/"+user.id, { isActive: !user.isActive }) + .subscribe( + ret => {}, + err => { + console.log("onSliderIsActive"); + console.log(err); + } + ); + } + + + onSlideIsAccepted(user: any): void + { + // il faut envoyer la négation de user.role.isAccepted + const role0 = { + name: user.role.name, + permission: user.role.permission, + isAccepted: !user.role.isAccepted, + }; + this.messageService + .put("user/update/"+user.id, {role: role0}) + .subscribe( + ret => {}, + err => { + console.log("onSlideIsAccepted"); + console.log(err); + } + ); + } + + + getAge(date: Date): number + { + if((date === null) || (date === undefined)) return -1; + else { + const diff = Date.now() - (new Date(date)).getTime(); + const age = new Date(diff); + return Math.abs(age.getUTCFullYear() - 1970); + } + } + + + onFilter(): void + { + let tab1 = []; + if(this.roleName === "user") { + this.displayedColumns = this.displayedColumnsUser; + tab1 = this.tabUser; + } + else if(this.roleName === "advertiser") { + this.displayedColumns = this.displayedColumnsAdvertiser; + tab1 = this.tabAdvertiser; + } + else if(this.roleName === "admin") { + this.displayedColumns = this.displayedColumnsAdmin; + tab1 = this.tabAdmin; + } + + let tab2 = []; + for(let user of tab1) + { + let valide: boolean = true; + + if(user.isActive && this.active) valide = true; + else if((!user.isActive) && this.noActive) valide = true; + else valide = false; + if(valide) + { + if ((user.lastConnexion === null) && (this.startDate !== null)) valide = false; + else if ((user.lastConnexion === null) && (this.endDate !== null)) valide = false; + else if (this.startDate !== null) + { + if(this.startDate.getTime() > user.lastConnexion.getTime()) valide = false; + else if (this.endDate !== null) + { + if(this.endDate.getTime() < user.lastConnexion.getTime()) valide = false; + } + } + } + + if(valide) tab2.push(user); + } + + this.dataSource = new MatTableDataSource(tab2); + this.dataSource.sort = this.sort; + this.dataSource.paginator = this.paginator; + } + + + onNewStartDate(event): void { + this.startDate = new Date(event); + } + + onNewEndDate(event): void { + this.endDate = new Date(event); + } + +} diff --git a/admin/src/app/admin/userList/popup-create-user/popup-create-user.component.html b/admin/src/app/admin/userList/popup-create-user/popup-create-user.component.html new file mode 100644 index 0000000..dc7ac87 --- /dev/null +++ b/admin/src/app/admin/userList/popup-create-user/popup-create-user.component.html @@ -0,0 +1,160 @@ +
+ + +
+ + Utilisateur
+ Annonceur
+ Admin +
+

+ + +
+ + +
+
+ + +
+ + +
+ {{errorMessage}} +
+ + +
+ + +
+ +
+ + + + + + + + + +
+
+ +

+ +
+ + +
+ + + + Email + +
+ + + + Pseudo + +
+ + + + Mot de passe + +
+ + + + Confirmation mot de passe + + + +
+ + +
+ + + + Date de naissance + +
+ + + + Homme     + Femme + +

+ + + +
+ +
+
+ + + + + + + + + + +
+
+ +

+ + + +
+ + +
+ + + Email + +
+ + + Pseudo + +
+ + + Entreprise + +
+
+ + +
+ + + Mot de passe + +
+ + + Confirmation nouveau mot de passe + + +
+ +
+ + +
diff --git a/admin/src/app/admin/userList/popup-create-user/popup-create-user.component.scss b/admin/src/app/admin/userList/popup-create-user/popup-create-user.component.scss new file mode 100644 index 0000000..4c8a0c6 --- /dev/null +++ b/admin/src/app/admin/userList/popup-create-user/popup-create-user.component.scss @@ -0,0 +1,16 @@ +.myContainer { + font-size: small; +} + +img { + margin: 0px 0px 10px 0px; + width: 10%; + height: 10%; + border: solid 2px black; + border-radius: 50%; + font-size: xxx-large; +} + +.leftCol { + border-right: solid 1px #dcdcdc; +} diff --git a/admin/src/app/admin/userList/popup-create-user/popup-create-user.component.spec.ts b/admin/src/app/admin/userList/popup-create-user/popup-create-user.component.spec.ts new file mode 100644 index 0000000..9c57fcc --- /dev/null +++ b/admin/src/app/admin/userList/popup-create-user/popup-create-user.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupCreateUserComponent } from './popup-create-user.component'; + +describe('PopupCreateUserComponent', () => { + let component: PopupCreateUserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupCreateUserComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupCreateUserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/admin/userList/popup-create-user/popup-create-user.component.ts b/admin/src/app/admin/userList/popup-create-user/popup-create-user.component.ts new file mode 100644 index 0000000..b5714a2 --- /dev/null +++ b/admin/src/app/admin/userList/popup-create-user/popup-create-user.component.ts @@ -0,0 +1,125 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {MessageService} from "../../../utils/message/message.service"; + +@Component({ + selector: 'app-popup-create-user', + templateUrl: './popup-create-user.component.html', + styleUrls: ['./popup-create-user.component.scss'] +}) +export class PopupCreateUserComponent implements OnInit +{ + user: any; + hasError: boolean = false; + errorMessage: string = ""; + password: string = ""; + confirmPassword: string = ""; + + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data, + private messageService: MessageService ) { } + + + // Initialise l'utilisateur qui va être créé + ngOnInit(): void + { + this.user = { + _id: "", + login: "", + hashPass: "", + email: "", + role: { + name: "", + permission: 0, + isAccepted: false, + }, + profileImageUrl: "", + dateOfBirth: null, + gender: "man", + interests: [], + company: "", + isActive: false, + createdAt: new Date(), + updatedAt: new Date(), + lastConnexion: new Date() + }; + } + + + // Crée le nouvel utilisateur + onEnregistrer(): void + { + this.checkField(); + if(!this.hasError) + { + this.user.hashPass = this.password; + this.user.role = this.user.role.name; + this.messageService + .post("user/create", this.user) + .subscribe(ret => this.onEnregistrerCallback(ret), err => this.onEnregistrerCallback(err)); + } + } + + + // Callback de 'onEnregistrer' + onEnregistrerCallback(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + this.dialogRef.close(retour.data); + } + } + + + // Check les champs saisies par l'utilisateur + checkField(): void + { + if(this.user.login.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'pseudo'."; + this.hasError = true; + } + else if(this.user.email.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'email'."; + this.hasError = true; + } + else if(!this.isValidEmail(this.user.email)) { + this.errorMessage = "Email invalide."; + this.hasError = true; + } + else if(this.password.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'mot de passe'."; + this.hasError = true; + } + else if(this.password !== this.confirmPassword) { + this.errorMessage = "Le mot de passe est différent de sa confirmation."; + this.hasError = true; + } + else if((this.user.role.name === 'advertiser') && (this.user.company.length === 0)) { + this.errorMessage = "Veuillez remplir le champ 'entreprise'."; + this.hasError = true; + } + else { + this.errorMessage = "" ; + this.hasError = false; + } + } + + + // Indique si email a bien le format d'un email + isValidEmail(email): boolean + { + let re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); + } + + + // Récupère la liste des centres d'intérets (car celle-ci est remplie à l'aide d'un component intermédiaire) + onEventInputInterests(myInterets: string[]): void + { + this.user.interests = myInterets; + } + +} diff --git a/admin/src/app/admin/userList/popup-delete-user/popup-delete-user.component.html b/admin/src/app/admin/userList/popup-delete-user/popup-delete-user.component.html new file mode 100644 index 0000000..26e3854 --- /dev/null +++ b/admin/src/app/admin/userList/popup-delete-user/popup-delete-user.component.html @@ -0,0 +1,8 @@ + + Êtes-vous sûr de vouloir supprimer {{user.login}} ? + + + + + + diff --git a/admin/src/app/admin/userList/popup-delete-user/popup-delete-user.component.scss b/admin/src/app/admin/userList/popup-delete-user/popup-delete-user.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/admin/src/app/admin/userList/popup-delete-user/popup-delete-user.component.spec.ts b/admin/src/app/admin/userList/popup-delete-user/popup-delete-user.component.spec.ts new file mode 100644 index 0000000..273cdc6 --- /dev/null +++ b/admin/src/app/admin/userList/popup-delete-user/popup-delete-user.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupDeleteUserComponent } from './popup-delete-user.component'; + +describe('PopupDeleteUserComponent', () => { + let component: PopupDeleteUserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupDeleteUserComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupDeleteUserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/admin/userList/popup-delete-user/popup-delete-user.component.ts b/admin/src/app/admin/userList/popup-delete-user/popup-delete-user.component.ts new file mode 100644 index 0000000..cd8091c --- /dev/null +++ b/admin/src/app/admin/userList/popup-delete-user/popup-delete-user.component.ts @@ -0,0 +1,29 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {MessageService} from "../../../utils/message/message.service"; + +@Component({ + selector: 'app-popup-delete-user', + templateUrl: './popup-delete-user.component.html', + styleUrls: ['./popup-delete-user.component.scss'] +}) +export class PopupDeleteUserComponent implements OnInit +{ + user; + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data, + private messageService: MessageService ) { } + + ngOnInit(): void + { + this.user = this.data.user; + } + + onValidate(): void + { + // --- FAUX CODE --- + this.dialogRef.close(true); + } + +} diff --git a/admin/src/app/admin/utils/navbar-admin/navbar-admin.component.html b/admin/src/app/admin/utils/navbar-admin/navbar-admin.component.html new file mode 100644 index 0000000..a73059e --- /dev/null +++ b/admin/src/app/admin/utils/navbar-admin/navbar-admin.component.html @@ -0,0 +1,37 @@ + diff --git a/admin/src/app/admin/utils/navbar-admin/navbar-admin.component.scss b/admin/src/app/admin/utils/navbar-admin/navbar-admin.component.scss new file mode 100644 index 0000000..285d629 --- /dev/null +++ b/admin/src/app/admin/utils/navbar-admin/navbar-admin.component.scss @@ -0,0 +1,80 @@ +.navbar { + background-color: black; + height: 60px; + font-size: medium; + color: white; +} + + +.navbar-expand-lg { + border-bottom: solid; + border-color: white; + border-bottom-width: 2px; +} + + +// PolyNotFound +.navbar-brand { + font-family: cursive; + font-weight: bold; + font-size: x-large; + margin-left: 15px; + color: white; +} + + +.monLi { + margin: 0px 10px 0px 10px; +} + + +.nav-link { + color: white; +} +.nav-link:hover { + color: grey; +} +.myActiveLink { + text-decoration: underline; +} + + +.btnDeconnexion { + font-size: medium; + margin: 0px 10px 0px 10px +} +.btnDeconnexion:hover { + color: grey; +} + + +img { + border: solid 2px white; + border-radius: 50px; + margin: 0px 10px 0px 15px; + width: 40px; + height: 40px; +} +img:hover { + cursor: pointer; +} + + +// -------------------------------------------------------------------- + + +::ng-deep .mat-slide-toggle-thumb { + background-color: #c8c8c8; +} + +::ng-deep .mat-slide-toggle-bar { + background-color: #ffffff; +} + +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-thumb { + background-color: #ffffff; +} + +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-bar { + background-color: #646464; +} diff --git a/admin/src/app/admin/utils/navbar-admin/navbar-admin.component.spec.ts b/admin/src/app/admin/utils/navbar-admin/navbar-admin.component.spec.ts new file mode 100644 index 0000000..44f2cf6 --- /dev/null +++ b/admin/src/app/admin/utils/navbar-admin/navbar-admin.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NavbarAdminComponent } from './navbar-admin.component'; + +describe('NavbarAdminComponent', () => { + let component: NavbarAdminComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ NavbarAdminComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NavbarAdminComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/admin/utils/navbar-admin/navbar-admin.component.ts b/admin/src/app/admin/utils/navbar-admin/navbar-admin.component.ts new file mode 100644 index 0000000..bbf07fb --- /dev/null +++ b/admin/src/app/admin/utils/navbar-admin/navbar-admin.component.ts @@ -0,0 +1,38 @@ +import { Component } from '@angular/core'; +import {Router} from "@angular/router"; +import {ProfilService} from "../../../utils/profil/profil.service"; +import {MessageService} from "../../../utils/message/message.service"; + +@Component({ + selector: 'app-navbar-admin', + templateUrl: './navbar-admin.component.html', + styleUrls: ['./navbar-admin.component.scss'] +}) +export class NavbarAdminComponent +{ + routes: string[] = [ + "/admin", // 0 + "/admin/userList", // 1 + "/admin/adList", // 2 + "/admin/myProfil", // 3 + ]; + + url = this.router.url; + + constructor( private router: Router, + public profilService: ProfilService, + private messageService: MessageService ) { } + + onDeconnexion(): void + { + this.messageService + .delete('user/logout') + .subscribe(retour => this.onDeconnexionCallback(retour), err => this.onDeconnexionCallback(err)); + } + + onDeconnexionCallback(retour: any): void + { + if(retour.status !== "success") console.log(retour); + } + +} diff --git a/admin/src/app/app-routing.module.ts b/admin/src/app/app-routing.module.ts new file mode 100644 index 0000000..a61b1b1 --- /dev/null +++ b/admin/src/app/app-routing.module.ts @@ -0,0 +1,27 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import {PageLoginComponent} from "./beforeConnexion/login/page-login/page-login.component"; +import {PageRegisterComponent} from "./beforeConnexion/register/page-register/page-register.component"; +import {PageAdListAdminComponent} from "./admin/adList/page-ad-list-admin/page-ad-list-admin.component"; +import {PageProfilAdminComponent} from "./admin/myProfil/page-profil-admin/page-profil-admin.component"; +import {PageUserListComponent} from "./admin/userList/page-user-list/page-user-list.component"; + +const routes: Routes = [ + + // Before connexion + { path: '', component: PageLoginComponent }, + { path: 'login', component: PageLoginComponent }, + { path: 'register', component: PageRegisterComponent }, + + // Admin + { path: 'admin', component: PageUserListComponent }, + { path: 'admin/userList', component: PageUserListComponent }, + { path: 'admin/adList', component: PageAdListAdminComponent }, + { path: 'admin/myProfil', component: PageProfilAdminComponent }, +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/admin/src/app/app.component.html b/admin/src/app/app.component.html new file mode 100644 index 0000000..d5d92f3 --- /dev/null +++ b/admin/src/app/app.component.html @@ -0,0 +1,2 @@ + + diff --git a/admin/src/app/app.component.scss b/admin/src/app/app.component.scss new file mode 100644 index 0000000..22a5665 --- /dev/null +++ b/admin/src/app/app.component.scss @@ -0,0 +1,24 @@ +::ng-deep snack-bar-container.custom-class { + //background: yellow; +} +::ng-deep .custom-class .mat-simple-snackbar { + //color: green; + justify-content: center; +} + + +// aura +::ng-deep .mat-checkbox-ripple .mat-ripple-element { + background-color: grey !important; +} + +// contenu coche +::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background { + background-color: black !important; +} + +// indeterminate +::ng-deep .mat-checkbox .mat-checkbox-frame { + border: solid 1px black !important; + background-color: white !important; +} diff --git a/admin/src/app/app.component.spec.ts b/admin/src/app/app.component.spec.ts new file mode 100644 index 0000000..1391984 --- /dev/null +++ b/admin/src/app/app.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ + AppComponent + ], + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'admin'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app.title).toEqual('admin'); + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('.content span')?.textContent).toContain('admin app is running!'); + }); +}); diff --git a/admin/src/app/app.component.ts b/admin/src/app/app.component.ts new file mode 100644 index 0000000..e81341b --- /dev/null +++ b/admin/src/app/app.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent { + title = 'admin'; +} diff --git a/admin/src/app/app.module.ts b/admin/src/app/app.module.ts new file mode 100644 index 0000000..3537350 --- /dev/null +++ b/admin/src/app/app.module.ts @@ -0,0 +1,93 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { PageLoginComponent } from './beforeConnexion/login/page-login/page-login.component'; +import { PopupForgottenPasswordComponent } from './beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component'; +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {HttpClientModule} from "@angular/common/http"; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {MatTableModule} from "@angular/material/table"; +import {MatSortModule} from "@angular/material/sort"; +import {MatRadioModule} from "@angular/material/radio"; +import {MatDialogModule} from "@angular/material/dialog"; +import {MatSnackBarModule} from "@angular/material/snack-bar"; +import {MatButtonModule} from "@angular/material/button"; +import {MatCheckboxModule} from "@angular/material/checkbox"; +import { PageRegisterComponent } from './beforeConnexion/register/page-register/page-register.component'; +import { PopupConfirmationComponent } from './beforeConnexion/register/popup-confirmation/popup-confirmation.component'; +import { InputInterestsRegisterComponent } from './beforeConnexion/register/input-interests-register/input-interests-register.component'; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatChipsModule} from "@angular/material/chips"; +import {MatIconModule} from "@angular/material/icon"; +import {MatAutocompleteModule} from "@angular/material/autocomplete"; +import {MatStepperModule} from "@angular/material/stepper"; +import { NavbarBeforeConnexionComponent } from './beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component'; +import { PageProfilAdminComponent } from './admin/myProfil/page-profil-admin/page-profil-admin.component'; +import { PopupUpdateAdminComponent } from './admin/myProfil/popup-update-admin/popup-update-admin.component'; +import { PageAdListAdminComponent } from './admin/adList/page-ad-list-admin/page-ad-list-admin.component'; +import { PopupDeleteAdAdminComponent } from './admin/adList/popup-delete-ad-admin/popup-delete-ad-admin.component'; +import { PopupVisualizeImagesAdminComponent } from './admin/adList/popup-visualize-images-admin/popup-visualize-images-admin.component'; +import {MatDividerModule} from "@angular/material/divider"; +import {MatSelectModule} from "@angular/material/select"; +import {MatPaginatorModule} from "@angular/material/paginator"; +import {MatGridListModule} from "@angular/material/grid-list"; +import { PageUserListComponent } from './admin/userList/page-user-list/page-user-list.component'; +import { InputInterestsAdminComponent } from './admin/userList/input-interests-admin/input-interests-admin.component'; +import { PopupCreateUserComponent } from './admin/userList/popup-create-user/popup-create-user.component'; +import { PopupDeleteUserComponent } from './admin/userList/popup-delete-user/popup-delete-user.component'; +import {MatSlideToggleModule} from "@angular/material/slide-toggle"; +import { NavbarAdminComponent } from './admin/utils/navbar-admin/navbar-admin.component'; +import {MatInputModule} from "@angular/material/input"; + +@NgModule({ + declarations: [ + AppComponent, + PageLoginComponent, + PopupForgottenPasswordComponent, + PageRegisterComponent, + PopupConfirmationComponent, + InputInterestsRegisterComponent, + NavbarBeforeConnexionComponent, + PageProfilAdminComponent, + PopupUpdateAdminComponent, + PageAdListAdminComponent, + PopupDeleteAdAdminComponent, + PopupVisualizeImagesAdminComponent, + PageUserListComponent, + InputInterestsAdminComponent, + PopupCreateUserComponent, + PopupDeleteUserComponent, + NavbarAdminComponent + ], + imports: [ + BrowserModule, + AppRoutingModule, + BrowserAnimationsModule, + MatTableModule, + MatSortModule, + MatRadioModule, + FormsModule, + HttpClientModule, + MatDialogModule, + MatButtonModule, + MatCheckboxModule, + MatSnackBarModule, + MatFormFieldModule, + MatInputModule, + MatChipsModule, + MatIconModule, + MatAutocompleteModule, + ReactiveFormsModule, + MatStepperModule, + MatDividerModule, + MatSelectModule, + MatPaginatorModule, + MatGridListModule, + MatSlideToggleModule, + ], + providers: [], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/admin/src/app/beforeConnexion/login/page-login/page-login.component.html b/admin/src/app/beforeConnexion/login/page-login/page-login.component.html new file mode 100644 index 0000000..675270e --- /dev/null +++ b/admin/src/app/beforeConnexion/login/page-login/page-login.component.html @@ -0,0 +1,36 @@ +
+
+ + + +
+
+ + +
+

StreamNotFound

+ User Icon +
+ + +
+ + + +
+ {{errorMessage}} +
+ +
+ + + + +
+
+ + +
+
diff --git a/admin/src/app/beforeConnexion/login/page-login/page-login.component.scss b/admin/src/app/beforeConnexion/login/page-login/page-login.component.scss new file mode 100644 index 0000000..8924202 --- /dev/null +++ b/admin/src/app/beforeConnexion/login/page-login/page-login.component.scss @@ -0,0 +1,271 @@ +html { + background-color: #56baed; +} + +body { + font-family: "Poppins", sans-serif; + height: 100vh; +} + +a { + color: #5E89FF; + display:inline-block; + text-decoration: none; + font-weight: 400; +} + +h2 { + text-align: center; + font-size: 16px; + font-weight: 600; + text-transform: uppercase; + display:inline-block; + margin: 40px 8px 10px 8px; + color: #cccccc; +} + + + +/* STRUCTURE */ + +.wrapper { + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; + width: 100%; + min-height: 80%; + padding: 20px; +} + +#formContent { + -webkit-border-radius: 10px 10px 10px 10px; + border-radius: 10px 10px 10px 10px; + background: #fff; + padding: 30px; + width: 90%; + max-width: 450px; + position: relative; + padding: 0px; + -webkit-box-shadow: 0 30px 60px 0 rgba(0,0,0,0.3); + box-shadow: 0 30px 60px 0 rgba(0,0,0,0.3); + text-align: center; +} + +#formFooter { + background-color: #f6f6f6; + border-top: 1px solid #dce8f1; + padding: 25px; + text-align: center; + -webkit-border-radius: 0 0 10px 10px; + border-radius: 0 0 10px 10px; +} + + + +/* TABS */ + +h2.inactive { + color: #cccccc; +} + +h2.active { + color: #0d0d0d; + border-bottom: 2px solid #5fbae9; +} + + + +/* FORM TYPOGRAPHY*/ + +input[type=button], input[type=submit], input[type=reset] { + background-color: #5E89FF; + border: none; + color: white; + padding: 15px 80px; + text-align: center; + text-decoration: none; + display: inline-block; + text-transform: uppercase; + font-size: 13px; + -webkit-box-shadow: 0 10px 30px 0 rgba(95,186,233,0.4); + box-shadow: 0 10px 30px 0 rgba(95,186,233,0.4); + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; + margin: 5px 20px 40px 20px; + -webkit-transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -ms-transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; + transition: all 0.3s ease-in-out; +} + +input[type=button]:hover, input[type=submit]:hover, input[type=reset]:hover { + background-color: #39ace7; +} + +input[type=button]:active, input[type=submit]:active, input[type=reset]:active { + -moz-transform: scale(0.95); + -webkit-transform: scale(0.95); + -o-transform: scale(0.95); + -ms-transform: scale(0.95); + transform: scale(0.95); +} + +input[type=text], input[type=password] { + background-color: #f6f6f6; + border: none; + color: #0d0d0d; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 5px; + width: 85%; + border: 2px solid #f6f6f6; + -webkit-transition: all 0.5s ease-in-out; + -moz-transition: all 0.5s ease-in-out; + -ms-transition: all 0.5s ease-in-out; + -o-transition: all 0.5s ease-in-out; + transition: all 0.5s ease-in-out; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} + + + +input[type=text]:focus, input[type=password]:focus { + background-color: #fff; + border-bottom: 2px solid #5fbae9; +} + +input[type=text]::placeholder, input[type=password]::placeholder { + color: #cccccc; +} + +.bg{ + margin: 0; + padding: 0; + height: 100vh; + width: 100vw; + overflow-y: hidden; + overflow-x: hidden; +} + +/* ANIMATIONS */ + +/* Simple CSS3 Fade-in-down Animation */ +.fadeInDown { + -webkit-animation-name: fadeInDown; + animation-name: fadeInDown; + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +@-webkit-keyframes fadeInDown { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInDown { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +/* Simple CSS3 Fade-in Animation */ +@-webkit-keyframes fadeIn { from { opacity:0; } to { opacity:1; } } +@-moz-keyframes fadeIn { from { opacity:0; } to { opacity:1; } } +@keyframes fadeIn { from { opacity:0; } to { opacity:1; } } + +.fadeIn { + opacity:0; + -webkit-animation:fadeIn ease-in 1; + -moz-animation:fadeIn ease-in 1; + animation:fadeIn ease-in 1; + + -webkit-animation-fill-mode:forwards; + -moz-animation-fill-mode:forwards; + animation-fill-mode:forwards; + + -webkit-animation-duration:1s; + -moz-animation-duration:1s; + animation-duration:1s; +} + +.fadeIn.first { + -webkit-animation-delay: 0.4s; + -moz-animation-delay: 0.4s; + animation-delay: 0.4s; +} + +.fadeIn.second { + -webkit-animation-delay: 0.6s; + -moz-animation-delay: 0.6s; + animation-delay: 0.6s; +} + +.fadeIn.third { + -webkit-animation-delay: 0.8s; + -moz-animation-delay: 0.8s; + animation-delay: 0.8s; +} + +.fadeIn.fourth { + -webkit-animation-delay: 1s; + -moz-animation-delay: 1s; + animation-delay: 1s; +} + +/* Simple CSS3 Fade-in Animation */ +.underlineHover:after { + display: block; + left: 0; + bottom: -10px; + width: 0; + height: 2px; + //background-color: #5E89FF; + background-color: #5E89FF; + content: ""; + transition: width 0.2s; +} + +.underlineHover:hover { + color: #0d0d0d; +} + +.underlineHover:hover:after{ + width: 100%; +} + +h1{ + color: black; +} + +/* OTHERS */ + +*:focus { + outline: none; +} + +#icon { + width:30%; +} diff --git a/admin/src/app/beforeConnexion/login/page-login/page-login.component.spec.ts b/admin/src/app/beforeConnexion/login/page-login/page-login.component.spec.ts new file mode 100644 index 0000000..a4ee677 --- /dev/null +++ b/admin/src/app/beforeConnexion/login/page-login/page-login.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageLoginComponent } from './page-login.component'; + +describe('PageLoginComponent', () => { + let component: PageLoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageLoginComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageLoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/beforeConnexion/login/page-login/page-login.component.ts b/admin/src/app/beforeConnexion/login/page-login/page-login.component.ts new file mode 100644 index 0000000..555e496 --- /dev/null +++ b/admin/src/app/beforeConnexion/login/page-login/page-login.component.ts @@ -0,0 +1,101 @@ +import {Component, OnInit} from '@angular/core'; +import {Router} from "@angular/router"; +import {MatDialog} from "@angular/material/dialog"; +import {PopupForgottenPasswordComponent} from "../popup-forgotten-password/popup-forgotten-password.component"; +import {MatSnackBar} from "@angular/material/snack-bar"; +import {ProfilService} from "../../../utils/profil/profil.service"; +import {MessageService} from "../../../utils/message/message.service"; +import {ThemeService} from "../../../utils/theme/theme.service"; + + + +@Component({ + selector: 'app-page-login', + templateUrl: './page-login.component.html', + styleUrls: ['./page-login.component.scss'] +}) +export class PageLoginComponent implements OnInit +{ + email: string = "" ; + password: string = "" ; + hasError: boolean = false; + errorMessage: string = ""; + + + constructor( private messageService: MessageService, + private router: Router, + public themeService: ThemeService, + public dialog: MatDialog, + private snackBar: MatSnackBar, + private profilService: ProfilService) { } + + + ngOnInit(): void {} + + + onSeConnecter(): void + { + this.checkError(); + + if(!this.hasError) + { + let data = { + email: this.email, + hashPass: this.password + }; + this.messageService + .post('user/auth', data) + .subscribe( retour => this.onSeConnecterCallback(retour), err => this.onSeConnecterCallback(err)); + } + } + + + onSeConnecterCallback(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + this.errorMessage = retour.error.reason; + this.hasError = true; + } + else { + this.profilService.setId(retour.data.id); + this.profilService.setProfileImageUrl(retour.data.profileImageUrl); + if(retour.data.role.name === "user") this.router.navigateByUrl( '/user/search'); + else if(retour.data.role.name === "advertiser") this.router.navigateByUrl( '/advertiser/adList'); + else if(retour.data.role.name === "admin" || retour.data.role.name === "superAdmin") this.router.navigateByUrl( '/admin/userList'); + } + } + + + onForgottenPassword(): void + { + this.dialog + .open(PopupForgottenPasswordComponent, {width: '30%'}) + .afterClosed() + .subscribe(result => { + if((result !== null) && (result !== undefined)) + { + const config = { duration: 5000, panelClass: "custom-class" }; + this.snackBar.open( "Un mail de réinitialisation de mot de passe vous a été envoyé.", "", config); + } + }); + } + + + checkError(): void + { + if(this.email === "") { + this.errorMessage = "Veuillez remplir le champ email" ; + this.hasError = true; + } + else if(this.password === "") { + this.errorMessage = "Veuillez remplir le champ mot de passe" ; + this.hasError = true; + } + else { + this.errorMessage = "" ; + this.hasError = false; + } + } + +} diff --git a/admin/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.html b/admin/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.html new file mode 100644 index 0000000..c34b58e --- /dev/null +++ b/admin/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.html @@ -0,0 +1,24 @@ +

Récupération du mot de passe

+ +
+ + +
+ + Email + + +
+ + +
+ {{errorMessage}} +
+ + + + + + + + diff --git a/admin/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.scss b/admin/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.scss new file mode 100644 index 0000000..fa75013 --- /dev/null +++ b/admin/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.scss @@ -0,0 +1,12 @@ +h4 { + text-align: center; +} + +.myDiv { + text-align: center; + font-size: small; +} + +.myError { + text-align: center; +} diff --git a/admin/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.spec.ts b/admin/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.spec.ts new file mode 100644 index 0000000..ebf101c --- /dev/null +++ b/admin/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupForgottenPasswordComponent } from './popup-forgotten-password.component'; + +describe('PopupForgottenPasswordComponent', () => { + let component: PopupForgottenPasswordComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupForgottenPasswordComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupForgottenPasswordComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.ts b/admin/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.ts new file mode 100644 index 0000000..1ff70ce --- /dev/null +++ b/admin/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.ts @@ -0,0 +1,47 @@ +import { Component } from '@angular/core'; +import {MatDialogRef} from "@angular/material/dialog"; + + + +@Component({ + selector: 'app-popup-forgotten-password', + templateUrl: './popup-forgotten-password.component.html', + styleUrls: ['./popup-forgotten-password.component.scss'] +}) +export class PopupForgottenPasswordComponent +{ + email: string; + hasError: boolean = false; + errorMessage: string = ""; + + + constructor(public dialogRef: MatDialogRef) {} + + + // Click sur valider + onValidate() + { + if(this.email.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'email'." ; + this.hasError = true; + } + else if(!this.isValidEmail(this.email)) { + this.errorMessage = "Email invalide." ; + this.hasError = true; + } + else { + this.errorMessage = "" ; + this.hasError = false; + this.dialogRef.close(true); + } + } + + + // Indique si email a bien le format d'un email + isValidEmail(email): boolean + { + let re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); + } + +} diff --git a/admin/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.html b/admin/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.html new file mode 100644 index 0000000..2a7c484 --- /dev/null +++ b/admin/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.html @@ -0,0 +1,43 @@ + + + + + Centres d'intérêt + + + + + + + {{interest}} + + + + + + + + + + + + {{interest}} + + + + + + diff --git a/admin/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.scss b/admin/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/admin/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.spec.ts b/admin/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.spec.ts new file mode 100644 index 0000000..9917b1a --- /dev/null +++ b/admin/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InputInterestsRegisterComponent } from './input-interests-register.component'; + +describe('InputInterestsRegisterComponent', () => { + let component: InputInterestsRegisterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ InputInterestsRegisterComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InputInterestsRegisterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.ts b/admin/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.ts new file mode 100644 index 0000000..8dba84e --- /dev/null +++ b/admin/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.ts @@ -0,0 +1,121 @@ +import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; +import {COMMA, ENTER} from "@angular/cdk/keycodes"; +import {FormControl} from "@angular/forms"; +import {Observable} from "rxjs"; +import {map, startWith} from "rxjs/operators"; +import {MatChipInputEvent} from "@angular/material/chips"; +import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete"; +import {MessageService} from "../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-input-interests-register', + templateUrl: './input-interests-register.component.html', + styleUrls: ['./input-interests-register.component.scss'] +}) +export class InputInterestsRegisterComponent implements OnInit +{ + selectable = true; + removable = true; + separatorKeysCodes: number[] = [ENTER, COMMA]; + formControl = new FormControl(); + filteredInterests: Observable; + @Input() myInterests: string[] = []; + allInterests: string[] = []; + @Output() eventEmitter = new EventEmitter(); + @ViewChild('tagInput') tagInput: ElementRef; + interestsNotSelected: string[] = []; + + + constructor( private messageService: MessageService ) {} + + + ngOnInit(): void + { + this.filteredInterests = this.formControl.valueChanges.pipe( + startWith(null), + map((fruit: string | null) => fruit ? this._filter(fruit) : this.interestsNotSelected.slice())); + + this.messageService + .get("misc/getInterests") + .subscribe( retour => { + + if(retour.status !== "success") { + console.log(retour); + } + else { + this.allInterests = []; + for(let elt of retour.data) + { + this.allInterests.push(elt.interest); + this.interestsNotSelected.push(elt.interest); + } + } + }); + } + + + add(event: MatChipInputEvent): void + { + const value = (event.value || '').trim(); + const index = this.interestsNotSelected.indexOf(value); + if (value && (index !== -1) && (!this.myInterests.includes(value))) + { + this.myInterests.push(value); + event.chipInput!.clear(); + this.formControl.setValue(null); + this.eventEmitter.emit(this.myInterests); + this.interestsNotSelected.splice(index, 1); + } + } + + + remove(interest: string): void + { + // supprimer 'interest' de 'myInterest' + const index = this.myInterests.indexOf(interest); + if (index >= 0) this.myInterests.splice(index, 1); + this.eventEmitter.emit(this.myInterests); + + // remmettre 'interest' dans 'interestsNotSelected' + if(!this.interestsNotSelected.includes(interest)) + { + const indexOfAutres = this.interestsNotSelected.indexOf("Autres"); + if(indexOfAutres !== -1) + { + this.interestsNotSelected.splice(indexOfAutres, 1); + if(interest !== "Autres") this.interestsNotSelected.push(interest); + this.interestsNotSelected.sort(); + this.interestsNotSelected.push("Autres"); + } + else { + this.interestsNotSelected.push(interest); + if(interest !== "Autres") this.interestsNotSelected.sort(); + } + } + } + + + selected(event: MatAutocompleteSelectedEvent): void + { + const value = event.option.viewValue; + if(!this.myInterests.includes(value)) + { + this.myInterests.push(value); + const index = this.interestsNotSelected.indexOf(value); + this.interestsNotSelected.splice(index, 1); + } + this.tagInput.nativeElement.value = ''; + this.formControl.setValue(null); + this.eventEmitter.emit(this.myInterests); + } + + + private _filter(value: string): string[] + { + const filterValue = value.toLowerCase(); + return this.interestsNotSelected.filter(fruit => fruit.toLowerCase().includes(filterValue)); + } + +} diff --git a/admin/src/app/beforeConnexion/register/page-register/page-register.component.html b/admin/src/app/beforeConnexion/register/page-register/page-register.component.html new file mode 100644 index 0000000..fb6e3ac --- /dev/null +++ b/admin/src/app/beforeConnexion/register/page-register/page-register.component.html @@ -0,0 +1,162 @@ +
+
+ + + + + + + + + +
+ + + Utilisateur standard    + Annonceur + + + +
+ +
+
+
+ + + + +
+ + + + +
+ {{errorMessage}} +
+ + +
+ + +
+
+
+ +
+ +
+
+ + + + + + + +
+
+ + +
+

Compte

+ + + + Pseudo + + +
+ + + + Mot de passe + + +
+ + + + Confirmation mot de passe + + +
+ + +
+

Informations personelles

+ + + + Email + + +
+ + + + Homme     + Femme + +

+ + + + Date de naissance + + + + + +
+ +
+
+ + + + + + + + + + + Entreprise + + +
+ + + + Pseudo + + +
+ + + + Email + + +
+ + + + Mot de passe + + +
+ + + + Confirmation mot de passe + + + +
diff --git a/admin/src/app/beforeConnexion/register/page-register/page-register.component.scss b/admin/src/app/beforeConnexion/register/page-register/page-register.component.scss new file mode 100644 index 0000000..5f0dc53 --- /dev/null +++ b/admin/src/app/beforeConnexion/register/page-register/page-register.component.scss @@ -0,0 +1,47 @@ +.myContainer { + width: 100vw; + height: 100vh; +} + + +mat-stepper { + width: 60%; + margin: 10vh auto; + border: solid 1px black; + border-radius: 20px; +} + + +.leftCol { + border-right: solid 1px #dcdcdc; +} + + +.myRow { + margin: 15px 0px 15px 0px; +} +.myLabel { + text-align: right; + padding: 0px 5px 0px 0px; + margin: 0px; + font-weight: bold; +} +.myValue { + text-align: left; + padding: 0px 0px 0px 5px; + margin: 0px; +} + + +// ------------------------------------------------------------------------- + + +::ng-deep .mat-radio-inner-circle { + color: black !important; + background-color: black !important; +} + +::ng-deep .mat-radio-outer-circle{ + color: black !important; + border: solid 1px gray !important; +} diff --git a/admin/src/app/beforeConnexion/register/page-register/page-register.component.spec.ts b/admin/src/app/beforeConnexion/register/page-register/page-register.component.spec.ts new file mode 100644 index 0000000..5cff194 --- /dev/null +++ b/admin/src/app/beforeConnexion/register/page-register/page-register.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageRegisterComponent } from './page-register.component'; + +describe('PageRegisterComponent', () => { + let component: PageRegisterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageRegisterComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageRegisterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/beforeConnexion/register/page-register/page-register.component.ts b/admin/src/app/beforeConnexion/register/page-register/page-register.component.ts new file mode 100644 index 0000000..788c8be --- /dev/null +++ b/admin/src/app/beforeConnexion/register/page-register/page-register.component.ts @@ -0,0 +1,134 @@ +import { Component } from '@angular/core'; +import {PopupConfirmationComponent} from "../popup-confirmation/popup-confirmation.component"; +import {MessageService} from "../../../utils/message/message.service"; +import {Router} from "@angular/router"; +import {MatDialog} from "@angular/material/dialog"; +import {ThemeService} from "../../../utils/theme/theme.service"; + + + +@Component({ + selector: 'app-page-register', + templateUrl: './page-register.component.html', + styleUrls: ['./page-register.component.scss'] +}) +export class PageRegisterComponent +{ + password: string = ""; + confirmPassword: string = ""; + hasError: boolean = false; + errorMessage: string = ""; + user = { + _id: "", + login: "", + hashPass: "", + email: "", + role: { + name: "user", + permission: 0, + isAccepted: false, + }, + profileImageUrl: "", + dateOfBirth: null, + gender: "man", + interests: [], + company: "", + isActive: true, + createdAt: new Date(), + updatedAt: new Date(), + lastConnexion: null + }; + + + constructor( private messageService: MessageService, + private router: Router, + public dialog: MatDialog, + public themeService: ThemeService ) { } + + + // Envoie de l'utilisateur au backend + onEnregistrer(): void + { + this.checkField(); + if(!this.hasError) + { + let data: any = Object.assign({}, this.user); + if(this.user.role.name === "user") data.role = "user" ; + else data.role = "advertiser"; + data.hashPass = this.password; + this.messageService + .post('user/create', data) + .subscribe(retour => this.onEnregistrerCallback(retour), err => this.onEnregistrerCallback(err)); + } + } + + + // Gestion de la réponse du backend + onEnregistrerCallback(retour): void + { + if(retour.status !== "success") { + console.log(retour); + } + else + { + const config = { + width: '25%', + data: {roleName: this.user.role.name} + }; + this.dialog + .open(PopupConfirmationComponent, config) + .afterClosed() + .subscribe(result => this.router.navigateByUrl( '/login' )); + } + } + + + // Check les champs saisies par l'utilisateur + checkField(): void + { + if((this.user.role.name === 'advertiser') && (this.user.company.length === 0)) { + this.errorMessage = "Veuillez remplir le champ 'entreprise'."; + this.hasError = true; + } + else if(this.user.login.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'pseudo'."; + this.hasError = true; + } + else if(this.user.email.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'email'."; + this.hasError = true; + } + else if(!this.isValidEmail(this.user.email)) { + this.errorMessage = "Email invalide."; + this.hasError = true; + } + else if(this.password.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'mot de passe'."; + this.hasError = true; + } + else if(this.password !== this.confirmPassword) { + this.errorMessage = "Le mot de passe est différent de sa confirmation."; + this.hasError = true; + } + else { + this.errorMessage = "" ; + this.hasError = false; + } + } + + + // Indique si email a bien le format d'un email + isValidEmail(email): boolean + { + let re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); + } + + + // Récupère la liste des centres d'intérets (car celle-ci est remplie à l'aide d'un component intermédiaire) + onEventInputInterests(myInterets: string[]): void + { + this.user.interests = myInterets; + } + +} diff --git a/admin/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.html b/admin/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.html new file mode 100644 index 0000000..1cd51fe --- /dev/null +++ b/admin/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.html @@ -0,0 +1,11 @@ +

+ Votre inscription a bien été effectuée. +

+ +

+ Votre inscription est en cours de validation. +

+ +
+ +
diff --git a/admin/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.scss b/admin/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.scss new file mode 100644 index 0000000..85730e0 --- /dev/null +++ b/admin/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.scss @@ -0,0 +1,7 @@ +p { + font-size: small; +} + +div { + font-size: small; +} diff --git a/admin/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.spec.ts b/admin/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.spec.ts new file mode 100644 index 0000000..d6f9908 --- /dev/null +++ b/admin/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupConfirmationComponent } from './popup-confirmation.component'; + +describe('PopupConfirmationComponent', () => { + let component: PopupConfirmationComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupConfirmationComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupConfirmationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.ts b/admin/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.ts new file mode 100644 index 0000000..59e3325 --- /dev/null +++ b/admin/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.ts @@ -0,0 +1,13 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; + +@Component({ + selector: 'app-popup-confirmation', + templateUrl: './popup-confirmation.component.html', + styleUrls: ['./popup-confirmation.component.scss'] +}) +export class PopupConfirmationComponent +{ + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data) {} +} diff --git a/admin/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.html b/admin/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.html new file mode 100644 index 0000000..d4ad9f5 --- /dev/null +++ b/admin/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.html @@ -0,0 +1,40 @@ + +
+ +
+ + + + + + +
+ +
diff --git a/admin/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.scss b/admin/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.scss new file mode 100644 index 0000000..e1fefaa --- /dev/null +++ b/admin/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.scss @@ -0,0 +1,79 @@ +.navbar { + background-color: black; + height: 60px; + font-size: medium; + color: white; +} + + +.navbar-expand-lg { + border-bottom: solid; + border-color: white; + border-bottom-width: 2px; +} + + +// PolyNotFound +.navbar-brand { + font-family: cursive; + font-weight: bold; + font-size: x-large; + margin-left: 15px; + color: white; +} + + +// Recherche, Mes Playlists, Historique +.nav-link { + color: white; +} +.nav-link:hover { + color: grey; +} + + +// Bonton deconnexion +.btnDeconnexion { + font-size: medium; + margin: 0px 10px 0px 10px +} +.btnDeconnexion:hover { + color: grey; +} + + +.monLi { + margin: 0px 10px 0px 10px; +} + + +img { + border: solid 2px white; + border-radius: 50px; + margin: 0px 10px 0px 15px; + width: 40px; + height: 40px; +} +img:hover { + cursor: pointer; +} + + +// -------------------------------------------------------------------- + + +::ng-deep .mat-slide-toggle-thumb { + background-color: #c8c8c8; +} + +::ng-deep .mat-slide-toggle-bar { + background-color: #ffffff; +} + +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-thumb { + background-color: #ffffff; +} + +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-bar { + background-color: #646464; +} diff --git a/admin/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.spec.ts b/admin/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.spec.ts new file mode 100644 index 0000000..f3f7f27 --- /dev/null +++ b/admin/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NavbarBeforeConnexionComponent } from './navbar-before-connexion.component'; + +describe('NavbarBeforeConnexionComponent', () => { + let component: NavbarBeforeConnexionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ NavbarBeforeConnexionComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NavbarBeforeConnexionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/admin/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.ts b/admin/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.ts new file mode 100644 index 0000000..4a3f05e --- /dev/null +++ b/admin/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.ts @@ -0,0 +1,11 @@ +import {Component, Input} from '@angular/core'; + +@Component({ + selector: 'app-navbar-before-connexion', + templateUrl: './navbar-before-connexion.component.html', + styleUrls: ['./navbar-before-connexion.component.scss'] +}) +export class NavbarBeforeConnexionComponent +{ + @Input() pour = "login"; +} diff --git a/admin/src/app/utils/message/message.service.spec.ts b/admin/src/app/utils/message/message.service.spec.ts new file mode 100644 index 0000000..1db761b --- /dev/null +++ b/admin/src/app/utils/message/message.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { MessageService } from './message.service'; + +describe('MessageService', () => { + let service: MessageService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(MessageService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/admin/src/app/utils/message/message.service.ts b/admin/src/app/utils/message/message.service.ts new file mode 100644 index 0000000..c20d5b1 --- /dev/null +++ b/admin/src/app/utils/message/message.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import {HttpClient, HttpParams} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {environment} from "../../../environments/environment"; + +@Injectable({ + providedIn: 'root' +}) +export class MessageService +{ + + constructor( private http: HttpClient ) { } + + post(url: string, data: any): Observable + { + const urlComplete = environment.debutUrl + url ; + return this.http.post(urlComplete, data, {withCredentials: true}); + } + + get(url: string, params:HttpParams = new HttpParams()): Observable + { + const urlComplete = environment.debutUrl + url ; + return this.http.get(urlComplete,{ withCredentials: true, params: params }); + } + + put(url: string, data: any): Observable + { + const urlComplete = environment.debutUrl + url ; + return this.http.put(urlComplete, data, {withCredentials: true}); + } + + delete(url: string): Observable + { + const urlComplete = environment.debutUrl + url ; + return this.http.delete(urlComplete,{withCredentials: true}); + } + +} diff --git a/admin/src/app/utils/profil/profil.service.spec.ts b/admin/src/app/utils/profil/profil.service.spec.ts new file mode 100644 index 0000000..5cee000 --- /dev/null +++ b/admin/src/app/utils/profil/profil.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ProfilService } from './profil.service'; + +describe('ProfilService', () => { + let service: ProfilService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ProfilService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/admin/src/app/utils/profil/profil.service.ts b/admin/src/app/utils/profil/profil.service.ts new file mode 100644 index 0000000..4bbe5ea --- /dev/null +++ b/admin/src/app/utils/profil/profil.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class ProfilService +{ + + getId(): string + { + return localStorage.getItem('id'); + } + + getProfileImageUrl(): string + { + return localStorage.getItem('profileImageUrl'); + } + + setId(id: string): void + { + localStorage.setItem('id', id); + } + + setProfileImageUrl(profileImageUrl: string): void + { + localStorage.setItem('profileImageUrl', profileImageUrl); + } + +} diff --git a/admin/src/app/utils/theme/theme.service.spec.ts b/admin/src/app/utils/theme/theme.service.spec.ts new file mode 100644 index 0000000..1c2957b --- /dev/null +++ b/admin/src/app/utils/theme/theme.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ThemeService } from './theme.service'; + +describe('ThemeService', () => { + let service: ThemeService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ThemeService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/admin/src/app/utils/theme/theme.service.ts b/admin/src/app/utils/theme/theme.service.ts new file mode 100644 index 0000000..00768e5 --- /dev/null +++ b/admin/src/app/utils/theme/theme.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class ThemeService +{ + + isLightTheme = true; + + getClassTheme(): string + { + if(this.isLightTheme) return "lightTheme" ; + else return "darkTheme" + } + +} diff --git a/admin/src/assets/.gitkeep b/admin/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/admin/src/assets/darkBackground.webp b/admin/src/assets/darkBackground.webp new file mode 100644 index 0000000..0d0692b Binary files /dev/null and b/admin/src/assets/darkBackground.webp differ diff --git a/admin/src/assets/lightBackground.jpg b/admin/src/assets/lightBackground.jpg new file mode 100644 index 0000000..164cb51 Binary files /dev/null and b/admin/src/assets/lightBackground.jpg differ diff --git a/admin/src/assets/logo.png b/admin/src/assets/logo.png new file mode 100644 index 0000000..93b9375 Binary files /dev/null and b/admin/src/assets/logo.png differ diff --git a/admin/src/assets/logo_plateforms/dailymotion.png b/admin/src/assets/logo_plateforms/dailymotion.png new file mode 100644 index 0000000..d35ee8a Binary files /dev/null and b/admin/src/assets/logo_plateforms/dailymotion.png differ diff --git a/admin/src/assets/logo_plateforms/youtube.png b/admin/src/assets/logo_plateforms/youtube.png new file mode 100644 index 0000000..5924c8d Binary files /dev/null and b/admin/src/assets/logo_plateforms/youtube.png differ diff --git a/admin/src/assets/play.png b/admin/src/assets/play.png new file mode 100644 index 0000000..194f73b Binary files /dev/null and b/admin/src/assets/play.png differ diff --git a/admin/src/assets/profil.png b/admin/src/assets/profil.png new file mode 100644 index 0000000..b35b2e4 Binary files /dev/null and b/admin/src/assets/profil.png differ diff --git a/admin/src/assets/uploadFile.png b/admin/src/assets/uploadFile.png new file mode 100644 index 0000000..cff9f38 Binary files /dev/null and b/admin/src/assets/uploadFile.png differ diff --git a/admin/src/environments/environment.prod.ts b/admin/src/environments/environment.prod.ts new file mode 100644 index 0000000..8d9f516 --- /dev/null +++ b/admin/src/environments/environment.prod.ts @@ -0,0 +1,4 @@ +export const environment = { + production: true, + debutUrl: "https://polynotfound.herokuapp.com/api/" +}; diff --git a/admin/src/environments/environment.ts b/admin/src/environments/environment.ts new file mode 100644 index 0000000..6c4970f --- /dev/null +++ b/admin/src/environments/environment.ts @@ -0,0 +1,17 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false, + debutUrl: "http://127.0.0.1:3000/api/" +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/plugins/zone-error'; // Included with Angular CLI. diff --git a/admin/src/favicon.ico b/admin/src/favicon.ico new file mode 100644 index 0000000..997406a Binary files /dev/null and b/admin/src/favicon.ico differ diff --git a/admin/src/index.html b/admin/src/index.html new file mode 100644 index 0000000..0e892e2 --- /dev/null +++ b/admin/src/index.html @@ -0,0 +1,13 @@ + + + + + StreamNotFound + + + + + + + + diff --git a/admin/src/main.ts b/admin/src/main.ts new file mode 100644 index 0000000..c7b673c --- /dev/null +++ b/admin/src/main.ts @@ -0,0 +1,12 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/admin/src/polyfills.ts b/admin/src/polyfills.ts new file mode 100644 index 0000000..373f538 --- /dev/null +++ b/admin/src/polyfills.ts @@ -0,0 +1,65 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** + * IE11 requires the following for NgClass support on SVG elements + */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/admin/src/styles.scss b/admin/src/styles.scss new file mode 100644 index 0000000..90d4ee0 --- /dev/null +++ b/admin/src/styles.scss @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/admin/src/test.ts b/admin/src/test.ts new file mode 100644 index 0000000..b4dd603 --- /dev/null +++ b/admin/src/test.ts @@ -0,0 +1,27 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: { + context(path: string, deep?: boolean, filter?: RegExp): { + keys(): string[]; + (id: string): T; + }; +}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), + { teardown: { destroyAfterEach: true }}, +); + +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/admin/tsconfig.app.json b/admin/tsconfig.app.json new file mode 100644 index 0000000..82d91dc --- /dev/null +++ b/admin/tsconfig.app.json @@ -0,0 +1,15 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/admin/tsconfig.json b/admin/tsconfig.json new file mode 100644 index 0000000..4a4dc62 --- /dev/null +++ b/admin/tsconfig.json @@ -0,0 +1,23 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "es2015", + "module": "es2020", + "lib": [ + "es2018", + "dom" + ] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false + } +} diff --git a/admin/tsconfig.spec.json b/admin/tsconfig.spec.json new file mode 100644 index 0000000..092345b --- /dev/null +++ b/admin/tsconfig.spec.json @@ -0,0 +1,18 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "files": [ + "src/test.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/app-backend/config/functions.config.js b/app-backend/config/functions.config.js new file mode 100644 index 0000000..b229914 --- /dev/null +++ b/app-backend/config/functions.config.js @@ -0,0 +1,26 @@ +const request = require("request"); +const VideoCategories = require("../models/objects/video.categories.model"); + +function asyncRequest(uri, option){ + return new Promise(function(resolve){ + request(uri, option,function (error, response, body){ + resolve({response: response, body: JSON.parse(body)}); + }); + }); +} +module.exports.asyncRequest = asyncRequest; + +function asyncInterest(interest, source){ + return new Promise(function(resolve){ + for(const i in VideoCategories){ + for(const j in VideoCategories[i].categories){ + if((VideoCategories[i].categories[j].name === interest || VideoCategories[i].categories[j].id === interest) + && VideoCategories[i].categories[j].source === source){ + resolve(VideoCategories[i].interest); + } + } + } + resolve(null); + }); +} +module.exports.asyncInterest = asyncInterest; diff --git a/app-backend/config/host.config.js b/app-backend/config/host.config.js new file mode 100644 index 0000000..6a62f28 --- /dev/null +++ b/app-backend/config/host.config.js @@ -0,0 +1,26 @@ +if(process.env.YOUTUBE_API_KEY === undefined || + process.env.YOUTUBE_API_KEY === '' || + process.env.DAILYMOTION_API_KEY === undefined || + process.env.DAILYMOTION_API_KEY === ''){ + console.log('Error Env YOUTUBE_API_KEY & DAILYMOTION_API_KEY Variables'); + process.exit(); +} + +console.log('Env variables YOUTUBE_API_KEY & DAILYMOTION_API_KEY received'); + +module.exports = { + youtube: { + name: "Youtube", + shortname: "yt", + baseAPIUrl: 'https://youtube.googleapis.com/youtube/v3', + baseChannelUrl: 'https://www.youtube.com/channel/', + YOUTUBE_API_KEY: process.env.YOUTUBE_API_KEY + }, + dailymotion: { + name: "Dailymotion", + shortname: "dm", + baseAPIUrl: 'https://api.dailymotion.com', + baseChannelUrl: 'https://www.dailymotion.com/', + DAILYMOTION_API_KEY: process.env.DAILYMOTION_API_KEY + } +}; diff --git a/app-backend/config/mongodb.config.js b/app-backend/config/mongodb.config.js new file mode 100644 index 0000000..2180acc --- /dev/null +++ b/app-backend/config/mongodb.config.js @@ -0,0 +1,4 @@ +module.exports = { + prodUrl: process.env.DATABASE, + devUrl: "mongodb://127.0.0.1:27017/polynotfound" +}; diff --git a/app-backend/config/response.config.js b/app-backend/config/response.config.js new file mode 100644 index 0000000..dfca2db --- /dev/null +++ b/app-backend/config/response.config.js @@ -0,0 +1,9 @@ +function sendMessage (res, successCode, data, token=null) { + res.status(200).json({ status: 'success', successCode: successCode, token: token, data: data }); +} + +function sendError (res, statusCode, errorCode, reason, token=null) { + res.status(statusCode).json({ status: 'error', errorCode: errorCode, token: token, reason: reason}); +} + +module.exports = { sendMessage, sendError }; diff --git a/app-backend/config/sessionJWT.config.js b/app-backend/config/sessionJWT.config.js new file mode 100644 index 0000000..7dbc86c --- /dev/null +++ b/app-backend/config/sessionJWT.config.js @@ -0,0 +1,109 @@ +const sessionJWTConfig = require ('jsonwebtoken'); +require('dotenv').config({ path: './app-backend/.env' }); +const {sendError} = require ("./response.config"); + +if(process.env.JWTRS256_PRIVATE_KEY === undefined || process.env.JWTRS256_PUBLIC_KEY === undefined){ + console.log('Error Env JWTRS256_PRIVATE_KEY & JWTRS256_PUBLIC_KEY Variables'); + process.exit(); +} + +console.log('Env variables JWTRS256_PRIVATE_KEY & JWTRS256_PUBLIC_KEY received'); +const JWTRS256_PRIVATE_KEY = Buffer.from(process.env.JWTRS256_PRIVATE_KEY, 'base64').toString('utf-8'); +const JWTRS256_PUBLIC_KEY = Buffer.from(process.env.JWTRS256_PUBLIC_KEY, 'base64').toString('utf-8'); + + +function createSessionJWT (id, email, profileImageUrl, role) { + return sessionJWTConfig.sign( + { + id: id, + email: email, + profileImageUrl: profileImageUrl, + role: role, + midExp: Math.floor(Date.now() / 1000) + 1800 + }, + JWTRS256_PRIVATE_KEY, + { + algorithm: 'RS256', + expiresIn: '1h' + } + ); +} + +function createSessionCookie(req, res, payload) { + let jwtToken; + if (typeof payload.id !== 'undefined' && + typeof payload.email !== 'undefined' && + typeof payload.profileImageUrl !== 'undefined' && + typeof payload.role !== 'undefined' && + typeof payload.midExp !== 'undefined' && + (Math.floor(Date.now() / 1000) <= payload.midExp)) { + jwtToken = req.headers.cookie; + } + else { + jwtToken = createSessionJWT(payload.id, payload.email, payload.profileImageUrl, payload.role); + } + res.cookie('SESSIONID', jwtToken, {httpOnly:true, secure:false}); +} + +function decodeSessionCookie(sessionid) { + if (typeof sessionid === 'undefined') { + return {id: -1, email: -1, profileImageUrl: -1, role: -1}; + } + try { + const token = sessionJWTConfig.verify( + sessionid, + JWTRS256_PUBLIC_KEY, + {algorithms: ['RS256']}); + return {token: token}; + } + catch (err) { + return {id: -1, email: -1, profileImageUrl: -1, role: -1}; + } +} + +function getSession(sessionid) { + return decodeSessionCookie(sessionid); +} +module.exports.getSession = getSession + +function setSessionCookie (req, res, session) { + createSessionCookie(req, res, session); +} +module.exports.setSessionCookie = setSessionCookie; + +function getToken(session) { + if (typeof session === 'undefined' || typeof session.token === 'undefined') return -1; + return session.token; +} +module.exports.getToken = getToken; + +function checkLogin(req, res, role=null){ + if(typeof req.cookies !== 'undefined'){ + const session = getSession(req.cookies.SESSIONID); + const token = getToken(session); + if(typeof token.email === 'undefined' || + token.email === -1 || + typeof token.id === 'undefined' || + token.id === -1){ + return sendError(res, 500, 102, "User not authenticated."); + } else { + token.midExp = new Date(token.midExp*1000); + token.iat = new Date(token.iat*1000); + token.exp = new Date(token.exp*1000); + if(role === null){ + return token; + } else { + if(typeof token.role !== 'undefined' && + ((Array.isArray(role) && role.includes(token.role)) || + ( typeof role === 'object' && typeof token.role.permission !== 'undefined' && token.role.permission >= role.permission && token.role.isAccepted === true))){ + return token; + } else { + return sendError(res, 500, 106, "User doesn't have permission.", token); + } + } + } + } else { + return sendError(res, 500, -1, "Cookies don't exist."); + } +} +module.exports.checkLogin = checkLogin; diff --git a/app-backend/controllers/ad.controller.js b/app-backend/controllers/ad.controller.js new file mode 100644 index 0000000..d92be1f --- /dev/null +++ b/app-backend/controllers/ad.controller.js @@ -0,0 +1,290 @@ +const db = require("../models/mongodb.model"); +const {sendError, sendMessage} = require ("../config/response.config"); +const {checkLogin} = require("../config/sessionJWT.config"); +const ObjectId = require('mongoose').Types.ObjectId; +const roles = require("../models/objects/role.model"); +const Ad = db.ads; + +// Create a new Ad +exports.create = (req, res) => { + const token = checkLogin(req, res, roles.Advertiser); + if(token && req.body.title){ + Ad.exists({title: req.body.title, userId: token.id, isActive: true}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the Ad already exists.", token); + } else{ + if(docs === null) { + let ad; + + ad = new Ad({ + userId: token.id, + title: req.body.title, + images: req.body.images ? req.body.images : undefined, + url: req.body.url ? req.body.url : undefined, + interests: req.body.interests ? req.body.interests : undefined, + comment: req.body.comment ? req.body.comment : undefined, + isVisible: req.body.isVisible ? req.body.isVisible : undefined, + isActive: req.body.isActive ? req.body.isActive : undefined + }); + + // Save User in the database + ad + .save(ad) + .then(data => { + return sendMessage(res, 41, data, token) + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the Ad.", token); + }); + } else{ + return sendError(res, 500, 104, err || `Ad ${req.body.title} already exists.`, token); + } + } + }); + } else { + return sendError(res, 500, -1, `No title given`, token); + } +}; + +// Retrieve all Ad from id if admin or session id +exports.findAll = (req, res) => { + const token = checkLogin(req, res, roles.Advertiser); + if(token){ + let query = {}; + let condition; + + const adId = req.query.adId; + condition = adId ? adId : undefined; + query._id = condition; + + let userId; + if(typeof token.role !== 'undefined' && + typeof token.role.permission !== 'undefined' && + typeof token.role.isAccepted !== 'undefined' && + token.role.isAccepted === true && + token.role.permission >= roles.Admin.permission) { + userId = req.query.userId; + } else { + userId = token.id; + } + condition = userId ? userId : undefined; + query.userId = condition; + + const title = req.query.title; + condition = title ? { $regex: new RegExp(title), $options: "i" } : undefined; + query.title = condition; + + const url = req.query.url; + condition = url ? { $regex: new RegExp(url), $options: "i" } : undefined; + query.url = condition; + + const interests = req.query.interests; + condition = interests ? {$in: interests.split(',')} : undefined; + query["interests.interest"] = condition + + const comment = req.query.comment; + condition = comment ? { $regex: new RegExp(comment), $options: "i" } : undefined; + query.comment = condition; + + const isVisible = req.query.isVisible; + condition = isVisible ? isVisible : undefined; + query.isVisible = condition; + + const isActive = req.query.isActive; + condition = isActive ? isActive : undefined; + query.isActive = condition; + + const sort = req.query.sort; + if(sort !== 'undefined'){ + switch (sort){ + case 'asc': + condition = {title: 1}; + break; + case 'desc': + condition = {title: -1}; + break; + case 'createdAtAsc': + condition = {createdAt: 1}; + break; + case 'createdAtDesc': + condition = {createdAt: -1}; + break; + case 'updatedAtAsc': + condition = {updatedAt: 1}; + break; + case 'updatedAtDesc': + condition = {updatedAt: -1}; + break; + default: + condition = {title: 1}; + } + } + const query_sort = {sort: condition}; + + // Remove undefined key + Object.keys(query).forEach(key => query[key] === undefined ? delete query[key] : {}); + console.log(query); + + Ad.find(query, {}, query_sort) + .then(data => { + if(data){ + return sendMessage(res, 42, data, token); + } + }) + .catch(err => { + return sendError(res,500,100,err.message || "Some error occurred while finding the Ads.", token); + }); + } +}; + +// Find single Ad from id if admin or session id +exports.findOne = (req, res) => { + const token = checkLogin(req, res, roles.Advertiser); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(id && ObjectId.isValid(id)){ + Ad.findById(id, {}) + .then(data => { + if(data){ + return sendMessage(res, 43, data, token); + } else { + return sendError(res,404,105,`Ad not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res,500,100,err.message || `Some error occurred while finding the Ad with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Update a Ad with ad id +exports.update = (req, res) => { + const token = checkLogin(req, res, roles.Advertiser); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(typeof req.body._id !== 'undefined' || typeof req.body.id !== 'undefined'){ + return sendError(res, 500, -1, `User do not have the permission to modify id or _id`, token); + } else{ + let update = {}; + let condition; + + const title = req.body.title; + condition = title ? title : undefined; + update.title = condition; + + const images = req.body.images; + condition = images ? images : undefined; + update.images = condition; + + const url = req.body.url; + condition = url ? url : undefined; + update.url = condition; + + let interests = req.body.interests; + condition = interests ? {interests: [...new Map(interests.map(v => [v.id, v])).values()]} : undefined; + update.$addToSet = condition; + + const comment = req.body.comment; + condition = comment ? comment : undefined; + update.comment = condition; + + const isVisible = req.body.isVisible; + if(typeof isVisible !== 'undefined'){ + condition = isVisible; + } else{ + condition = undefined; + } + update.isVisible = condition; + + const isActive = req.body.isActive; + if(typeof isActive !== 'undefined'){ + condition = isActive; + } else{ + condition = undefined; + } + update.isActive = condition; + + // Remove undefined key + Object.keys(update).forEach(key => update[key] === undefined ? delete update[key] : {}); + + if(id && ObjectId.isValid(id)){ + Ad.updateOne({_id: id, userId: token.id}, update) + .then(data => { + if(data) { + //Object.keys(update).forEach(key => data[key] = update[key]); + return sendMessage(res, 44, update, token); + } else { + return sendError(res, 404, -1, `Ad not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, -1, err.message || `Some error occurred while updating the Ad with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete an Ad with ad id +exports.delete = (req, res) => { + const token = checkLogin(req, res, roles.Advertiser); + if(token && typeof req.params.id !== 'undefined') { + let match = null; + const id = req.params.id; + if(id && ObjectId.isValid(id)){ + if(typeof token.role !== 'undefined' && + typeof token.role.permission !== 'undefined' && + typeof token.role.isAccepted !== 'undefined' && + token.role.isAccepted === true && + token.role.permission >= roles.Admin.permission) { + match = {_id: id, isActive: true}; + } else { + match = {_id: id, userId: token.id, isActive: true}; + } + Ad.findOneAndUpdate(match, {isActive: false}, {useFindAndModify: false, new: true}) + .then(data => { + if(data) { + if(data.isActive !== true){ + return sendMessage(res, 45, {message: `Ad ${id} was successfully deleted.`}, token); + } else { + return sendError(res, 404, 105, `Ad ${id} was not deleted.`, token); + } + } else { + return sendError(res, 404, 105, `Ad not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, 100, err.message || `Some error occurred while deleting the Ad with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete all Ad from session id +exports.deleteAll = (req, res) => { + const token = checkLogin(req, res, roles.Advertiser); + if(token) { + Ad.updateMany({userId: {$eq: token.id}, isActive: true}, {isActive: false}) + .then(data => { + return sendMessage(res, 46, { + message: `${data.modifiedCount} Ads were deleted successfully.` + }); + }) + .catch(err => { + return sendError(res, 500, -1, err.message || "Some error occurred while removing all Ads."); + }); + } +}; diff --git a/app-backend/controllers/misc.controller.js b/app-backend/controllers/misc.controller.js new file mode 100644 index 0000000..326e138 --- /dev/null +++ b/app-backend/controllers/misc.controller.js @@ -0,0 +1,7 @@ +const {sendMessage} = require ("../config/response.config"); +const interests = require("../models/objects/video.categories.model"); + +// Get all interests available +exports.getInterests = (req, res) => { + return sendMessage(res, 51, interests, null) +}; diff --git a/app-backend/controllers/playlist.controller.js b/app-backend/controllers/playlist.controller.js new file mode 100644 index 0000000..51442a1 --- /dev/null +++ b/app-backend/controllers/playlist.controller.js @@ -0,0 +1,407 @@ +const db = require("../models/mongodb.model"); +const {sendError, sendMessage} = require ("../config/response.config"); +const {checkLogin} = require("../config/sessionJWT.config"); +const {youtube, dailymotion} = require("../config/host.config"); +const {asyncRequest, asyncInterest} = require("../config/functions.config"); +const ObjectId = require('mongoose').Types.ObjectId; +const Playlist = db.playlists; +const Video = db.videos; + +// Create a new Playlist +exports.create = (req, res) => { + const token = checkLogin(req, res); + if(token && req.body.name){ + const video = req.body.video; + if(typeof video !== 'undefined' && + video !== null && + typeof video.videoId !== 'undefined' && + video.videoId !== null && + typeof video.source !== 'undefined' && + video.source !== null && + typeof video.interest !== 'undefined' && + video.interest !== null + ){ + Video.exists({userId: token.id, videoId: video.videoId, source: video.source, isActive: true}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the Video already exists.", token); + } else{ + if(docs === null) { + let video; + + video = new Video({ + userId: token.id, + videoId: id, + source: req.body.source, + interest: req.body.interest, + watchedDates: [new Date()] + }); + + // Save Video in the database + video + .save(video) + .then(data => { + if(data) { + Playlist.exists({name: req.body.name, isActive: true}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the Playlist already exists.", token); + } else{ + if(docs === null) { + let playlist; + + playlist = new Playlist({ + userId: token.id, + name: req.body.name, + videoIds: data._id ? [data._id] : undefined, + isActive: true + }); + + // Save User in the database + playlist + .save(playlist) + .then(data => { + return sendMessage(res, 21, data, token) + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the Playlist.", token); + }); + } else{ + return sendError(res, 500, 104, err || `Playlist ${req.body.name} already exists.`, token); + } + } + }); + } + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the Video.", token); + }); + } else{ + const id = docs._id.toString(); + Playlist.exists({name: req.body.name, isActive: true}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the Playlist already exists.", token); + } else{ + if(docs === null) { + let playlist; + + playlist = new Playlist({ + userId: token.id, + name: req.body.name, + videoIds: [id], + isActive: true + }); + + // Save User in the database + playlist + .save(playlist) + .then(data => { + return sendMessage(res, 21, data, token) + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the Playlist.", token); + }); + } else{ + return sendError(res, 500, 104, err || `Playlist ${req.body.name} already exists.`, token); + } + } + }); + } + } + }); + } else { + Playlist.exists({name: req.body.name, isActive: true}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the Playlist already exists.", token); + } else{ + if(docs === null) { + let playlist; + + playlist = new Playlist({ + userId: token.id, + name: req.body.name, + videoIds: req.body.videoIds ? req.body.videoIds : undefined, + isActive: req.body.isActive ? req.body.isActive : undefined + }); + + // Save User in the database + playlist + .save(playlist) + .then(data => { + return sendMessage(res, 21, data, token) + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the Playlist.", token); + }); + } else{ + return sendError(res, 500, 104, err || `Playlist ${req.body.name} already exists.`, token); + } + } + }); + } + } +}; + +// Retrieve all Playlist from id if admin or session id +exports.findAll = (req, res) => { + const token = checkLogin(req, res); + if(token){ + let query = {}; + let condition; + + const playlistId = req.query.playlistId; + condition = playlistId ? playlistId : undefined; + query._id = condition; + + const userId = token.id; + condition = userId ? userId : undefined; + query.userId = condition; + + const videoIds = req.query.videoIds; + condition = videoIds ? {$in: videoIds} : undefined; + query.videoIds = condition; + + const name = req.query.name; + condition = name ? { $regex: new RegExp(name), $options: "i" } : undefined; + query.name = condition; + + const isActive = req.query.isActive; + condition = isActive ? isActive : undefined; + query.isActive = condition; + + const sort = req.query.sort; + if(sort !== 'undefined'){ + switch (sort){ + case 'asc': + condition = {name: 1}; + break; + case 'desc': + condition = {name: -1}; + break; + case 'createdAtAsc': + condition = {createdAt: 1}; + break; + case 'createdAtDesc': + condition = {createdAt: -1}; + break; + case 'updatedAtAsc': + condition = {updatedAt: 1}; + break; + case 'updatedAtDesc': + condition = {updatedAt: -1}; + break; + default: + condition = {name: 1}; + } + } + const query_sort = {sort: condition}; + + // Remove undefined key + Object.keys(query).forEach(key => query[key] === undefined ? delete query[key] : {}); + console.log(query); + + Playlist.find(query, {}, query_sort) + .then(data => { + return sendMessage(res, 22, data, token); + }) + .catch(err => { + return sendError(res,500,100,err.message || "Some error occurred while finding the Playlists.", token); + }); + } +}; + +// Find single Playlist from session id +exports.findOne = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(id && ObjectId.isValid(id)){ + Playlist.aggregate([ + {$match: {_id: new ObjectId(id), userId: token.id, isActive: true}}, + {$unwind: '$videoIds'}, + {$project: { + userId: true, + name: true, + isActive: true, + createdAt: true, + updatedAt: true, + videoIds: {$toObjectId: '$videoIds'} + }}, + {$lookup: { + from: 'videos', + localField: 'videoIds', + foreignField: '_id', + as: 'videos' + }}, + {$unwind: '$videos'}, + {$group: { + _id: '$_id', + userId: {$first: "$userId"}, + name: {$first: "$name"}, + isActive: {$first: "$isActive"}, + createdAt: {$first: "$createdAt"}, + updatedAt: {$first: "$updatedAt"}, + videos: {$push: "$videos"} + }} + ]) + .then(async data => { + let yt_results = []; + let dm_results = []; + let yt_videoIds = ""; + let dm_videoIds = ""; + + for (const i in data[0].videos) { + if (data[0].videos[i].source === youtube.name) { + yt_videoIds = yt_videoIds + data[0].videos[i].videoId + ","; + } else if (data[0].videos[i].source === dailymotion.name) { + dm_videoIds = dm_videoIds + data[0].videos[i].videoId + ","; + } + } + if (yt_videoIds !== "") { + const uri = youtube.baseAPIUrl + '/videos' + '?part=snippet&part=statistics&id=' + yt_videoIds.slice(0, -1) + '&key=' + youtube.YOUTUBE_API_KEY; + const dataVideos = await asyncRequest(uri, {}); + if (dataVideos.response.statusCode === 200 && dataVideos.body.items.length > 0) { + yt_results = dataVideos.body.items; + } + } + + if (dm_videoIds !== "") { + const uri = dailymotion.baseAPIUrl + '/videos?ids=' + dm_videoIds.slice(0, -1) + '&fields=thumbnail_480_url%2Ctitle%2Cid'; + const data = await asyncRequest(uri, {}); + const response = data.response; + const jsonBody = data.body; + if (response.statusCode === 200) { + dm_results = jsonBody.list; + } + } + for (const i in data[0].videos) { + if (data[0].videos[i].source === youtube.name) { + const obj = yt_results.filter(obj => obj.id === data[0].videos[i].videoId); + data[0].videos[i].imageUrl = obj[0].snippet.thumbnails.medium.url ? obj[0].snippet.thumbnails.medium.url : null; + data[0].videos[i].interest = obj[0].snippet.categoryId ? await asyncInterest(obj[0].snippet.categoryId, youtube.name): null; + data[0].videos[i].title = obj[0].snippet.title ? obj[0].snippet.title : null; + data[0].videos[i].views = obj[0].statistics.viewCount ? parseInt(obj[0].statistics.viewCount) : null; + data[0].videos[i].publishedAt = obj[0].snippet.publishedAt ? obj[0].snippet.publishedAt : null; + } else if (data[0].videos[i].source === dailymotion.name) { + const obj = dm_results.filter(obj => obj.id === data[0].videos[i].videoId); + data[0].videos[i].imageUrl = obj[0].thumbnail_480_url ? obj[0].thumbnail_480_url : null; + data[0].videos[i].interest = obj[0]['channel.name'] ? await asyncInterest( obj[0]['channel.name'], dailymotion.name) : null; + data[0].videos[i].title = obj[0].title ? obj[0].title : null; + data[0].videos[i].views = obj[0].views_total ? parseInt(obj[0].views_total) : null; + data[0].videos[i].publishedAt = obj[0].created_time ? new Date(obj[0].created_time * 1000) : null + } + } + return sendMessage(res, 12, data[0], token) + }) + .catch(err => { + return sendError(res,500,100,err.message || `Some error occurred while finding the Playlist with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Update a Playlist with playlist id +exports.update = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(typeof req.body._id !== 'undefined' || typeof req.body.id !== 'undefined'){ + return sendError(res, 500, -1, `User do not have the permission to modify id or _id`, token); + } else{ + const ids = id.split(','); + let update = {}; + let condition; + + const name = req.body.name; + condition = name ? name : undefined; + update.name = condition; + + const videoIds = req.body.videoIds; + condition = videoIds ? videoIds : undefined; + update.videoIds = condition; + + const videoId = req.body.videoId; + if(typeof videoId !== 'undefined' && typeof videoId.id !== 'undefined' && typeof videoId.action !== 'undefined'){ + if(videoId.action === 'add'){ + condition = videoId.id ? {videoIds: videoId.id} : undefined; + update.$addToSet = condition; + } else if(videoId.action === 'delete'){ + condition = videoId.id ? {videoIds: videoId.id} : undefined; + update.$pull = condition; + } + } + + const isActive = req.body.isActive; + if(typeof isActive !== 'undefined'){ + condition = isActive; + } else{ + condition = undefined; + } + update.isActive = condition; + + // Remove undefined key + Object.keys(update).forEach(key => update[key] === undefined ? delete update[key] : {}); + + Playlist.updateMany({_id: {$in: ids}, userId: token.id, isActive: true}, update, {new: false}) + .then(data => { + if(data) { + if(data.modifiedCount > 0){ + return sendMessage(res, 24, update, token); + } else { + return sendError(res, 500, -1, `Video in Playlist ${data} already exists.`, token); + } + } else { + return sendError(res, 404, -1, `Playlist not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, -1, err.message || `Some error occurred while updating the Playlist with id=${id}`, token); + }); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete a Playlist with playlist id +exports.delete = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(id && ObjectId.isValid(id)){ + Playlist.findByIdAndUpdate(id, {isActive: false}, {useFindAndModify: false}) + .then(data => { + if(data) { + return sendMessage(res, 25, {message: `Playlist ${id} was successfully deleted.`}, token); + } else { + return sendError(res, 404, 105, `Playlist not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, 100, err.message || `Some error occurred while deleting the Playlist with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete all Playlists from session id +exports.deleteAll = (req, res) => { + const token = checkLogin(req, res); + if(token) { + Playlist.updateMany({userId: {$eq: token.id}, isActive: true}, {isActive: false}) + .then(data => { + return sendMessage(res, 26, { + message: `${data.modifiedCount} Playlists were deleted successfully.` + }); + }) + .catch(err => { + return sendError(res, 500, -1, err.message || "Some error occurred while removing all Playlists."); + }); + } +}; diff --git a/app-backend/controllers/user.controller.js b/app-backend/controllers/user.controller.js new file mode 100644 index 0000000..098db1b --- /dev/null +++ b/app-backend/controllers/user.controller.js @@ -0,0 +1,546 @@ +const db = require("../models/mongodb.model"); +const {sendError, sendMessage} = require ("../config/response.config"); +const {checkLogin, setSessionCookie} = require("../config/sessionJWT.config"); +const ObjectId = require('mongoose').Types.ObjectId; +const roles = require("../models/objects/role.model"); +const {youtube, dailymotion} = require("../config/host.config"); +const {asyncRequest} = require("../config/functions.config"); +const User = db.users; +const Video = db.videos; +const Ad = db.ads; + +// Authenticate a User +exports.auth = (req, res) => { + // Validate request + if (!req.body.email || !req.body.hashPass) { + sendError(res, 400,-1,"Content can not be empty . (email and hashPass needed)"); + } else{ + // Check User in the database + User + .findOne({email: req.body.email, hashPass: req.body.hashPass, isActive: true, "role.isAccepted": true}, {role: true, profileImageUrl: true}) + .then(data => { + if (data !== null){ + User.findByIdAndUpdate(data._id.toString(), {lastConnexion: new Date()}, {useFindAndModify: false}, + function (err) { + if (err){ + return sendError(res, 400, 100,err.message || "Some error occurred while updating the User."); + } + else{ + const dataRes = {id: data._id.toString(), email: req.body.email, profileImageUrl: data.profileImageUrl, role: data.role}; + setSessionCookie(req, res, dataRes); + return sendMessage(res, 1, dataRes); + } + }); + } else { + setSessionCookie(req, res, {id: -1, email: -1, profileImageUrl: -1, role: -1}); + return sendError(res, 500, 101, "Invalid login or password."); + } + }) + .catch(err => { + return sendError(res, 400, 100,err.message || "Some error occurred while authenticating the User."); + }); + } +}; + +// Logout a User +exports.logout = (req, res) => { + const token = checkLogin(req, res); + if(token){ + setSessionCookie(req, res, {id: -1, email: -1, profileImageUrl: -1, role: -1}); + return sendMessage(res, 2, {message: "User disconnected"}); + } +}; + +// Request password reset with email +exports.resetPass = (req, res) => { + return sendError(res, 501, -1, "User.resetPass not Implemented", null); +}; + +// Create and Save a new User +exports.create = (req, res) => { + // Validate request + if (!req.body.email || !req.body.hashPass || !req.body.login) { + sendError(res, 400,-1,"Content can not be empty . (email, hashPass and login needed"); + } + else{ + User.exists({email: req.body.email}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the User already exists."); + } else{ + if(docs === null) { + let user; + let var_role; + if(typeof req.body.role !== 'undefined'){ + switch(req.body.role){ + case 'admin': + var_role = roles.Admin; + break; + case 'advertiser': + var_role = roles.Advertiser; + break; + default: + var_role = roles.User; + } + } else{ + var_role = roles.User; + } + + user = new User({ + email: req.body.email, + hashPass: req.body.hashPass, + login: req.body.login, + role: var_role, + company: req.body.company ? req.body.company : null, + dateOfBirth: req.body.dateOfBirth ? req.body.dateOfBirth : null, + gender: req.body.gender ? req.body.gender : null, + interests: req.body.interests ? req.body.interests : null, + }); + + // Save User in the database + user + .save(user) + .then(data => { + data.hashPass = undefined; // Hiding hashPass on return + return sendMessage(res, 4, data) + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the User."); + }); + } else{ + return sendError(res, 500, 104, err || `Email ${req.body.email} already exists.`); + } + } + }); + } +}; + +// Retrieve all Users from the database if at least admin. +exports.findAll = (req, res) => { + const token = checkLogin(req, res, roles.Admin); + if(token){ + let query = {}; + let condition; + + const ids = req.query.userId; + condition = ids ? {$in: ids} : undefined; + query._id = condition; + + const email = req.query.email; + condition = email ? { $regex: new RegExp(email), $options: "i" } : undefined; + query.email = condition; + + const login = req.query.login; + condition = login ? { $regex: new RegExp(login), $options: "i" } : undefined; + query.login = condition; + + const role = req.query.role; + condition = role ? role : undefined; + query["role.name"] = condition; + + const company = req.query.company; + condition = company ? { $regex: new RegExp(company), $options: "i" } : undefined; + query.company = condition; + + const dateOfBirth = req.query.dateOfBirth; + condition = dateOfBirth ? new Date(dateOfBirth) : undefined; + query.dateOfBirth = condition; + + const gender = req.query.gender; + condition = gender ? gender : undefined; + query.gender = condition; + + const isActive = req.query.isActive; + condition = isActive ? isActive : undefined; + query.isActive = condition; + + const isAccepted = req.query.isAccepted; + if(isAccepted !== 'undefined'){ + switch (isAccepted){ + case 'true': + condition = true; + break; + case 'false': + condition = false; + break; + } + } + query["role.isAccepted"] = condition; + + const sort = req.query.sort; + if(sort !== 'undefined'){ + switch (sort){ + case 'asc': + condition = {email: 1}; + break; + case 'desc': + condition = {email: -1}; + break; + case 'lastConnexionAsc': + condition = {lastConnexion: 1}; + break; + case 'lastConnexionDesc': + condition = {lastConnexion: -1}; + break; + case 'createdAtAsc': + condition = {createdAt: 1}; + break; + case 'createdAtDesc': + condition = {createdAt: -1}; + break; + case 'updatedAtAsc': + condition = {updatedAt: 1}; + break; + case 'updatedAtDesc': + condition = {updatedAt: -1}; + break; + default: + condition = {email: 1}; + } + } + const query_sort = {sort: condition}; + + // Remove undefined key + Object.keys(query).forEach(key => query[key] === undefined ? delete query[key] : {}); + console.log(query); + + User.find(query, {hashPass: false}, query_sort) + .then(data => { + return sendMessage(res, 5, data, token); + }) + .catch(err => { + return sendError(res,500,100,err.message || "Some error occurred while retrieving users.", token); + }); + } +}; + +// Find a single User by session id +exports.findOne = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + let id = null; + if(typeof token.id !== 'undefined' && token.id === req.params.id){ + id = req.params.id; + } else { + if(typeof token.role !== 'undefined' && + typeof token.role.permission !== 'undefined' && + typeof token.role.isAccepted !== 'undefined' && + token.role.isAccepted === true && + token.role.permission >= roles.Admin.permission) { + id = req.params.id; + } else { + return sendError(res, 500, 106, `User do not have the permission.`, token); + } + } + if(id && ObjectId.isValid(id)){ + User.findById(id, {hashPass: false}) + .then(data => { + if(data){ + return sendMessage(res, 6, data, token); + } else { + return sendError(res,404,105,`User not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res,500,100,err.message || `Some error occurred while finding the User with id=${id}`, token); + }); + } else { + sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Update a User by the id in the request +exports.update = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + let id = null; + if(typeof token.id !== 'undefined' && token.id === req.params.id){ + id = req.params.id; + } else { + if(typeof token.role !== 'undefined' && + typeof token.role.permission !== 'undefined' && + typeof token.role.isAccepted !== 'undefined' && + token.role.isAccepted === true && + token.role.permission >= roles.Admin.permission) { + id = req.params.id; + } else { + return sendError(res, 500, 106, `User do not have the permission.`, token); + } + } + if(id && ObjectId.isValid(id)){ + let update = null; + if(typeof req.body._id !== 'undefined' || typeof req.body.id !== 'undefined'){ + return sendError(res, 500, -1, `User do not have the permission to modify id or _id`, token); + } else{ + if(typeof req.body.role !== 'undefined' || + typeof req.body.isActive !== 'undefined' || + typeof req.body.lastConnexion !== 'undefined' || + typeof req.body.createdAt !== 'undefined'|| + typeof req.body.updatedAt !== 'undefined'){ + if(typeof token.role !== 'undefined' && + typeof token.role.permission !== 'undefined' && + typeof token.role.isAccepted !== 'undefined' && + token.role.isAccepted === true && + token.role.permission >= roles.Admin.permission) { + update = true; + } else{ + return sendError(res, 500, 106, `User do not have the permission to modify these keys.`, token); + } + } else{ + update = true; + } + } + if(update === true){ + User.findByIdAndUpdate(id, req.body, {useFindAndModify: false}) + .then(data => { + if(data) { + data.hashPass = undefined; + Object.keys(req.body).forEach(key => data[key] = req.body[key]); + sendMessage(res, 7, data, token); + } else { + sendError(res, 404, -1, `User not found with id=${id}`, token); + } + }) + .catch(err => { + sendError(res, 500, -1, err.message || `Some error occurred while updating the User with id=${id}`, token); + }); + } + } else { + sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + + + +// Delete a User with the specified id in the request +exports.delete = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + let id = null; + if(typeof token.id !== 'undefined' && token.id === req.params.id){ + id = req.params.id; + } else { + if(typeof token.role !== 'undefined' && + typeof token.role.permission !== 'undefined' && + typeof token.role.isAccepted !== 'undefined' && + token.role.isAccepted === true && + token.role.permission >= roles.Admin.permission) { + id = req.params.id; + } else { + return sendError(res, 500, 106, `User do not have the permission.`, token); + } + } + if(id && ObjectId.isValid(id)){ + User.findByIdAndUpdate(id, {isActive: false}, {useFindAndModify: false}) + .then(data => { + if(data) { + return sendMessage(res, 8, {message: `User ${id} was successfully deleted.`}, token); + } else { + return sendError(res, 404, 105, `User not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, 100, err.message || `Some error occurred while deleting the User with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete all Users from the database except superAdmin +exports.deleteAll = (req, res) => { + const token = checkLogin(req, res, roles.SuperAdmin); + if(token) { + User.deleteMany({login: {$ne: "superAdmin"}}) + .then(data => { + return sendMessage(res, 9, { + message: `${data.deletedCount} Users were deleted successfully.` + }); + }) + .catch(err => { + return sendError(res, 500, 100, err.message || "Some error occurred while removing all Users."); + }); + } +}; + +// Get all Roles depending on the role of the User +exports.roles = (req, res) => { + const token = checkLogin(req, res); + if(token){ + return sendMessage(res, 10, roles, token); + } +}; + +// Get 1 or multiple ad adapted to the User session id +exports.ad = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.query.quantity !== 'undefined'){ + const id = token.id; + const quantity = req.query.quantity; + // Interests from the user and from last 20 videos viewed if no ad matches -> find x ad from these interests + add date view to the ad + let interests = []; + const maxInterests = 20; + let limit = maxInterests; + User.findById(id, {_id: false, interests: true}) + .then(data => { + if(typeof data.interests !== 'undefined' && data.interests !== null){ + interests = interests.concat(data.interests); + limit = maxInterests-data.interests.length; + } + Video.aggregate([ + {$match: {userId: id}}, + {$project: {_id: false, interest: true}}, + {$sort: {watchedDates: -1}}, + {$limit: limit}, + {$unwind: '$interest'}, + {$group: {_id: null, interests: {$push: '$interest'}}} + ]) + .then(data => { + if(typeof data[0] !== 'undefined' && + typeof data[0].interests !== 'undefined' && + data[0].interests !== [] && + data[0].interests !== null){ + interests = interests.concat(data[0].interests); + } + let match, pick; + if(interests.length > 0){ + match = {$match: {isVisible: true, isActive: true, interests: {$elemMatch: {interest: {$in: interests}}}}}; + pick = {$limit: parseInt(quantity, 10)} + } else { + match = {$match: {isVisible: true, isActive: true}}; + pick = {$sample: {size: parseInt(quantity, 10)}}; + } + + Ad.aggregate([ + match, + pick + ]) + .then(data => { + if(data.length > 0){ + let ids = [] + for(const i in data){ids.push(data[i]._id);} + Ad.updateMany({_id: {$in: ids}}, {$push: {views: [new Date()]}}) + .then(dataUpdate => { + if(dataUpdate && dataUpdate.modifiedCount > 0){ + return sendMessage(res, 11, data, token); + } else { + return sendError(res,500,101,`Some error occurred while updating ${quantity} ad(s) for the User.`, token); + } + }) + .catch(err => { + return sendError(res,500,101,err.message || `Some error occurred while updating ${quantity} ad(s) for the User.`, token); + }); + } else { + Ad.aggregate([{$match: {isVisible: true, isActive: true}}, {$sample: {size: parseInt(quantity, 10)}}]) + .then(data => { + let ids = [] + for(const i in data){ids.push(data[i]._id);} + Ad.updateMany({_id: {$in: ids}}, {$push: {views: [new Date()]}}) + .then(dataUpdate => { + if(dataUpdate && dataUpdate.modifiedCount > 0){ + return sendMessage(res, 11, data, token); + } else { + return sendError(res,500,101,`Some error occurred while updating ${quantity} ad(s) for the User.`, token); + } + }) + .catch(err => { + return sendError(res,500,101,err.message || `Some error occurred while updating ${quantity} ad(s) for the User.`, token); + }); + }) + .catch(err => { + return sendError(res,500,101,err.message || `Some error occurred while getting ${quantity} ad(s) for the User.`, token); + }); + } + }) + .catch(err => { + return sendError(res,500,101,err.message || `Some error occurred while getting ${quantity} ad(s) for the User.`, token); + }); + }) + .catch(err => { + return sendError(res,500,102,err.message || `Some error occurred while getting ${quantity} ad(s) for the User.`, token); + }); + }) + .catch(err => { + return sendError(res,500,100,err.message || `Some error occurred while getting ${quantity} ad(s) for the User.`, token); + }); + } else { + sendError(res, 500, -1, `No quantity given`, token); + } +}; + +// Get History +exports.history = (req, res) => { + const token = checkLogin(req, res); + if(token){ + const id = token.id; + + Video.aggregate([ + {$match: {userId: id, $expr: {$gt: [{$size: "$watchedDates"}, 0]}}}, + {$limit: 300}, + {$project: { + videoId: true, + source: true, + tags: true, + interest: true, + views: {$size: '$watchedDates'}, + watchedDate: {$arrayElemAt: ["$watchedDates", -1]}, + createdAt: true, + updatedAt: true + }}, + {$sort: {watchedDate: -1}}]) + .then(async data => { + let yt_results = []; + let dm_results = []; + let yt_videoIds = ""; + let dm_videoIds = ""; + + for(const i in data) { + if(data[i].source === youtube.name) { + yt_videoIds = yt_videoIds + data[i].videoId + ","; + } else if (data[i].source === dailymotion.name) { + dm_videoIds = dm_videoIds + data[i].videoId + ","; + } + } + if(yt_videoIds !== ""){ + const uri = youtube.baseAPIUrl + '/videos' + '?part=snippet&part=statistics&id=' + yt_videoIds.slice(0, -1) + '&key=' + youtube.YOUTUBE_API_KEY; + const dataVideos = await asyncRequest(uri, {}); + if (dataVideos.response.statusCode === 200 && dataVideos.body.items.length > 0) { + yt_results = dataVideos.body.items; + } + } + + if(dm_videoIds !== ""){ + const uri = dailymotion.baseAPIUrl + '/videos?ids='+dm_videoIds.slice(0, -1)+'&fields=thumbnail_480_url%2Ctitle%2Cid'; + const data = await asyncRequest(uri, {}); + const response = data.response; + const jsonBody = data.body; + if(response.statusCode === 200){ + dm_results = jsonBody.list; + } + } + for(const i in data) { + if(data[i].source === youtube.name) { + const obj = yt_results.filter(obj => obj.id === data[i].videoId); + data[i].imageUrl = obj[0].snippet.thumbnails.medium.url ? obj[0].snippet.thumbnails.medium.url : null; + data[i].title = obj[0].snippet.title ? obj[0].snippet.title : null; + } else if (data[i].source === dailymotion.name) { + const obj = dm_results.filter(obj => obj.id === data[i].videoId); + data[i].imageUrl = obj[0].thumbnail_480_url ? obj[0].thumbnail_480_url : null; + data[i].title = obj[0].title ? obj[0].title : null; + } + } + return sendMessage(res, 12, data, token) + }) + .catch(err => { + return sendError(res,500,100,err.message || "Some error occurred while getting the User history.", token); + }); + } +}; diff --git a/app-backend/controllers/video.controller.js b/app-backend/controllers/video.controller.js new file mode 100644 index 0000000..f928918 --- /dev/null +++ b/app-backend/controllers/video.controller.js @@ -0,0 +1,473 @@ +const db = require("../models/mongodb.model"); +const request = require('request'); +const {sendError, sendMessage} = require ("../config/response.config"); +const {checkLogin} = require("../config/sessionJWT.config"); +const {youtube, dailymotion} = require("../config/host.config"); +const {asyncRequest, asyncInterest} = require("../config/functions.config"); +const ObjectId = require('mongoose').Types.ObjectId; +const Video = db.videos; + + +// Search Videos +exports.search = async (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.query.q !== 'undefined'){ + const query = req.query.q; + const maxResults = req.query.maxResults ? req.query.maxResults : 45; + const pageToken = req.query.pageToken ? req.query.pageToken : undefined; + let sources; + if(typeof req.query.sources !== 'undefined' && req.query.sources !== ''){ + sources = req.query.sources.split(','); + } else { + sources = ["yt", "dm"]; + } + let yt_results = []; + let dm_results = []; + for(const i in sources){ + if(sources[i] === youtube.shortname){ + if(youtube.YOUTUBE_API_KEY !== 'undefined' && youtube.YOUTUBE_API_KEY !== ''){ + let uri; + if(query !== ''){ + if(typeof pageToken !== 'undefined'){ + uri = youtube.baseAPIUrl+'/search'+'?part=snippet&maxResults='+maxResults+'&q='+query+'&pageToken='+pageToken+'&key='+youtube.YOUTUBE_API_KEY; + } else{ + uri = youtube.baseAPIUrl+'/search'+'?part=snippet&maxResults='+maxResults+'&q='+query+'&key='+youtube.YOUTUBE_API_KEY; + } + const dataIds = await asyncRequest(uri, {}); + if(dataIds.response.statusCode === 200 && dataIds.body.items.length > 0){ + let yt_videoIds = ""; + dataIds.body.items.forEach(item => yt_videoIds = yt_videoIds+item.id.videoId+","); + uri = youtube.baseAPIUrl+'/videos'+'?part=snippet&part=statistics&id='+yt_videoIds.slice(0, -1)+'&key='+youtube.YOUTUBE_API_KEY; + const dataVideos = await asyncRequest(uri, {}); + if(dataVideos.response.statusCode === 200 && dataVideos.body.items.length > 0){ + yt_results = dataVideos.body.items; + } + } + } else { + uri = youtube.baseAPIUrl+'/videos'+'?part=snippet&part=statistics&chart=mostPopular&maxResults='+maxResults+'&key='+youtube.YOUTUBE_API_KEY; + const dataVideos = await asyncRequest(uri, {}); + if(dataVideos.response.statusCode === 200 && dataVideos.body.items.length > 0){ + yt_results = dataVideos.body.items; + } + } + } else{ + return sendError(res, 500, -1, `Error Env Variable DAILYMOTION_API_KEY missing, please contact the admin.`, token); + } + } else if(sources[i] === dailymotion.shortname){ + if(dailymotion.DAILYMOTION_API_KEY !== 'undefined' && dailymotion.DAILYMOTION_API_KEY !== '') { + let uri; + if(query !== ''){ + uri = dailymotion.baseAPIUrl + '/videos?limit='+maxResults+'&search='+query+'&fields=created_time%2Cdescription%2Cthumbnail_480_url%2Clikes_total%2Ctitle%2Cid%2Cembed_url%2Cviews_total%2Cowner.username%2Cowner.id%2Cchannel.name'; + } else { + uri = dailymotion.baseAPIUrl + '/videos?limit='+maxResults+'&sort=trending&fields=created_time%2Cdescription%2Cthumbnail_480_url%2Clikes_total%2Ctitle%2Cid%2Cembed_url%2Cviews_total%2Cowner.username%2Cowner.id%2Cchannel.name'; + } + const data = await asyncRequest(uri, {}); + const response = data.response; + const jsonBody = data.body; + if(response.statusCode === 200){ + dm_results = jsonBody.list; + } + } else{ + return sendError(res, 500, -1, `Error Env Variable DAILYMOTION_API_KEY missing, please contact the admin.`, token); + } + } + } + + let results = []; + for(let i = 0; i < Math.max(dm_results.length, yt_results.length); i++){ + + // Youtube + if(yt_results.length > i){ + const yt_data = { + videoId: yt_results[i].id, + source: youtube.name, + imageUrl: yt_results[i].snippet.thumbnails.medium.url ? yt_results[i].snippet.thumbnails.medium.url : null, + title: yt_results[i].snippet.title ? yt_results[i].snippet.title : null, + channelTitle: yt_results[i].snippet.channelTitle ? yt_results[i].snippet.channelTitle : null, + channelUrl: youtube.baseChannelUrl+yt_results[i].snippet.channelId ? yt_results[i].snippet.channelId : null, + description: yt_results[i].snippet.description ? yt_results[i].snippet.description : null, + embedUrl: 'https://www.youtube.com/embed/'+yt_results[i].id, + interest: await asyncInterest(yt_results[i].snippet.categoryId, youtube.name), + views: yt_results[i].statistics.viewCount ? parseInt(yt_results[i].statistics.viewCount) : null, + likes: yt_results[i].statistics.likeCount ? parseInt(yt_results[i].statistics.likeCount) : null, + dislikes: yt_results[i].statistics.dislikeCount ? parseInt(yt_results[i].statistics.dislikeCount) : null, + publishedAt: yt_results[i].snippet.publishedAt ? yt_results[i].snippet.publishedAt : null + }; + results.push(yt_data); + } + + // Dailymotion + if(dm_results.length > i) { + const channelTitle = dm_results[i]['owner.username'] ? dm_results[i]['owner.username'] : null; + const dm_data = { + videoId: dm_results[i].id ? dm_results[i].id : null, + source: dailymotion.name, + imageUrl: dm_results[i].thumbnail_480_url ? dm_results[i].thumbnail_480_url : null, + title: dm_results[i].title ? dm_results[i].title : null, + channelTitle: channelTitle.charAt(0).toUpperCase() + channelTitle.slice(1), + channelUrl: dailymotion.baseChannelUrl + channelTitle, + description: dm_results[i].description ? dm_results[i].description : null, + embedUrl: dm_results[i].embed_url ? dm_results[i].embed_url : null, + interest: await asyncInterest(dm_results[i]['channel.name'], dailymotion.name), + views: dm_results[i].views_total ? parseInt(dm_results[i].views_total) : null, + likes: dm_results[i].likes_total ? parseInt(dm_results[i].likes_total) : null, + dislikes: null, + publishedAt: dm_results[i].created_time ? new Date(dm_results[i].created_time * 1000) : null + }; + results.push(dm_data); + } + } + return sendMessage(res, 31, results, token); + } else{ + return sendError(res, 500, -1, `No q given`, token); + } +}; + +// Get Video with id of source +exports.get = (req, res) => { + if(typeof req.query.source !== 'undefined' && typeof req.params.id !== 'undefined'){ + const source = req.query.source; + const id = req.params.id; + if(source === youtube.shortname){ + if(youtube.YOUTUBE_API_KEY !== 'undefined' && youtube.YOUTUBE_API_KEY !== ''){ + const uri = youtube.baseAPIUrl+'/videos'+'?part=snippet&part=statistics&id='+id+'&key='+youtube.YOUTUBE_API_KEY; + request(uri,{},async function (error, response, body){ + if(typeof body !== 'undefined'){ + const jsonBody = JSON.parse(body); + if(jsonBody.items.length !== 0 && + typeof jsonBody.items[0] !== 'undefined' && + typeof jsonBody.items[0].id !== 'undefined' && + jsonBody.items[0].id === id){ + const imageUrl = jsonBody.items[0].snippet.thumbnails.standard.url ? jsonBody.items[0].snippet.thumbnails.standard.url : null; + const title = jsonBody.items[0].snippet.title ? jsonBody.items[0].snippet.title : null; + const channelId = jsonBody.items[0].snippet.channelId ? jsonBody.items[0].snippet.channelId : null; + const channelTitle = jsonBody.items[0].snippet.channelTitle ? jsonBody.items[0].snippet.channelTitle : null; + const description = jsonBody.items[0].snippet.description ? jsonBody.items[0].snippet.description : null; + //const embedUrl = jsonBody.embed_url ? jsonBody.embed_url : null; + const publishedAt = jsonBody.items[0].snippet.publishedAt ? jsonBody.items[0].snippet.publishedAt : null; + const interest = jsonBody.items[0].snippet.categoryId ? await asyncInterest(jsonBody.items[0].snippet.categoryId, youtube.name): null; + const views = jsonBody.items[0].statistics.viewCount ? parseInt(jsonBody.items[0].statistics.viewCount) : null; + const likes = jsonBody.items[0].statistics.likeCount ? parseInt(jsonBody.items[0].statistics.likeCount) : null; + const dislikes = jsonBody.items[0].statistics.dislikeCount ? parseInt(jsonBody.items[0].statistics.dislikeCount) : null; + const data = { + videoId: id, + source: youtube.name, + imageUrl: imageUrl, + title: title, + channelTitle: channelTitle, + channelUrl: youtube.baseChannelUrl+channelId, + description: description, + embedUrl: 'https://www.youtube.com/embed/'+id, + interest: interest, + views: views, + likes: likes, + dislikes: dislikes, + publishedAt: publishedAt + }; + return sendMessage(res, 32, data); + } else{ + return sendError(res, 404, -1, `No result`); + } + } else{ + return sendError(res, 500, -1, error); + } + }); + } else{ + return sendError(res, 500, -1, `Error Env Variable YOUTUBE_API_KEY missing, please contact the admin.`); + } + } else if(source === dailymotion.shortname){ + if(dailymotion.DAILYMOTION_API_KEY !== 'undefined' && dailymotion.DAILYMOTION_API_KEY !== ''){ + const uri = dailymotion.baseAPIUrl+'/video/'+id+'?fields=created_time%2Cdescription%2Cthumbnail_480_url%2Clikes_total%2Ctitle%2Cid%2Cembed_url%2Cviews_total%2Cowner.username%2Cowner.id%2Cchannel.name'; + request(uri,{},async function (error, response, body) { + if (typeof body !== 'undefined') { + const jsonBody = JSON.parse(body); + if(response.statusCode === 200 && + typeof jsonBody.id !== 'undefined' && + jsonBody.id === id){ + const imageUrl = jsonBody.thumbnail_480_url ? jsonBody.thumbnail_480_url : null; + const title = jsonBody.title ? jsonBody.title : null; + //const channelId = jsonBody['owner.id'] ? jsonBody['owner.id'] : null; + const channelTitle = jsonBody['owner.username'] ? jsonBody['owner.username'] : null; + const description = jsonBody.description ? jsonBody.description : null; + const embedUrl = jsonBody.embed_url ? jsonBody.embed_url : null; + const publishedAt = jsonBody.created_time ? new Date(jsonBody.created_time * 1000) : null; + const interest = jsonBody['channel.name'] ? await asyncInterest(jsonBody['channel.name'], dailymotion.name) : null; + const views = jsonBody.views_total ? parseInt(jsonBody.views_total) : null; + const likes = jsonBody.likes_total ? parseInt(jsonBody.likes_total) : null; + const dislikes = null; + const data = { + videoId: id, + source: dailymotion.name, + imageUrl: imageUrl, + title: title, + channelTitle: channelTitle.charAt(0).toUpperCase() + channelTitle.slice(1), + channelUrl: dailymotion.baseChannelUrl+channelTitle, + description: description, + embedUrl: embedUrl, + interest: interest, + views: views, + likes: likes, + dislikes: dislikes, + publishedAt: publishedAt + }; + return sendMessage(res, 32, data); + } else{ + return sendError(res, 404, -1, jsonBody.error.message); + } + } + }); + } else{ + return sendError(res, 500, -1, `Error Env Variable DAILYMOTION_API_KEY missing, please contact the admin.`); + } + } else{ + return sendError(res, 500, -1, `Wrong source name`); + } + } else{ + return sendError(res, 500, -1, `No source or/and id given`); + } +}; + +// Create a new Video +exports.create = (req, res) => { + const token = checkLogin(req, res); + if(token && + typeof req.body.source !== 'undefined' && + typeof req.body.interest !== 'undefined' && + typeof req.params.id !== 'undefined'){ + const id = req.params.id; + Video.exists({userId: token.id, videoId: id, source: req.body.source, isActive: true}, function (err, docs){ + if(err){ + sendError(res, 500,100,err.message || "Some error occurred while checking if the Video already exists.", token); + } else{ + if(docs === null) { + let video; + + video = new Video({ + userId: token.id, + videoId: id, + source: req.body.source, + interest: req.body.interest, + watchedDates: [new Date()] + }); + + // Save Video in the database + video + .save(video) + .then(data => { + return sendMessage(res, 33, data, token) + }) + .catch(err => { + return sendError(res, 500,100,err.message || "Some error occurred while creating the Video.", token); + }); + } else{ + const id = docs._id.toString(); + Video.findByIdAndUpdate(id, {$push: {watchedDates: [new Date()]}}, {useFindAndModify: false, new: true}) + .then(data => { + if(data) { + return sendMessage(res, 33, data, token); + } else { + return sendError(res, 404, 105, `Video not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, 100, err.message || `Some error occurred while updating the Video with id=${id}`, token); + }); + } + } + }); + } else { + return sendError(res, 500, -1, `No source or interest or id given`, token); + } +}; + +// Retrieve all Videos +exports.findAll = (req, res) => { + const token = checkLogin(req, res); + if(token){ + let query = {}; + let condition; + + const userId = req.query.userId; + condition = userId ? userId : undefined; + query.userId = condition; + + const videoId = req.query.videoId; + condition = videoId ? videoId : undefined; + query.videoId = condition; + + const source = req.query.source; + condition = source ? source : undefined; + query.source = condition; + + const interests = req.query.interests; + condition = interests ? {$in: interests} : undefined; + query["interests.interest"] = condition + + const isActive = req.query.isActive; + condition = isActive ? isActive : undefined; + query.isActive = condition; + + const sort = req.query.sort; + if(sort !== 'undefined'){ + switch (sort){ + case 'asc': + condition = {videoId: 1}; + break; + case 'desc': + condition = {videoId: -1}; + break; + case 'createdAtAsc': + condition = {createdAt: 1}; + break; + case 'createdAtDesc': + condition = {createdAt: -1}; + break; + case 'updatedAtAsc': + condition = {updatedAt: 1}; + break; + case 'updatedAtDesc': + condition = {updatedAt: -1}; + break; + default: + condition = {createdAt: -1}; + } + } + const query_sort = {sort: condition}; + + // Remove undefined key + Object.keys(query).forEach(key => query[key] === undefined ? delete query[key] : {}); + console.log(query); + + Video.find(query, {}, query_sort) + .then(data => { + return sendMessage(res, 34, data, token); + }) + .catch(err => { + return sendError(res,500,100,err.message || "Some error occurred while finding the Videos.", token); + }); + } +}; + +// Find single Video with id +exports.findOne = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(id && ObjectId.isValid(id)){ + Video.findById(id, {}) + .then(data => { + if(data){ + return sendMessage(res, 35, data, token); + } else { + return sendError(res,404,105,`Video not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res,500,100,err.message || `Some error occurred while finding the Video with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Update Video with id +exports.update = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(typeof req.body._id !== 'undefined' || typeof req.body.id !== 'undefined'){ + return sendError(res, 500, -1, `User do not have the permission to modify id or _id`, token); + } else{ + let update = {}; + let condition; + + const watchedDate = req.body.watchedDate; + if(typeof watchedDate !== 'undefined'){ + if(watchedDate){ + condition = {watchedDates: [new Date()]} + } else { + condition = undefined; + } + } else{ + condition = undefined; + } + update.$push = condition; + + const watchedDates = req.body.watchedDates ? req.body.watchedDates : undefined; + update.watchedDates = watchedDates; + + const isActive = req.body.isActive; + if(typeof isActive !== 'undefined'){ + condition = isActive; + } else{ + condition = undefined; + } + update.isActive = condition; + + // Remove undefined key + Object.keys(update).forEach(key => update[key] === undefined ? delete update[key] : {}); + + if(id && ObjectId.isValid(id)){ + Video.updateOne({_id: id, userId: token.id, isActive: true}, update) + .then(data => { + if(data) { + return sendMessage(res, 36, update, token); + } else { + return sendError(res, 404, -1, `Video not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, -1, err.message || `Some error occurred while updating the Video with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete Video with id +exports.delete = (req, res) => { + const token = checkLogin(req, res); + if(token && typeof req.params.id !== 'undefined') { + const id = req.params.id; + if(id && ObjectId.isValid(id)){ + Video.updateOne({_id: id, userId: token.id, isActive: true}, {isActive: false}, {useFindAndModify: false}) + .then(data => { + if(data.modifiedCount > 0) { + return sendMessage(res, 37, {message: `Video ${id} was successfully deleted.`}, token); + } else { + return sendError(res, 404, 105, `Video not found with id=${id}`, token); + } + }) + .catch(err => { + return sendError(res, 500, 100, err.message || `Some error occurred while deleting the Video with id=${id}`, token); + }); + } else { + return sendError(res, 500, -1, `Error id is not valid`, token); + } + } else { + return sendError(res, 500, -1, `No id given`, token); + } +}; + +// Delete all Videos +exports.deleteAll = (req, res) => { + const token = checkLogin(req, res); + if(token) { + Video.updateMany({userId: {$eq: token.id}, isActive: true}, {isActive: false}) + .then(data => { + return sendMessage(res, 38, { + message: `${data.modifiedCount} Videos were deleted successfully.`, + }); + }) + .catch(err => { + return sendError(res, 500, 100, err.message || "Some error occurred while removing all Videos."); + }); + } +}; diff --git a/app-backend/jwtRS256.key b/app-backend/jwtRS256.key new file mode 100644 index 0000000..be25b1d --- /dev/null +++ b/app-backend/jwtRS256.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJJwIBAAKCAgEAlt1zgyBeUIHeCYz4vbxEdqtupPjwy7hqL2iFbGGoJUudbvw4 +LpRFmE0iw4cCuRGHwkO8vbqhCEJwpu7v5v/sYr3wOAioksr/PdHvbpKHMgLBUdnx +n2fGBiNn6vdKMddAjqYoGBvgIhER+A5YXQ96IrVcvlu3ZJKfXx0alTZh8bsWV7PL +rkf4j9brG66ymYdjg3VMfltQoLnV+MsubJp8WVSP7MnqJ09js2Kg7/FpvhZDhk1o +/ky4ZZ3V6gpwVl16jApBpn6DRqpnOx1i67P19remDrFgCnKMpdHU9cg4zK7NqMVo +Q5ZD1R/w6hu+y8UIpWFBQoby7GuAoUGmzAWvHrDDLJfAAnrYJmhDu7YOew5M+bAO +BfVC3jO0gCO7Eu1TAbdkoBwvlFErorGlZxGRm79BlOmd590Ri+JvLHcek9KG3Yo4 +2M171QfYOK+167SJ+yGmIXadCENAEun7ocoNPZm4SMkJC2BfS9k00j83FhVPbE/v +z0x8iGlCDQRxO/ML2YvT47bHiXpbDXz/iXE6m1YIVhabUW6xLt4LaHxSJ5hf5/4p +OF7YCxfgFssL4QQKIxsac3eEOyMpUhudVXhH3TBaKipzUdmtY99zhP+u4cKnqAUV +k3Esd15IJ3q3eOWd+s4sugTqZQ0eeHlvnRScTF0Fskrxt5uxbmrAHJ/JlgcCAwEA +AQKCAgAkcCZXNHkzLrrHScJGaXOLygo2VF4shkU3YCJhtBCduW60AL09GtjYWUEd +Qr24n3BPznaGyM3eJdX5q9pLy+J3UvLzRbwZgGg3sT4IUeHPhXTV5TMD0le04dZW +nRXRhfD0w1MJx6nR8JeLd0OGbpeMTIuy+ooIA5X0rh3ejj0H2q0hz65RUNbLPxiY +HhDLHjU96hXoqvuFZn000zZKSuHf3ZnJklc42Wb2S8sdSQzGObBjfszuJu+es4mY +Np89bOSNcGi84u+oIL5AO+/JiDTPOzojcFoHC/XGkzDBkHOPlGTd75goQuHtVwKk +OMFTjCpzb1MI03lW+vwddrLnsjPCcfY76v+5ASryMh5LFvIOaAXYrPaHU9vcaytJ +hmyiVvviPf0VbpwBqkI3MPajoFNk8uScrRQe6frhHyOgmQLIpijaZ44PV9pZYED8 +EVULn0VQzyidF2T6JMU1/Zq6/Bk29uhqUPLof42jhi/jqCX0uJAnyqOHO2ZzMF5d +xI55xFHJ+f3Zac5TacyOGA+7fsziyVWKXe73wk8RBgIbUiK0x273zvykIKJ1W19M +2ihfr8fBjLgPpfeUont2cDX90hJUkEJhsi0PwQhfZS9WtH0Z88pBjO1OZsIfRdZP +Y542RH1U1vl730CQTHl6YcqsEHV1rL0nxsBmBMjRA277xrqd0QKCAQEAxL7tjbXe +IKAy3TvXBdLzRpZjWF/6NDCvpHicfjujx7/6yxyaIg5KbWNzRI4RTuAetPuPP1V0 +7ygAR49RZTkZxzSG0JjpzkXoPjydTBDqIazJ0X1tS7ltnVMil6wiX8vm2DnlAB/S +fki5JK9x+jY/YrRIbkGp1LNMOG57pxRn3vQoJl4X1lmjTgqLiJR8lOlS/Ur07b6s +yzsBinuWTtPrB5eCHFdn7nQPoHzUdbJHO/DOs0fmp1mbQ/IbFs4vSUay/KetBjDE +4YbUyBRd6J7KIFEy2SlCeg1eyCQGK2cbghlBX7ljdWQmYaSK8QHlDRkbJaYw1q2P +9Hi+HZhXR0YxuQKCAQEAxE0ff5b1fDsZYvJU4CZ73AWxjF4hlM2KMcJ37H53tVjq +EpsQadxz2qFMZ60Nvk9lSzLMsXePgVChIXgL5s5GMesInqxbIUaQJ+fnwJaMYU+6 +OYrilV64IqUSC36Arecsm39faYk+y4I34Zqtn6iJeuiifGaVZr66uuKS820wq92g +rVnsjSjWZw4F1Zh0X9hZRqteKPQZRcqT6Nnrb48Ko0GcC7MyeXAYjEGZi9EGBiWh +xM5zzzmK1H3TrJyXopbmIxauKy+Uvzhbu8XiaCP2LJy5JKGoN39CYLZ5fD+gtHhU +oycRyCNzv512QPngBc5L+qm27FB/nUcePYhNn3DlvwKCAQAqu/nnTPiJh6JksWm8 +Bxz2WRYNDRPQoD6Wb+g19whVC9sSoQzNluMNrYtM+brCsiWZVAbCT/KNO9gLsxKP +9P4nab5assweFMskMZBNBGOGmvxfN2o1B3rKsFMUNLxmqGhk0PZvt3nGGk+0qzML +kRrlepk88aBM5gEJRN4w5VrGb6wE4W02DtRM0DeMnAwPYDq5b72Rolfv8Mod5Ug5 +qQgk5wDI3SdjAOygBF01j1qvp1eOU3DDKtBxyrWdl31n4iZggJ8xeAhboCi5qMrZ +CxyGNnzf9HlO/3Z17HU2mje8Y3Xyr9btmapIjS/st2ekF67w149GIf7hsBRDY2KZ +xaJpAoIBABg5zGFaqAEk6gUkEwV2umJ08Wx5UXujwiJR9ariELP4vSp+qI/n/QRG +U7+Xw9Jwyfd11X0xXCyBXuFah118p30RnIa4jqaUpsXGAPvrmHeJ91mSolP81Iyy +AoAYpJjRptep6ISFw5IqB+t3w2Wozw60FxlzL4z2jOTzgV85YoBTYbkDGAZzu8IM +IPwzTGlnsdze2UgEl/nc/lQGUN/7rzxmpHNtMhV6mAz3K5Ptv+iSTFyVfgVc/J0s +yirLSItwRAagje4WeS0AL5IE4eQoTAFFdjPnH3N56R82ZdfHonWjRA6+i5s/hNFp +BWcPb25H/2h3+XNAm/80/65oW+wJatECggEAfsCp+hf3lwV5RbI155z+7aAG8vsh +09r4Dw3VjtaKfhZBqk0LsiqHr8J+v49Fo0Q5nJzMEyqIN47jrLuKcqV8f43CIIuP +Rq3QpulJIisbSfhth4gJRhjpFzYAc+JLUftCNJXAii/yr0fFUg+O3XUwwwsdbDK5 +QV2N0lCRGyN1WUVH/Zm7NPeaLDz4xv4YWtz7Yj8bepbKgD8gHV4UCuApULbEwSQp +uUPksMHLIz+1DKhZVgu+/ldz1sCV7Tomj/MMONQh0jgdfpOILfSad7M/f3rHtuDt +DQHiWGLseNypmBZtaFDz1P+NkkFeSBlabbEpVOoTCUwTjIqfXPNyj/zTTQ== +-----END RSA PRIVATE KEY----- diff --git a/app-backend/jwtRS256.key.pub b/app-backend/jwtRS256.key.pub new file mode 100644 index 0000000..d89cfa5 --- /dev/null +++ b/app-backend/jwtRS256.key.pub @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlt1zgyBeUIHeCYz4vbxE +dqtupPjwy7hqL2iFbGGoJUudbvw4LpRFmE0iw4cCuRGHwkO8vbqhCEJwpu7v5v/s +Yr3wOAioksr/PdHvbpKHMgLBUdnxn2fGBiNn6vdKMddAjqYoGBvgIhER+A5YXQ96 +IrVcvlu3ZJKfXx0alTZh8bsWV7PLrkf4j9brG66ymYdjg3VMfltQoLnV+MsubJp8 +WVSP7MnqJ09js2Kg7/FpvhZDhk1o/ky4ZZ3V6gpwVl16jApBpn6DRqpnOx1i67P1 +9remDrFgCnKMpdHU9cg4zK7NqMVoQ5ZD1R/w6hu+y8UIpWFBQoby7GuAoUGmzAWv +HrDDLJfAAnrYJmhDu7YOew5M+bAOBfVC3jO0gCO7Eu1TAbdkoBwvlFErorGlZxGR +m79BlOmd590Ri+JvLHcek9KG3Yo42M171QfYOK+167SJ+yGmIXadCENAEun7ocoN +PZm4SMkJC2BfS9k00j83FhVPbE/vz0x8iGlCDQRxO/ML2YvT47bHiXpbDXz/iXE6 +m1YIVhabUW6xLt4LaHxSJ5hf5/4pOF7YCxfgFssL4QQKIxsac3eEOyMpUhudVXhH +3TBaKipzUdmtY99zhP+u4cKnqAUVk3Esd15IJ3q3eOWd+s4sugTqZQ0eeHlvnRSc +TF0Fskrxt5uxbmrAHJ/JlgcCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/app-backend/jwtRS256.sh b/app-backend/jwtRS256.sh new file mode 100755 index 0000000..64ecaff --- /dev/null +++ b/app-backend/jwtRS256.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +ssh-keygen -t rsa -b 4096 -m PEM -f jwtRS256.key -q -N "" +openssl rsa -in jwtRS256.key -pubout -outform PEM -out jwtRS256.key.pub +#rm .env +echo "JWTRS256_PRIVATE_KEY='`cat ./jwtRS256.key | base64 -w 0`'" >> .env +echo "JWTRS256_PUBLIC_KEY='`cat ./jwtRS256.key.pub | base64 -w 0`'" >> .env +source .env +rm jwtRS256.key diff --git a/app-backend/models/database/ads.model.js b/app-backend/models/database/ads.model.js new file mode 100644 index 0000000..2389c44 --- /dev/null +++ b/app-backend/models/database/ads.model.js @@ -0,0 +1,44 @@ +module.exports = mongoose => { + let schema = mongoose.Schema({ + userId: String, + title: String, + images: { + type: Array, + default: [] + }, + url: { + type: String, + default: null + }, + interests: { + type: Array, + default: [] + }, + comment: { + type: String, + default: null + }, + views: { + type: Array, + default: [] + }, + isVisible: { + type: Boolean, + default: true + }, + isActive: { + type: Boolean, + default: true + } + }, + { timestamps: true } + ); + + schema.method("toJSON", function() { + const { __v, _id, ...object } = this.toObject(); + object.id = _id; + return object; + }); + + return mongoose.model("ads", schema); +}; diff --git a/app-backend/models/database/playlists.model.js b/app-backend/models/database/playlists.model.js new file mode 100644 index 0000000..ad065a5 --- /dev/null +++ b/app-backend/models/database/playlists.model.js @@ -0,0 +1,24 @@ +module.exports = mongoose => { + let schema = mongoose.Schema({ + userId: String, + videoIds: { + type: Array, + default: [] + }, + name: String, + isActive: { + type: Boolean, + default: true + } + }, + { timestamps: true } + ); + + schema.method("toJSON", function() { + const { __v, _id, ...object } = this.toObject(); + object.id = _id; + return object; + }); + + return mongoose.model("playlists", schema); +}; diff --git a/app-backend/models/database/users.model.js b/app-backend/models/database/users.model.js new file mode 100644 index 0000000..9467b3c --- /dev/null +++ b/app-backend/models/database/users.model.js @@ -0,0 +1,48 @@ +const roles = require("../objects/role.model"); + +module.exports = mongoose => { + let schema = mongoose.Schema({ + email: String, + hashPass: String, // WARNING: We don't want to send back the hashPass + login: String, + role: { + type: Object, + default: roles.User + }, + company: String, + profileImageUrl: { + type: String, + default: "https://www.handiclubnimois.fr/wp-content/uploads/2020/10/blank-profile-picture-973460_1280.png" + }, + dateOfBirth: { + type: Date, + default: null + }, + gender: { + type: String, + default: null + }, + interests: { + type: Array, + default: null + }, + isActive: { + type: Boolean, + default: true + }, + lastConnexion: { + type: Date, + default: null + } + }, + { timestamps: true } + ); + + schema.method("toJSON", function() { + const { __v, _id, ...object } = this.toObject(); + object.id = _id; + return object; + }); + + return mongoose.model("users", schema); +}; diff --git a/app-backend/models/database/videos.model.js b/app-backend/models/database/videos.model.js new file mode 100644 index 0000000..4d322da --- /dev/null +++ b/app-backend/models/database/videos.model.js @@ -0,0 +1,29 @@ +module.exports = mongoose => { + let schema = mongoose.Schema({ + userId: String, + videoId: String, + source: String, + interest: { + type: String, + default: null + }, + watchedDates: { + type: Array, + default: null + }, + isActive: { + type: Boolean, + default: true + } + }, + { timestamps: true } + ); + + schema.method("toJSON", function() { + const { __v, _id, ...object } = this.toObject(); + object.id = _id; + return object; + }); + + return mongoose.model("videos", schema); +}; diff --git a/app-backend/models/mongodb.model.js b/app-backend/models/mongodb.model.js new file mode 100644 index 0000000..9ddb7cd --- /dev/null +++ b/app-backend/models/mongodb.model.js @@ -0,0 +1,19 @@ +const dbConfig = require("../config/mongodb.config"); +const mongoose = require("mongoose"); +mongoose.Promise = global.Promise; + +const db = {}; +db.mongoose = mongoose; + +if(typeof process.env.NODE_ENV !== 'undefined' && process.env.NODE_ENV === 'production'){ + db.url = dbConfig.prodUrl; +} else { + db.url = dbConfig.devUrl; +} + +db.users = require("./database/users.model")(mongoose); +db.playlists = require("./database/playlists.model")(mongoose); +db.videos = require("./database/videos.model")(mongoose); +db.ads = require("./database/ads.model")(mongoose); + +module.exports = db; diff --git a/app-backend/models/objects/image.model.js b/app-backend/models/objects/image.model.js new file mode 100644 index 0000000..72ec783 --- /dev/null +++ b/app-backend/models/objects/image.model.js @@ -0,0 +1,10 @@ +class Image { + constructor(base64, url, description, type){ + this.base64 = base64; + this.url = url; + this.description = description; + this.type = type; + } +} + +module.exports = Image; diff --git a/app-backend/models/objects/role.model.js b/app-backend/models/objects/role.model.js new file mode 100644 index 0000000..2e1703c --- /dev/null +++ b/app-backend/models/objects/role.model.js @@ -0,0 +1,22 @@ +module.exports = { + User: { + name: "user", + permission: 0, + isAccepted: true + }, + Advertiser: { + name: "advertiser", + permission: 5, + isAccepted: false + }, + Admin: { + name: "admin", + permission: 10, + isAccepted: false + }, + SuperAdmin: { + name: "superAdmin", + permission: 1000, + isAccepted: true + } +}; diff --git a/app-backend/models/objects/video.categories.model.js b/app-backend/models/objects/video.categories.model.js new file mode 100644 index 0000000..6eaab33 --- /dev/null +++ b/app-backend/models/objects/video.categories.model.js @@ -0,0 +1,157 @@ +const {youtube, dailymotion} = require('../../config/host.config'); +module.exports = [ + { + id: 0, + interest: "Actualités", + categories: [ + {id: "news", name: "News", source: dailymotion.name}, + {id: "25", name: "News & Politics", source: youtube.name}, + ] + }, + { + id: 1, + interest: "Animaux", + categories: [ + {id: "animals", name: "animaux", source: dailymotion.name}, + {id: "15", name: "Pets & Animals", source: youtube.name}, + ] + }, + { + id: 2, + interest: "Arts", + categories: [ + {id: "creation", name: "Art", source: dailymotion.name}, + {id: "", name: "", source: youtube.name}, + ] + }, + { + id: 3, + interest: "Autos", + categories: [ + {id: "auto", name: "Auto-Moto", source: dailymotion.name}, + {id: "2", name: "Autos & Vehicles", source: youtube.name}, + ] + }, + { + id: 4, + interest: "Divertissements", + categories: [ + {id: "tv", name: "TV", source: dailymotion.name}, + {id: "fun", name: "Humour & Divertissement", source: dailymotion.name}, + {id: "webcam", name: "Webcam", source: dailymotion.name}, + {id: "23", name: "Comedy", source: youtube.name}, + {id: "24", name: "Entertainment", source: youtube.name}, + {id: "43", name: "Shows", source: youtube.name} + ] + }, + { + id: 5, + interest: "Éducation", + categories: [ + {id: "school", name: "Éducation", source: dailymotion.name}, + {id: "27", name: "Education", source: youtube.name} + ] + }, + { + id: 6, + interest: "Événements", + categories: [ + {id: "", name: "", source: dailymotion.name}, + {id: "19", name: "Travel & Events", source: youtube.name}, + ] + }, + { + id: 7, + interest: "Films", + categories: [ + {id: "shortfilms", name: "Cinéma", source: dailymotion.name}, + {id: "1", name: "Film & Animation", source: youtube.name}, + {id: "18", name: "Short Movies", source: youtube.name}, + {id: "30", name: "Movies", source: youtube.name}, + {id: "31", name: "Anime/Animation", source: youtube.name}, + {id: "32", name: "Action/Adventure", source: youtube.name}, + {id: "33", name: "Comedy", source: youtube.name}, + {id: "35", name: "Documentary", source: youtube.name}, + {id: "36", name: "Drama", source: youtube.name}, + {id: "39", name: "Horror", source: youtube.name}, + {id: "40", name: "Sci-Fi/Fantasy", source: youtube.name}, + {id: "41", name: "Thriller", source: youtube.name}, + {id: "42", name: "Shorts", source: youtube.name}, + {id: "44", name: "Trailers", source: youtube.name} + ] + }, + { + id: 8, + interest: "Jeux vidéo", + categories: [ + {id: "videogames", name: "Jeux vidéo", source: dailymotion.name}, + {id: "20", name: "Gaming", source: youtube.name}, + ] + }, + { + id: 9, + interest: "Kids", + categories: [ + {id: "kids", name: "Kids", source: dailymotion.name}, + {id: "", name: "", source: youtube.name}, + ] + }, + { + id: 10, + interest: "Modes de vie", + categories: [ + {id: "lifestyle", name: "Lifestyle & Tutoriels", source: dailymotion.name}, + {id: "26", name: "Howto & Style", source: youtube.name}, + ] + }, + { + id: 11, + interest: "Musiques", + categories: [ + {id: "music", name: "Musique", source: dailymotion.name}, + {id: "10", name: "Music", source: youtube.name}, + ] + }, + { + id: 12, + interest: "People", + categories: [ + {id: "people", name: "Amis & Famille", source: dailymotion.name}, + {id: "21", name: "Videoblogging", source: youtube.name}, + {id: "22", name: "People & Blogs", source: youtube.name}, + {id: "37", name: "Family", source: youtube.name}, + ] + }, + { + id: 13, + interest: "Science et Technologie", + categories: [ + {id: "tech", name: "Tech", source: dailymotion.name}, + {id: "28", name: "Science & Technology", source: youtube.name}, + ] + }, + { + id: 14, + interest: "Sports", + categories: [ + {id: "sport", name: "Sport", source: dailymotion.name}, + {id: "17", name: "Sports", source: youtube.name}, + ] + }, + { + id: 15, + interest: "Voyages", + categories: [ + {id: "travel", name: "Voyages", source: dailymotion.name}, + {id: "38", name: "Foreign", source: youtube.name}, + ] + }, + { + id: 16, + interest: "Autres", + categories: [ + {id: "29", name: "Nonprofits & Activism", source: youtube.name}, + {id: "33", name: "Classics", source: youtube.name} + ] + } +]; diff --git a/app-backend/routes/ad.routes.js b/app-backend/routes/ad.routes.js new file mode 100644 index 0000000..738892b --- /dev/null +++ b/app-backend/routes/ad.routes.js @@ -0,0 +1,24 @@ +const ads = require("../controllers/ad.controller"); +module.exports = app => { + let router = require("express").Router(); + + // Create a new Ad + router.post("/ad/create", ads.create); + + // Retrieve all Ad from id if admin or session id + router.get("/ad/findAll", ads.findAll); + + // Find single Ad from id if admin or session id + router.get("/ad/findOne/:id", ads.findOne); + + // Update a Ad with ad id + router.put("/ad/update/:id", ads.update); + + // Delete a Ad with ad id + router.delete("/ad/delete/:id", ads.delete); + + // Delete all Ad from id if admin or session id + router.delete("/ad/deleteAll", ads.deleteAll); + + app.use('/api', router); +}; diff --git a/app-backend/routes/misc.routes.js b/app-backend/routes/misc.routes.js new file mode 100644 index 0000000..7aa5be6 --- /dev/null +++ b/app-backend/routes/misc.routes.js @@ -0,0 +1,9 @@ +const misc = require("../controllers/misc.controller"); +module.exports = app => { + let router = require("express").Router(); + + // Get all interests available + router.get("/misc/getInterests", misc.getInterests); + + app.use('/api', router); +}; diff --git a/app-backend/routes/playlist.routes.js b/app-backend/routes/playlist.routes.js new file mode 100644 index 0000000..3b9beeb --- /dev/null +++ b/app-backend/routes/playlist.routes.js @@ -0,0 +1,24 @@ +const playlists = require("../controllers/playlist.controller"); +module.exports = app => { + let router = require("express").Router(); + + // Create a new Playlist + router.post("/playlist/create", playlists.create); + + // Retrieve all Playlist from id if admin or session id + router.get("/playlist/findAll", playlists.findAll); + + // Find single Playlist from id if admin or session id + router.get("/playlist/findOne/:id", playlists.findOne); + + // Update a Playlist with playlist id + router.put("/playlist/update/:id", playlists.update); + + // Delete a Playlist with playlist id + router.delete("/playlist/delete/:id", playlists.delete); + + // Delete all Playlists from id if admin or session id + router.delete("/playlist/deleteAll", playlists.deleteAll); + + app.use('/api', router); +}; diff --git a/app-backend/routes/user.routes.js b/app-backend/routes/user.routes.js new file mode 100644 index 0000000..346cb04 --- /dev/null +++ b/app-backend/routes/user.routes.js @@ -0,0 +1,42 @@ +const users = require("../controllers/user.controller"); +module.exports = app => { + let router = require("express").Router(); + + // Authenticate a User + router.post("/user/auth", users.auth); + + // Logout a User + router.delete("/user/logout", users.logout); + + // Request password reset with email + router.post("/user/resetPass", users.resetPass); + + // Create and Save a new User + router.post("/user/create", users.create); + + // Retrieve all Users if admin + router.get("/user/findAll", users.findAll); + + // Find single User from id if admin or session id + router.get("/user/findOne/:id", users.findOne); + + // Update a User from id if admin or session id + router.put("/user/update/:id", users.update); + + // Delete a User from id if admin or session id + router.delete("/user/delete/:id", users.delete); + + // Delete all Users if superAdmin + router.delete("/user/deleteAll", users.deleteAll); + + // Get all Roles depending on the User session id + router.get("/user/roles", users.roles); + + // Get 1 or multiple ad adapted to the User session id + router.get("/user/ad", users.ad); + + // Get History + router.get("/user/history", users.history); + + app.use('/api', router); +}; diff --git a/app-backend/routes/video.routes.js b/app-backend/routes/video.routes.js new file mode 100644 index 0000000..a07de28 --- /dev/null +++ b/app-backend/routes/video.routes.js @@ -0,0 +1,30 @@ +const videos = require("../controllers/video.controller"); +module.exports = app => { + let router = require("express").Router(); + + // Search Videos + router.get("/video/search", videos.search); + + // Get Video with id of source + router.get("/video/get/:id", videos.get); + + // Create a new Video + router.post("/video/create/:id", videos.create); + + // Retrieve all Videos + router.get("/video/findAll", videos.findAll); + + // Find single Video with id + router.get("/video/findOne/:id", videos.findOne); + + // Update Video with id + router.put("/video/update/:id", videos.update); + + // Delete Video with id + router.delete("/video/delete/:id", videos.delete); + + // Delete all Videos + router.delete("/video/deleteAll", videos.deleteAll); + + app.use('/api', router); +}; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ee31835 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,41 @@ +version: '3.8' + +services: + frontend: + container_name: frontend + build: . + command: ng serve --host 0.0.0.0 + volumes: + - ./src:/data/frontend/ + - ./node_modules:/data/frontend/node_modules + ports: + - 4200:4200 + depends_on: + - backend + links: + - backend + + backend: + container_name: backend + build: ./backend + command: node server.js + volumes: + - ./backend:/data/backend + - ./backend/node_modules:/data/backend/node_modules + - ./backend/app:/data/backend/app + ports: + - 3000:3000 + depends_on: + - mongodb + links: + - mongodb + environment: + NODE_ENV: development + + mongodb: + image: mongo + container_name: mongodb + volumes: + - ./backend/database:/data/db + ports: + - 27017:27017 diff --git a/e2e/protractor.conf.js b/e2e/protractor.conf.js new file mode 100644 index 0000000..361e7f0 --- /dev/null +++ b/e2e/protractor.conf.js @@ -0,0 +1,37 @@ +// @ts-check +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter'); + +/** + * @type { import("protractor").Config } + */ +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './src/**/*.e2e-spec.ts' + ], + capabilities: { + browserName: 'chrome' + }, + directConnect: true, + SELENIUM_PROMISE_MANAGER: false, + baseUrl: 'http://localhost:4200/', + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + onPrepare() { + require('ts-node').register({ + project: require('path').join(__dirname, './tsconfig.json') + }); + jasmine.getEnv().addReporter(new SpecReporter({ + spec: { + displayStacktrace: StacktraceOption.PRETTY + } + })); + } +}; \ No newline at end of file diff --git a/e2e/src/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000..b4a36ca --- /dev/null +++ b/e2e/src/app.e2e-spec.ts @@ -0,0 +1,23 @@ +import { browser, logging } from 'protractor'; +import { AppPage } from './app.po'; + +describe('workspace-project App', () => { + let page: AppPage; + + beforeEach(() => { + page = new AppPage(); + }); + + it('should display welcome message', async () => { + await page.navigateTo(); + expect(await page.getTitleText()).toEqual('frontend app is running!'); + }); + + afterEach(async () => { + // Assert that there are no errors emitted from the browser + const logs = await browser.manage().logs().get(logging.Type.BROWSER); + expect(logs).not.toContain(jasmine.objectContaining({ + level: logging.Level.SEVERE, + } as logging.Entry)); + }); +}); diff --git a/e2e/src/app.po.ts b/e2e/src/app.po.ts new file mode 100644 index 0000000..c9c85ab --- /dev/null +++ b/e2e/src/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class AppPage { + async navigateTo(): Promise { + return browser.get(browser.baseUrl); + } + + async getTitleText(): Promise { + return element(by.css('app-root .content span')).getText(); + } +} diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json new file mode 100644 index 0000000..0782539 --- /dev/null +++ b/e2e/tsconfig.json @@ -0,0 +1,13 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "module": "commonjs", + "target": "es2018", + "types": [ + "jasmine", + "node" + ] + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..1aacb0a --- /dev/null +++ b/package.json @@ -0,0 +1,67 @@ +{ + "name": "frontend", + "version": "1.0.0", + "scripts": { + "ng": "ng", + "start": "node server.js", + "dev": "ng serve", + "build": "ng build --configuration production", + "test": "ng test", + "lint": "ng lint", + "e2e": "ng e2e" + }, + "private": true, + "dependencies": { + "@angular/animations": "^12.2.11", + "@angular/cdk": "^12.2.11", + "@angular/cli": "~12.2.11", + "@angular/common": "^12.2.11", + "@angular/compiler": "^12.2.11", + "@angular/compiler-cli": "~12.2.11", + "@angular/core": "^12.2.11", + "@angular/forms": "^12.2.11", + "@angular/material": "^12.2.11", + "@angular/platform-browser": "^12.2.11", + "@angular/platform-browser-dynamic": "^12.2.11", + "@angular/router": "^12.2.11", + "@ng-bootstrap/ng-bootstrap": "^10.0.0", + "angular-responsive-carousel": "^2.1.2", + "body-parser": "^1.19.0", + "bootstrap": "^5.1.3", + "chart.js": "^2.9.3", + "cookie-parser": "^1.4.5", + "cors": "^2.8.5", + "dotenv": "^10.0.0", + "express": "^4.17.1", + "jquery": "^3.6.0", + "jsonwebtoken": "^8.5.1", + "mongoose": "^6.0.12", + "ng2-charts": "^2.2.3", + "popper": "^1.0.1", + "request": "^2.88.2", + "rxjs": "~6.6.0", + "tslib": "^2.0.0", + "typescript": "~4.3.5", + "zone.js": "~0.11.3" + }, + "devDependencies": { + "@angular-devkit/build-angular": "~12.2.11", + "@angular/cli": "~12.2.11", + "@angular/compiler-cli": "~12.2.11", + "@angular/localize": "^12.2.11", + "@types/jasmine": "~3.6.0", + "@types/node": "^12.11.1", + "codelyzer": "^6.0.0", + "jasmine-core": "~3.6.0", + "jasmine-spec-reporter": "~5.0.0", + "karma": "~6.3.5", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage": "~2.0.3", + "karma-jasmine": "~4.0.0", + "karma-jasmine-html-reporter": "^1.5.0", + "protractor": "~7.0.0", + "ts-node": "~8.3.0", + "tslint": "~6.1.0", + "typescript": "~4.3.5" + } +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..91f4105 --- /dev/null +++ b/server.js @@ -0,0 +1,84 @@ +const path = require('path'); +const express = require('express'); +const app = express(); +const port = process.env.PORT || 3000; + +const cookieParser = require('cookie-parser'); +app.use(cookieParser()); + +const bodyParser = require('body-parser'); +app.use(bodyParser.urlencoded({extended:true})); +app.use(bodyParser.json()); + +const cors = require('cors'); +app.use(cors({origin: 'http://127.0.0.1:4200', credentials: true})); + +const db = require("./app-backend/models/mongodb.model"); +console.log("Db Url: ",db.url); +db.mongoose + .connect(db.url, { + useNewUrlParser: true, + useUnifiedTopology: true + }, function (err){ + const admin = new db.mongoose.mongo.Admin(db.mongoose.connection.db); + admin.buildInfo(function (err, info) { + console.log("MongoDB Version: "+info.version); + }); + if(err){ + console.log("Cannot connect to the database!", err); + process.exit(); + } else{ + console.log("Connected to the database!", db.url); + } + }); + +require("./app-backend/routes/user.routes")(app); +require("./app-backend/routes/playlist.routes")(app); +require("./app-backend/routes/video.routes")(app); +require("./app-backend/routes/ad.routes")(app); +require("./app-backend/routes/misc.routes")(app); + +const roles = require("./app-backend/models/objects/role.model"); +const User = db.users; +const login = 'superAdmin'; +const hashPass = 'hashPassSuperAdmin'; +const mail = 'superAdmin@email.admin'; + +User.exists({role: roles.SuperAdmin}, function (err, docs){ + if(err){ + console.log("Some error occurred while checking if superAdmin already exists."); + } else{ + if(docs === null){ + const user = new User({ + login: login, + hashPass: hashPass, + email: mail, + role: roles.SuperAdmin + }); + user + .save(user) + .then(data => { + data.hashPass = undefined; // Hiding hashPass on return + console.log(data); + }) + .catch(err => { + console.log(err.message || "Some error occurred while creating superAdmin."); + }); + } else { + console.log("superAdmin already exist !"); + } + } +}); + +app.get('/*all', function(req,res) { + res.sendFile(path.join(__dirname+ '/dist/index.html')); +}); + +app.use(express.static(__dirname + '/dist/frontend')); +app.get('/*', function(req,res) { + res.sendFile(path.join(__dirname+ '/dist/frontend/index.html')); +}); + +app.listen(port, '0.0.0.0',() => { + console.log (`listening on port ${port}`); +}); diff --git a/userAndAdvertiser/.browserslistrc b/userAndAdvertiser/.browserslistrc new file mode 100644 index 0000000..427441d --- /dev/null +++ b/userAndAdvertiser/.browserslistrc @@ -0,0 +1,17 @@ +# This file is used by the build system to adjust CSS and JS output to support the specified browsers below. +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries + +# For the full list of supported browsers by the Angular framework, please see: +# https://angular.io/guide/browser-support + +# You can see what browsers were selected by your queries by running: +# npx browserslist + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major versions +last 2 iOS major versions +Firefox ESR +not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. diff --git a/userAndAdvertiser/.editorconfig b/userAndAdvertiser/.editorconfig new file mode 100644 index 0000000..59d9a3a --- /dev/null +++ b/userAndAdvertiser/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/userAndAdvertiser/.gitignore b/userAndAdvertiser/.gitignore new file mode 100644 index 0000000..de51f68 --- /dev/null +++ b/userAndAdvertiser/.gitignore @@ -0,0 +1,45 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc +# Only exists if Bazel was run +/bazel-out + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events*.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db diff --git a/userAndAdvertiser/README.md b/userAndAdvertiser/README.md new file mode 100644 index 0000000..d2ab4a3 --- /dev/null +++ b/userAndAdvertiser/README.md @@ -0,0 +1,27 @@ +# UserAndAdvertiser + +This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.14. + +## Development server + +Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. + +## Code scaffolding + +Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. + +## Build + +Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. + +## Running unit tests + +Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). + +## Running end-to-end tests + +Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. + +## Further help + +To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. diff --git a/userAndAdvertiser/angular.json b/userAndAdvertiser/angular.json new file mode 100644 index 0000000..5e25aaf --- /dev/null +++ b/userAndAdvertiser/angular.json @@ -0,0 +1,113 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "userAndAdvertiser": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + }, + "@schematics/angular:application": { + "strict": true + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/userAndAdvertiser", + "index": "src/index.html", + "main": "src/main.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", + "src/styles.scss", + "node_modules/bootstrap/scss/bootstrap.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "500kb", + "maximumError": "1mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "2kb", + "maximumError": "4kb" + } + ], + "fileReplacements": [ + { + "replace": "src/environments/environment.ts", + "with": "src/environments/environment.prod.ts" + } + ], + "outputHashing": "all" + }, + "development": { + "buildOptimizer": false, + "optimization": false, + "vendorChunk": true, + "extractLicenses": false, + "sourceMap": true, + "namedChunks": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "browserTarget": "userAndAdvertiser:build:production" + }, + "development": { + "browserTarget": "userAndAdvertiser:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "userAndAdvertiser:build" + } + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "main": "src/test.ts", + "polyfills": "src/polyfills.ts", + "tsConfig": "tsconfig.spec.json", + "karmaConfig": "karma.conf.js", + "inlineStyleLanguage": "scss", + "assets": [ + "src/favicon.ico", + "src/assets" + ], + "styles": [ + "src/styles.scss" + ], + "scripts": [] + } + } + } + } + }, + "defaultProject": "userAndAdvertiser" +} diff --git a/userAndAdvertiser/karma.conf.js b/userAndAdvertiser/karma.conf.js new file mode 100644 index 0000000..e82c4ac --- /dev/null +++ b/userAndAdvertiser/karma.conf.js @@ -0,0 +1,44 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + jasmine: { + // you can add configuration options for Jasmine here + // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html + // for example, you can disable the random execution with `random: false` + // or set a specific seed with `seed: 4321` + }, + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + jasmineHtmlReporter: { + suppressAll: true // removes the duplicated traces + }, + coverageReporter: { + dir: require('path').join(__dirname, './coverage/userAndAdvertiser'), + subdir: '.', + reporters: [ + { type: 'html' }, + { type: 'text-summary' } + ] + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true + }); +}; diff --git a/userAndAdvertiser/package.json b/userAndAdvertiser/package.json new file mode 100644 index 0000000..f5a3d69 --- /dev/null +++ b/userAndAdvertiser/package.json @@ -0,0 +1,45 @@ +{ + "name": "user-and-advertiser", + "version": "0.0.0", + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "private": true, + "dependencies": { + "@angular/animations": "~12.2.0", + "@angular/cdk": "^13.1.1", + "@angular/common": "~12.2.0", + "@angular/compiler": "~12.2.0", + "@angular/core": "~12.2.0", + "@angular/forms": "~12.2.0", + "@angular/material": "^13.1.1", + "@angular/platform-browser": "~12.2.0", + "@angular/platform-browser-dynamic": "~12.2.0", + "@angular/router": "~12.2.0", + "bootstrap": "^5.1.3", + "jquery": "^3.6.0", + "ng2-charts": "^2.2.3", + "popper": "^1.0.1", + "rxjs": "~6.6.0", + "tslib": "^2.3.0", + "zone.js": "~0.11.4" + }, + "devDependencies": { + "@angular-devkit/build-angular": "~12.2.14", + "@angular/cli": "~12.2.14", + "@angular/compiler-cli": "~12.2.0", + "@types/jasmine": "~3.8.0", + "@types/node": "^12.11.1", + "jasmine-core": "~3.8.0", + "karma": "~6.3.0", + "karma-chrome-launcher": "~3.1.0", + "karma-coverage": "~2.0.3", + "karma-jasmine": "~4.0.0", + "karma-jasmine-html-reporter": "~1.7.0", + "typescript": "~4.3.5" + } +} diff --git a/userAndAdvertiser/src/app/advertiser/adList/drag-and-drop/drag-and-drop.component.html b/userAndAdvertiser/src/app/advertiser/adList/drag-and-drop/drag-and-drop.component.html new file mode 100644 index 0000000..ba7dc4f --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/drag-and-drop/drag-and-drop.component.html @@ -0,0 +1,33 @@ +
+ +
Images
+ +
Glisser déposer
+
ou
+
Cliquer pour selectionner
+
+ +
+ info +
+ +
+
+ file +
+

+ {{ file?.name }} +

+

+ {{ formatBytes(file?.size) }} +

+
+
+
+
+
+ +
+
diff --git a/userAndAdvertiser/src/app/advertiser/adList/drag-and-drop/drag-and-drop.component.scss b/userAndAdvertiser/src/app/advertiser/adList/drag-and-drop/drag-and-drop.component.scss new file mode 100644 index 0000000..91899f6 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/drag-and-drop/drag-and-drop.component.scss @@ -0,0 +1,135 @@ +.container { + width: 450px; + height: 180px; + padding: 20px 0px 20px 0px; + text-align: center; + border: dashed 1px #979797; + position: relative; + margin: 0 auto; +} + +input { + opacity: 0; + position: absolute; + z-index: 2; + width: 100%; + height: 100%; + top: 0; + left: 0; +} + + +.fileover { + animation: shake 1s; + animation-iteration-count: infinite; +} + +.files-list { + margin-top: 1.5rem; + + .single-file { + display: flex; + padding: 0.5rem; + justify-content: space-between; + align-items: center; + border: dashed 1px #979797; + margin-bottom: 1rem; + + img.delete { + margin-left: 0.5rem; + cursor: pointer; + align-self: flex-end; + } + + + display: flex; + flex-grow: 1; + + .name { + font-size: 14px; + font-weight: 500; + color: #353f4a; + margin: 0; + } + + .size { + font-size: 12px; + font-weight: 500; + color: #a4a4a4; + margin: 0; + margin-bottom: 0.25rem; + } + + .info { + width: 100% + } + } +} + +/* Shake animation */ +@keyframes shake { + 0% { + transform: translate(1px, 1px) rotate(0deg); + } + + 10% { + transform: translate(-1px, -2px) rotate(-1deg); + } + + 20% { + transform: translate(-3px, 0px) rotate(1deg); + } + + 30% { + transform: translate(3px, 2px) rotate(0deg); + } + + 40% { + transform: translate(1px, -1px) rotate(1deg); + } + + 50% { + transform: translate(-1px, 2px) rotate(-1deg); + } + + 60% { + transform: translate(-3px, 1px) rotate(0deg); + } + + 70% { + transform: translate(3px, 1px) rotate(-1deg); + } + + 80% { + transform: translate(-1px, -1px) rotate(1deg); + } + + 90% { + transform: translate(1px, 2px) rotate(0deg); + } + + 100% { + transform: translate(1px, -2px) rotate(-1deg); + } +} + + +.progress-cont { + height: 7px; + width: 100%; + border-radius: 4px; + background-color: #d0d0d0; + position: relative; + + .progress { + width: 0; + height: 100%; + position: absolute; + z-index: 1; + top: 0; + left: 0; + border-radius: 4px; + background-color: #4c97cb; + transition: 0.5s all; + } +} diff --git a/userAndAdvertiser/src/app/advertiser/adList/drag-and-drop/drag-and-drop.component.spec.ts b/userAndAdvertiser/src/app/advertiser/adList/drag-and-drop/drag-and-drop.component.spec.ts new file mode 100644 index 0000000..e4666b0 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/drag-and-drop/drag-and-drop.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DragAndDropComponent } from './drag-and-drop.component'; + +describe('DragAndDropComponent', () => { + let component: DragAndDropComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ DragAndDropComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DragAndDropComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/advertiser/adList/drag-and-drop/drag-and-drop.component.ts b/userAndAdvertiser/src/app/advertiser/adList/drag-and-drop/drag-and-drop.component.ts new file mode 100644 index 0000000..e626bef --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/drag-and-drop/drag-and-drop.component.ts @@ -0,0 +1,93 @@ +import {Component, ElementRef, EventEmitter, OnInit, Output, ViewChild} from '@angular/core'; + +@Component({ + selector: 'app-drag-and-drop', + templateUrl: './drag-and-drop.component.html', + styleUrls: ['./drag-and-drop.component.scss'] +}) +export class DragAndDropComponent +{ + @ViewChild("fileDropRef", { static: false }) fileDropEl: ElementRef; + info_image = "Vos annonces seront affichées dans un rectangle de rapport 1/5 avec: \n • 1 la largeur du rectangle \n • 5 la hauteur du rectangle" ; + files: any[] = []; + @Output() eventEmitter = new EventEmitter(); + + /** + * on file drop handler + */ + onFileDropped($event) { + this.prepareFilesList($event); + this.eventEmitter.emit(this.files); + } + + /** + * handle file from browsing + */ + fileBrowseHandler(files) { + this.prepareFilesList(files); + this.eventEmitter.emit(this.files); + } + + /** + * Delete file from files list + * @param index (File index) + */ + deleteFile(index: number) { + if (this.files[index].progress < 100) { + console.log("Upload in progress."); + return; + } + this.files.splice(index, 1); + this.eventEmitter.emit(this.files); + } + + /** + * Simulate the upload process + */ + uploadFilesSimulator(index: number) { + setTimeout(() => { + if (index === this.files.length) { + return; + } else { + const progressInterval = setInterval(() => { + if (this.files[index].progress === 100) { + clearInterval(progressInterval); + this.uploadFilesSimulator(index + 1); + } else { + this.files[index].progress += 5; + } + }, 200); + } + }, 1000); + } + + /** + * Convert Files list to normal array list + * @param files (Files List) + */ + prepareFilesList(files: Array) { + for (const item of files) { + item.progress = 0; + this.files.push(item); + } + this.fileDropEl.nativeElement.value = ""; + this.uploadFilesSimulator(0); + } + + /** + * format bytes + * @param bytes (File size in bytes) + * @param decimals (Decimals point) + */ + formatBytes(bytes, decimals = 2) { + if (bytes === 0) { + return "0 Bytes"; + } + const k = 1024; + const dm = decimals <= 0 ? 0 : decimals; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; + } + +} diff --git a/userAndAdvertiser/src/app/advertiser/adList/input-interests-ad/input-interests-ad.component.html b/userAndAdvertiser/src/app/advertiser/adList/input-interests-ad/input-interests-ad.component.html new file mode 100644 index 0000000..6def6c2 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/input-interests-ad/input-interests-ad.component.html @@ -0,0 +1,43 @@ + + + + + Sujets + + + + + + + {{interest}} + + + + + + + + + + + + {{interest}} + + + + + + diff --git a/userAndAdvertiser/src/app/advertiser/adList/input-interests-ad/input-interests-ad.component.scss b/userAndAdvertiser/src/app/advertiser/adList/input-interests-ad/input-interests-ad.component.scss new file mode 100644 index 0000000..2c3a84d --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/input-interests-ad/input-interests-ad.component.scss @@ -0,0 +1,16 @@ +mat-form-field { + width: 100%; + font-size: small; +} + +mat-chip-list { + font-size: small; +} + +mat-chip { + font-size: small; +} + +input { + font-size: small; +} diff --git a/userAndAdvertiser/src/app/advertiser/adList/input-interests-ad/input-interests-ad.component.spec.ts b/userAndAdvertiser/src/app/advertiser/adList/input-interests-ad/input-interests-ad.component.spec.ts new file mode 100644 index 0000000..deae4d3 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/input-interests-ad/input-interests-ad.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InputInterestsAdComponent } from './input-interests-ad.component'; + +describe('BarTagsComponent', () => { + let component: InputInterestsAdComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ InputInterestsAdComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InputInterestsAdComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/advertiser/adList/input-interests-ad/input-interests-ad.component.ts b/userAndAdvertiser/src/app/advertiser/adList/input-interests-ad/input-interests-ad.component.ts new file mode 100644 index 0000000..7ae81aa --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/input-interests-ad/input-interests-ad.component.ts @@ -0,0 +1,121 @@ +import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; +import {COMMA, ENTER} from "@angular/cdk/keycodes"; +import {FormControl} from "@angular/forms"; +import {Observable} from "rxjs"; +import {map, startWith} from "rxjs/operators"; +import {MatChipInputEvent} from "@angular/material/chips"; +import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete"; +import {MessageService} from "../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-input-interests-ad', + templateUrl: './input-interests-ad.component.html', + styleUrls: ['./input-interests-ad.component.scss'] +}) +export class InputInterestsAdComponent implements OnInit +{ + selectable = true; + removable = true; + separatorKeysCodes: number[] = [ENTER, COMMA]; + formControl = new FormControl(); + filteredInterests: Observable; + @Input() myInterests: string[] = []; + allInterests: string[] = []; + @Output() eventEmitter = new EventEmitter(); + @ViewChild('tagInput') tagInput: ElementRef; + interestsNotSelected: string[] = []; + + + constructor( private messageService: MessageService ) {} + + + ngOnInit(): void + { + this.filteredInterests = this.formControl.valueChanges.pipe( + startWith(null), + map((fruit: string | null) => fruit ? this._filter(fruit) : this.interestsNotSelected.slice())); + + this.messageService + .get("misc/getInterests") + .subscribe( retour => { + + if(retour.status !== "success") { + console.log(retour); + } + else { + this.allInterests = []; + for(let elt of retour.data) + { + this.allInterests.push(elt.interest); + this.interestsNotSelected.push(elt.interest); + } + } + }); + } + + + add(event: MatChipInputEvent): void + { + const value = (event.value || '').trim(); + const index = this.interestsNotSelected.indexOf(value); + if (value && (index !== -1) && (!this.myInterests.includes(value))) + { + this.myInterests.push(value); + event.chipInput!.clear(); + this.formControl.setValue(null); + this.eventEmitter.emit(this.myInterests); + this.interestsNotSelected.splice(index, 1); + } + } + + + remove(interest: string): void + { + // supprimer 'interest' de 'myInterest' + const index = this.myInterests.indexOf(interest); + if (index >= 0) this.myInterests.splice(index, 1); + this.eventEmitter.emit(this.myInterests); + + // remmettre 'interest' dans 'interestsNotSelected' + if(!this.interestsNotSelected.includes(interest)) + { + const indexOfAutres = this.interestsNotSelected.indexOf("Autres"); + if(indexOfAutres !== -1) + { + this.interestsNotSelected.splice(indexOfAutres, 1); + if(interest !== "Autres") this.interestsNotSelected.push(interest); + this.interestsNotSelected.sort(); + this.interestsNotSelected.push("Autres"); + } + else { + this.interestsNotSelected.push(interest); + if(interest !== "Autres") this.interestsNotSelected.sort(); + } + } + } + + + selected(event: MatAutocompleteSelectedEvent): void + { + const value = event.option.viewValue; + if(!this.myInterests.includes(value)) + { + this.myInterests.push(value); + const index = this.interestsNotSelected.indexOf(value); + this.interestsNotSelected.splice(index, 1); + } + this.tagInput.nativeElement.value = ''; + this.formControl.setValue(null); + this.eventEmitter.emit(this.myInterests); + } + + + private _filter(value: string): string[] + { + const filterValue = value.toLowerCase(); + return this.interestsNotSelected.filter(fruit => fruit.toLowerCase().includes(filterValue)); + } + +} diff --git a/userAndAdvertiser/src/app/advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component.html b/userAndAdvertiser/src/app/advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component.html new file mode 100644 index 0000000..78dd263 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component.html @@ -0,0 +1,185 @@ +
+
+ + + +

+ + + + + + +
+ + +
+
+ + +
+ Filtre +
+ + + + +
+ +
+ + +
+ + + +
+ visible
+ non visible +
+ + + +
+ + Sujets + + + {{formControlInterests.value ? formControlInterests.value[0] : ''}} + + (+{{formControlInterests.value.length - 1}} {{formControlInterests.value?.length === 2 ? 'autre' : 'autres'}}) + + + {{topping}} + + + +
+ + + +
+ Période de création:   + + Date de début + + +   -   + + Date de fin + + +
+ +
+ +
+
+ + +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ power_settings_new + + + Titre + {{advert.title}} + Sujets + + {{interest}}, + {{interest}} + + Date de création + {{ advert.createdAt | date:'dd/LL/YYYY à HH:mm:ss' }} + Dernière modification + {{ advert.updatedAt | date:'dd/LL/YYYY à HH:mm:ss' }} + Vues + {{advert.countViews}} + Actions + + + +
Aucune vidéo ne correspond au filtre: "{{input.value}}"
+
+ +
+

+ +
+
diff --git a/userAndAdvertiser/src/app/advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component.scss b/userAndAdvertiser/src/app/advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component.scss new file mode 100644 index 0000000..370e312 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component.scss @@ -0,0 +1,87 @@ +.myContainer { + max-width: 100vw; + height: 100vh; + overflow-x: hidden; + font-size: small; +} + + +// ---------------------------------------------------------- + + +.filtersContainer { + width: 95%; + background-color: white; + padding: 10px 10px 10px 10px; +} + +.myRow { + margin-left: 1%; +} + +.textFilter { + width: 50%; + font-size: medium; + border-radius: 5px; +} + +.btnAjouter { + background-color: white; + border: solid 1px black; +} + + +// ---------------------------------------------------------- + + +table { + margin: 0 auto; + width: 94%; + font-size: small; +} +.darkTheme table { border: solid 2px white; } + +th.mat-sort-header-sorted { + color: black; +} + +td { + font-size: small; +} + +input { + width: 30%; + font-size: large; + border-radius: 5px; +} + + +// -------------------------------------------------------------------- + + +// rong gauche +::ng-deep .mat-slide-toggle-thumb { + background-color: white !important; +} + +// trait droite +::ng-deep .mat-slide-toggle-bar { + background-color: gray !important; +} + +// rond droite +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-thumb { + background-color: white !important; +} + +// trait gauche +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-bar { + background-color: cornflowerblue !important; +} + + +// ------------------------------------------------------------------------- + +::ng-deep .mat-pseudo-checkbox-checked { + background-color: black !important; +} diff --git a/userAndAdvertiser/src/app/advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component.spec.ts b/userAndAdvertiser/src/app/advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component.spec.ts new file mode 100644 index 0000000..9492c6c --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageAdListAdvertiserComponent } from './page-ad-list-advertiser.component'; + +describe('PageAdvertiserComponent', () => { + let component: PageAdListAdvertiserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageAdListAdvertiserComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageAdListAdvertiserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component.ts b/userAndAdvertiser/src/app/advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component.ts new file mode 100644 index 0000000..8797593 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component.ts @@ -0,0 +1,304 @@ +import {AfterViewInit, Component, OnInit, ViewChild} from '@angular/core'; +import {MatSort} from "@angular/material/sort"; +import {MatTableDataSource} from "@angular/material/table"; +import {MatDialog} from "@angular/material/dialog"; +import {PopupAddOrUpdateAdComponent} from "../popup-add-or-update-ad/popup-add-or-update-ad.component"; +import {MatSnackBar} from "@angular/material/snack-bar"; +import {PopupDeleteAdAdvertiserComponent} from "../popup-delete-ad-advertiser/popup-delete-ad-advertiser.component"; +import {MatPaginator} from "@angular/material/paginator"; +import {PopupVisualizeImagesAdvertiserComponent} from "../popup-visualize-images-advertiser/popup-visualize-images-advertiser.component"; +import {FormControl} from "@angular/forms"; +import {HttpParams} from "@angular/common/http"; +import {ThemeService} from "../../../utils/theme/theme.service"; +import {MessageService} from "../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-page-ad-list-advertiser', + templateUrl: './page-ad-list-advertiser.component.html', + styleUrls: ['./page-ad-list-advertiser.component.scss'] +}) +export class PageAdListAdvertiserComponent implements AfterViewInit +{ + displayedColumns: string[] = [ 'isVisible', 'title', 'interests', 'createdAt', 'updatedAt', 'countViews', 'actions' ]; + tabAdvertWithCountViews: any[] = []; + dataSource; + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + + visible: boolean = true; + noVisible: boolean = true; + startDate: Date = null; + endDate: Date = null; + formControlInterests = new FormControl(); + + allVideoCategorie = []; + allInterests: string[] = []; + + + constructor( public themeService: ThemeService, + public dialog: MatDialog, + private snackBar: MatSnackBar, + private messageService: MessageService ) { } + + + ngAfterViewInit(): void + { + // Ask interests + this.messageService + .get("misc/getInterests") + .subscribe(ret => this.afterReceivingInterests(ret), err => this.afterReceivingInterests(err) ); + + // Ask ads + let params = new HttpParams(); + params = params.append("isActive", true); + this.messageService + .get("ad/findAll", params) + .subscribe(ret => this.afterReceivingAds(ret), err => this.afterReceivingAds(err)); + } + + + afterReceivingInterests(retour: any): void + { + if(retour.status !== "success") { + console.log("afterReceivingInterests"); + console.log(retour); + } + else { + this.allVideoCategorie = retour.data; + this.allInterests = retour.data.map(x => x.interest); + this.allInterests.sort(); + } + } + + + afterReceivingAds(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + if(retour.data.length !== 0) + { + for(let advert of retour.data) this.tabAdvertWithCountViews.push(this.advertToAdvertWithCountViews(advert)); + this.dataSource = new MatTableDataSource(); + this.onFilter(); + } + } + } + + + applyFilter(event: Event): void + { + const filterValue = (event.target as HTMLInputElement).value; + this.dataSource.filter = filterValue.trim().toLowerCase(); + } + + + onVisualizeImages(advert: any) + { + if(advert.images.length !== 0) + { + const config = { + width: '30%', + height: '90%', + data: { images: advert.images } + }; + this.dialog + .open(PopupVisualizeImagesAdvertiserComponent, config) + .afterClosed() + .subscribe(retour => {}); + } + else { + const config = { duration: 2000, panelClass: "custom-class" }; + const message = "Cette annonce ne contient aucune image" ; + this.snackBar.open( message, "", config); + } + } + + + onAdd(): void + { + const config = { + width: '75%', + //height: '80%', + panelClass: 'custom-dialog-container', + data: { + action: "add", + advert: null, + allVideoCategorie: this.allVideoCategorie, + allTitle: this.tabAdvertWithCountViews.map(x => x.title) + } + }; + this.dialog + .open(PopupAddOrUpdateAdComponent, config) + .afterClosed() + .subscribe( advertAdded => { + + const config = { duration: 1000, panelClass: "custom-class" }; + let message = "" ; + if((advertAdded === undefined) || (advertAdded === null)) { + message = "Opération annulée" ; + } + else { + this.tabAdvertWithCountViews.push(this.advertToAdvertWithCountViews(advertAdded)); + this.onFilter(); + message = "L'annonce a bien été ajoutée ✔" ; + } + this.snackBar.open( message, "", config); + }); + } + + + onUpdate(advertToUpdate: any): void + { + const config = { + width: '75%', + //height: '80%', + panelClass: 'custom-dialog-container', + data: { + action: "update", + advert: advertToUpdate, + allVideoCategorie: this.allVideoCategorie, + allTitle: this.tabAdvertWithCountViews.map(x => x.title) + } + }; + this.dialog + .open(PopupAddOrUpdateAdComponent, config) + .afterClosed() + .subscribe( advertUpdated => { + + const config = { duration: 1000, panelClass: "custom-class" }; + let message = "" ; + if((advertUpdated === undefined) || (advertUpdated === null)) { + message = "Opération annulée" ; + } + else { + const index = this.tabAdvertWithCountViews.findIndex(elt => (elt.id === advertToUpdate.id)); + this.tabAdvertWithCountViews.splice(index, 1, this.advertToAdvertWithCountViews(advertUpdated)); + this.onFilter(); + message = "L'annonce a bien été modifiée ✔" ; + } + this.snackBar.open( message, "", config); + }); + } + + + onDelete(advert: any): void + { + const config = { + data: { advert: advert } + }; + this.dialog + .open(PopupDeleteAdAdvertiserComponent, config) + .afterClosed() + .subscribe( retour => { + + const config = { duration: 1000, panelClass: "custom-class" }; + let message = "" ; + if((retour === undefined) || (retour === null)) { + message = "Opération annulée" ; + } + else { + const index = this.dataSource.data.findIndex( elt => (elt.id === advert.id)); + this.dataSource.data.splice(index, 1); + this.dataSource.data = this.dataSource.data; + this.dataSource = this.dataSource; + message = advert.title + " a bien été supprimée ✔" ; + } + this.snackBar.open( message, "", config); + }); + } + + + onFilter(): void + { + if(this.dataSource === null || this.dataSource === undefined) this.dataSource = new MatTableDataSource(); + this.dataSource.data = []; + for(let advert of this.tabAdvertWithCountViews) + { + let valide: boolean = true; + + if(advert.isVisible && this.visible) valide = true; + else if((!advert.isVisible) && this.noVisible) valide = true; + else valide = false; + + if(valide) + { + if ((advert.createdAt === null) && (this.startDate !== null)) valide = false; + else if ((advert.createdAt === null) && (this.endDate !== null)) valide = false; + else if (this.startDate !== null) + { + if(this.startDate.getTime() > advert.createdAt.getTime()) valide = false; + else if (this.endDate !== null) + { + if(this.endDate.getTime() < advert.createdAt.getTime()) valide = false; + } + } + } + + if(valide) { + if(this.formControlInterests.value !== null) { + for (let interest of this.formControlInterests.value) { + if (advert.interests.indexOf(interest) === -1) { + valide = false; + break; + } + } + } + } + + if(valide) this.dataSource.data.push(advert); + } + + this.dataSource = new MatTableDataSource(this.dataSource.data); + this.dataSource.sort = this.sort; + this.dataSource.paginator = this.paginator; + } + + + onNewStartDate(event): void { + this.startDate = new Date(event); + } + + onNewEndDate(event): void { + this.endDate = new Date(event); + } + + + onSliderIsVisible(advert: any): void + { + // il faut envoyer la négation de user.isActive + this.messageService + .put("ad/update/"+advert.id, { isVisible: !advert.isVisible }) + .subscribe( + ret => {}, + err => { + console.log("onSliderIsVisible"); + console.log(err); + } + ); + } + + + advertToAdvertWithCountViews(advert) + { + return { + id: advert.id, + userId: advert.userId, + title: advert.title, + url: advert.url, + images: advert.images, + interests: advert.interests.map(x => x.interest), + comment: advert.comment, + views: advert.views, + countViews: advert.views.length, + isVisible: advert.isVisible, + isActive: advert.isActive, + createdAt: advert.createdAt, + updatedAt: advert.updatedAt, + } + } + +} diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-add-or-update-ad/popup-add-or-update-ad.component.html b/userAndAdvertiser/src/app/advertiser/adList/popup-add-or-update-ad/popup-add-or-update-ad.component.html new file mode 100644 index 0000000..e3b7986 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-add-or-update-ad/popup-add-or-update-ad.component.html @@ -0,0 +1,83 @@ +
+
+ + +

{{title}}

+ + +
+ + + +
+ + +
+ + + + Titre annonce + + + + + + + + + Commentaire + +
+ + + + URL + +
+ + + Visible

+ + +
+
+ Images déjà associées: +
+
+
+ + {{image.description}} + + +
+
+
+ +
+ + +
+ +
+ +
+ + +
+ + +
+ {{errorMessage}} +
+ + + + + + + + +
+
diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-add-or-update-ad/popup-add-or-update-ad.component.scss b/userAndAdvertiser/src/app/advertiser/adList/popup-add-or-update-ad/popup-add-or-update-ad.component.scss new file mode 100644 index 0000000..3bb2eed --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-add-or-update-ad/popup-add-or-update-ad.component.scss @@ -0,0 +1,90 @@ +.myContainer1 { + padding: 10px 10px 0px 25px; + margin: 0px 0px 0px 0px; + font-size: small; +} + + +.myContainer2 { + padding: 0px 0px 0px 0px; + margin: 0px 0px 0px 0px; + overflow-y: hidden; + overflow-x: hidden; + -ms-overflow-style: none; + scrollbar-width: none; +} +.myContainer2::-webkit-scrollbar { + display: none; +} + + + +h1 { + text-align: center; + font-size: large; +} + +.col-6, .col-8 { + border-left: solid 1px #a4a4a4; +} + + +// ------------------------------------------------------------------------- + +.titleContainer { + width: 100%; +} + +.commentContainer { + width: 100%; +} + +.imageContainer { + border: solid 1px grey; +} + +mat-dialog-actions { + margin-bottom: 0px; +} + +button { + font-size: small; +} + + +// ------------------------------------------------------------------------- +// --- LightTheme --- + +// aura +.lightTheme ::ng-deep .mat-checkbox-ripple .mat-ripple-element { + background-color: grey !important; +} + +// contenu coche +.lightTheme ::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background { + background-color: black !important; +} + +// indeterminate +.lightTheme ::ng-deep .mat-checkbox .mat-checkbox-frame { + border-color: black !important; + background-color: white !important; +} + +// --- DarkTheme --- + +// aura +.darTheme ::ng-deep .mat-checkbox-ripple .mat-ripple-element { + background-color: grey !important; +} + +// contenu coche +.darkTheme ::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background { + background-color: black !important; +} + +// indeterminate +.darkTheme ::ng-deep .mat-checkbox .mat-checkbox-frame { + border-color: white !important; + //background-color: white !important; +} diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-add-or-update-ad/popup-add-or-update-ad.component.spec.ts b/userAndAdvertiser/src/app/advertiser/adList/popup-add-or-update-ad/popup-add-or-update-ad.component.spec.ts new file mode 100644 index 0000000..ba74952 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-add-or-update-ad/popup-add-or-update-ad.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupAddOrUpdateAdComponent } from './popup-add-or-update-ad.component'; + +describe('PopupAddOrUpdateAdComponent', () => { + let component: PopupAddOrUpdateAdComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupAddOrUpdateAdComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupAddOrUpdateAdComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-add-or-update-ad/popup-add-or-update-ad.component.ts b/userAndAdvertiser/src/app/advertiser/adList/popup-add-or-update-ad/popup-add-or-update-ad.component.ts new file mode 100644 index 0000000..e927f68 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-add-or-update-ad/popup-add-or-update-ad.component.ts @@ -0,0 +1,220 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {MessageService} from "../../../utils/message/message.service"; +import {ThemeService} from "../../../utils/theme/theme.service"; + + + + +const ADVERT_VIDE = { + _id: "", + userId: "", + title: "", + url: "", + images: [], + interests: [], + comment: "", + views: [], + isVisible: true, + isActive: true, + createdAt: new Date(), + updatedAt: new Date(), +} + + +@Component({ + selector: 'app-popup-add-or-update-ad', + templateUrl: './popup-add-or-update-ad.component.html', + styleUrls: ['./popup-add-or-update-ad.component.scss'] +}) +export class PopupAddOrUpdateAdComponent implements OnInit +{ + advert: any; + title: string = "" ; + allVideoCategorie = []; + allTitle = []; + + tabOfNewImagesBase64 = []; + tabOfNewImagesName = []; + + hasError: boolean = false; + errorMessage: string = "" ; + + + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data, + private messageService: MessageService, + public themeService: ThemeService ) { } + + + ngOnInit(): void + { + this.allVideoCategorie = this.data.allVideoCategorie; + this.allTitle = this.data.allTitle.slice(); + if(this.data.action === "add") + { + this.advert = Object.assign({}, ADVERT_VIDE); + this.advert.images = []; + this.advert.interests = []; + this.title = "Ajouter annonce" ; + } + else + { + this.advert = Object.assign({}, this.data.advert); + this.advert.interests = this.data.advert.interests.slice(); + this.title = "Modifier annonce" ; + const indexOldTitle = this.allTitle.findIndex(title => title == this.advert.title); + this.allTitle.splice(indexOldTitle, 1); + } + } + + + onValidate(): void + { + this.checkField(); + if(!this.hasError) + { + // preparation des donnees + this.prepareAdvertInterests(); + this.prepareAdvertImages(); + + // si creation + if (this.data.action === "add") + { + this.messageService + .post("ad/create", this.advert) + .subscribe(ret => this.onCreateCallback(ret), err => this.onCreateCallback(err)); + } + // si update + else + { + const id = this.advert.id; + Reflect.deleteProperty(this.advert, "id"); + Reflect.deleteProperty(this.advert, "_id"); + this.messageService + .put("ad/update/" + id, this.advert) + .subscribe(ret => this.onUpdateCallback(ret, id), err => this.onUpdateCallback(err, id)); + } + } + } + + + checkField() + { + if(this.advert.title.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'titre'." ; + this.hasError = true; + } + else if(this.allTitle.includes(this.advert.title)) { + this.errorMessage = "Ce titre est déjà pris." ; + this.hasError = true; + } + else if((this.advert.images.length === 0) && (this.tabOfNewImagesName.length === 0)) { + this.errorMessage = "Veuillez uploader au moins une image." ; + this.hasError = true; + } + else { + this.errorMessage = ""; + this.hasError = false; + } + } + + + + onCreateCallback(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + this.dialogRef.close(); + } + else { + this.dialogRef.close(retour.data); + } + } + + + onUpdateCallback(retour: any, id: string): void + { + if(retour.status !== "success") { + console.log(retour); + this.dialogRef.close(); + } + else { + this.advert.id = id; + this.dialogRef.close(this.advert); + } + } + + + onEventInputTags(myTags: string[]): void + { + this.advert.interests = myTags; + } + + + onRemoveImgAlreadyPresent(image) + { + const index = this.advert.images.indexOf(image); + this.advert.images.splice(index, 1); + } + + + onReceiveNewImages(files: any): void + { + this.tabOfNewImagesBase64 = []; + this.tabOfNewImagesName = []; + if(files) + { + for(let file of files) + { + if(file) + { + const reader = new FileReader(); + reader.onload = this.handleReaderLoaded.bind(this); + this.tabOfNewImagesName.push(file.name) + reader.readAsBinaryString(file); + } + } + } + } + handleReaderLoaded(e) + { + this.tabOfNewImagesBase64.push('data:image/png;base64,' + btoa(e.target.result)) + } + + + // Met bien en forme les "images" avant d'être envoyer + prepareAdvertImages(): void + { + for(let i=0; i + Êtes-vous sûr de vouloir supprimer l'annonce {{advert.title}} ? + + + + + + diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-delete-ad-advertiser/popup-delete-ad-advertiser.component.scss b/userAndAdvertiser/src/app/advertiser/adList/popup-delete-ad-advertiser/popup-delete-ad-advertiser.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-delete-ad-advertiser/popup-delete-ad-advertiser.component.spec.ts b/userAndAdvertiser/src/app/advertiser/adList/popup-delete-ad-advertiser/popup-delete-ad-advertiser.component.spec.ts new file mode 100644 index 0000000..632a177 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-delete-ad-advertiser/popup-delete-ad-advertiser.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupDeleteAdAdvertiserComponent } from './popup-delete-ad-advertiser.component'; + +describe('PopupDeleteAdComponent', () => { + let component: PopupDeleteAdAdvertiserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupDeleteAdAdvertiserComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupDeleteAdAdvertiserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-delete-ad-advertiser/popup-delete-ad-advertiser.component.ts b/userAndAdvertiser/src/app/advertiser/adList/popup-delete-ad-advertiser/popup-delete-ad-advertiser.component.ts new file mode 100644 index 0000000..c137557 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-delete-ad-advertiser/popup-delete-ad-advertiser.component.ts @@ -0,0 +1,47 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {MessageService} from "../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-popup-delete-ad-advertiser', + templateUrl: './popup-delete-ad-advertiser.component.html', + styleUrls: ['./popup-delete-ad-advertiser.component.scss'] +}) +export class PopupDeleteAdAdvertiserComponent implements OnInit +{ + advert: any; + + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data, + private messageService: MessageService) { } + + + ngOnInit(): void + { + this.advert = this.data.advert; + } + + + onValidate(): void + { + this.messageService + .delete("ad/delete/"+this.advert.id) + .subscribe(ret => this.onValidateCallback(ret), err => this.onValidateCallback(err)); + } + + + onValidateCallback(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + this.dialogRef.close(); + } + else { + this.dialogRef.close(true); + } + } + +} diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-ad-advertiser/popup-visualize-ad-advertiser.component.html b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-ad-advertiser/popup-visualize-ad-advertiser.component.html new file mode 100644 index 0000000..a768258 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-ad-advertiser/popup-visualize-ad-advertiser.component.html @@ -0,0 +1,71 @@ +
+ +

{{advert.title}}

+ + + + + + + +
+
Images:
+
+
{{image.url}}
+
+
+ + +
+
Centre d'intérêt :
+
+
• {{tag}}
+
+
+ + +
+
Commentaire:
+
{{advert.comment}}
+
+ + +
+ +
{{advert.views}}
+
+ + +
+ +
+ {{ advert.createdAt | date:'dd/LL/YYYY à HH:mm:ss' }} +
+
+ + +
+ +
+ {{ advert.updatedAt | date:'dd/LL/YYYY à HH:mm:ss' }} +
+
+ + +
+ +
+ checked + close +
+
+ +
+ + + + + + + +
diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-ad-advertiser/popup-visualize-ad-advertiser.component.scss b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-ad-advertiser/popup-visualize-ad-advertiser.component.scss new file mode 100644 index 0000000..3e00dee --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-ad-advertiser/popup-visualize-ad-advertiser.component.scss @@ -0,0 +1,28 @@ +.lightTheme, .darkTheme { + background-image: none; +} + + +h1 { + text-align: center; + font-size: xx-large; +} + + +.myRow { + margin: 15px 0px 15px 0px; +} + + +.myLabel { + text-align: right; + padding: 0px 5px 0px 0px; + margin: 0px; + font-weight: bold; +} + +.myValue { + text-align: left; + padding: 0px 0px 0px 5px; + margin: 0px; +} diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-ad-advertiser/popup-visualize-ad-advertiser.component.spec.ts b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-ad-advertiser/popup-visualize-ad-advertiser.component.spec.ts new file mode 100644 index 0000000..56aedbc --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-ad-advertiser/popup-visualize-ad-advertiser.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupVisualizeAdAdvertiserComponent } from './popup-visualize-ad-advertiser.component'; + +describe('PopupVisualizeAdComponent', () => { + let component: PopupVisualizeAdAdvertiserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupVisualizeAdAdvertiserComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupVisualizeAdAdvertiserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-ad-advertiser/popup-visualize-ad-advertiser.component.ts b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-ad-advertiser/popup-visualize-ad-advertiser.component.ts new file mode 100644 index 0000000..80f5e09 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-ad-advertiser/popup-visualize-ad-advertiser.component.ts @@ -0,0 +1,26 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material/dialog"; +import {ThemeService} from "../../../utils/theme/theme.service"; + + + +@Component({ + selector: 'app-popup-visualize-ad-advertiser', + templateUrl: './popup-visualize-ad-advertiser.component.html', + styleUrls: ['./popup-visualize-ad-advertiser.component.scss'] +}) +export class PopupVisualizeAdAdvertiserComponent implements OnInit +{ + advert: any; + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data, + public themeService: ThemeService, + public dialog: MatDialog ) { } + + ngOnInit(): void + { + this.advert = this.data.advert; + } + +} diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-images-advertiser/popup-visualize-images-advertiser.component.html b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-images-advertiser/popup-visualize-images-advertiser.component.html new file mode 100644 index 0000000..dfbc2fe --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-images-advertiser/popup-visualize-images-advertiser.component.html @@ -0,0 +1,20 @@ +
+

+ +
+ + + + + + + + + + + + + + diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-images-advertiser/popup-visualize-images-advertiser.component.scss b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-images-advertiser/popup-visualize-images-advertiser.component.scss new file mode 100644 index 0000000..eb60d48 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-images-advertiser/popup-visualize-images-advertiser.component.scss @@ -0,0 +1,14 @@ +carousel { + width: 100%; + margin: 0 auto; + text-align: center; + justify-content: center +} + + + +.dialog-title { + display: flex; + justify-content: space-between; + align-items: center; +} diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-images-advertiser/popup-visualize-images-advertiser.component.spec.ts b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-images-advertiser/popup-visualize-images-advertiser.component.spec.ts new file mode 100644 index 0000000..25da0db --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-images-advertiser/popup-visualize-images-advertiser.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupVisualizeImagesAdvertiserComponent } from './popup-visualize-images-advertiser.component'; + +describe('PopupVisualizeImagesComponent', () => { + let component: PopupVisualizeImagesAdvertiserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupVisualizeImagesAdvertiserComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupVisualizeImagesAdvertiserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-images-advertiser/popup-visualize-images-advertiser.component.ts b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-images-advertiser/popup-visualize-images-advertiser.component.ts new file mode 100644 index 0000000..59e7c3d --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/adList/popup-visualize-images-advertiser/popup-visualize-images-advertiser.component.ts @@ -0,0 +1,38 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; + + + +@Component({ + selector: 'app-popup-visualize-images-advertiser', + templateUrl: './popup-visualize-images-advertiser.component.html', + styleUrls: ['./popup-visualize-images-advertiser.component.scss'] +}) +export class PopupVisualizeImagesAdvertiserComponent implements OnInit +{ + tabImages = []; + index: number = 0; + nbImage: number = 0; + + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data ) { } + + + ngOnInit(): void + { + this.tabImages = this.data.images; + this.nbImage = this.tabImages.length; + } + + onPrecedent(): void + { + if(this.index !== 0) this.index -= 1; + } + + onSuivant(): void + { + if(this.index !== (this.nbImage-1)) this.index += 1; + } + +} diff --git a/userAndAdvertiser/src/app/advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component.html b/userAndAdvertiser/src/app/advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component.html new file mode 100644 index 0000000..9af3317 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component.html @@ -0,0 +1,49 @@ +
+
+ + + + + +
+ + +
+ +
+ + +
+
Entreprise:
+
{{advertiser.company}}
+
+ + +
+
Pseudo:
+
{{advertiser.login}}
+
+ + +
+
Mail:
+
{{advertiser.email}}
+
+ + +
+
Date de création:
+
{{advertiser.createdAt | date:'dd/LL/YYYY'}}
+
+ + +
+ +
+ +
+ + +
+
diff --git a/userAndAdvertiser/src/app/advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component.scss b/userAndAdvertiser/src/app/advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component.scss new file mode 100644 index 0000000..966c9a2 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component.scss @@ -0,0 +1,61 @@ +.myContainer { + max-width: 100vw; + height: 100vh; + overflow-x: hidden; +} + + +.boite { + margin-left: auto; + margin-right: auto; + width: 25%; + margin-top: 10vh; + border: solid 3px; + border-radius: 10px; + padding: 20px 40px 20px 40px; + background-color: #ffffff; + text-align: center; + box-shadow: 10px 5px 5px black; +} +.lightTheme .boite { + border-color: black; +} +.darkTheme .boite { + border-color: white; +} + + +img { + margin: 0px 0px 10px 0px; + width: 5vw; + height: 5vw; + border: solid 2px black; + border-radius: 50%; + font-size: xxx-large; +} + + +.myRow { + margin: 15px 0px 15px 0px; +} +.myLabel { + text-align: right; + padding: 0px 5px 0px 0px; + margin: 0px; + font-weight: bold; +} +.myValue { + text-align: left; + padding: 0px 0px 0px 5px; + margin: 0px; +} + + +.btnContainer { + text-align: center; + margin-top: 40px; +} +.myBtn { + border: solid 1px black; + background-color: white; +} diff --git a/userAndAdvertiser/src/app/advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component.spec.ts b/userAndAdvertiser/src/app/advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component.spec.ts new file mode 100644 index 0000000..ebb9617 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageProfilAdvertiserComponent } from './page-profil-advertiser.component'; + +describe('PageProfilAdvertiserComponent', () => { + let component: PageProfilAdvertiserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageProfilAdvertiserComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageProfilAdvertiserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component.ts b/userAndAdvertiser/src/app/advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component.ts new file mode 100644 index 0000000..ee16328 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component.ts @@ -0,0 +1,89 @@ +import { Component, OnInit } from '@angular/core'; +import {MatDialog} from "@angular/material/dialog"; +import {MatSnackBar} from "@angular/material/snack-bar"; +import {PopupUpdateAdvertiserComponent} from "../popup-update-advertiser/popup-update-advertiser.component"; +import {ThemeService} from "../../../utils/theme/theme.service"; +import {MessageService} from "../../../utils/message/message.service"; +import {ProfilService} from "../../../utils/profil/profil.service"; + + + +@Component({ + selector: 'app-page-profil-advertiser', + templateUrl: './page-profil-advertiser.component.html', + styleUrls: ['./page-profil-advertiser.component.scss'] +}) +export class PageProfilAdvertiserComponent implements OnInit +{ + advertiser = { + _id: "", + login: "", + hashPass: "", + email: "", + role: { + name: "advertiser", + permission: 5, + isAccepted: true, + }, + profileImageUrl: "", + dateOfBirth: null, + gender: "man", + interests: [], + company: "", + isActive: true, + createdAt: new Date(), + updatedAt: new Date(), + lastConnexion: null + }; + + + constructor( public themeService: ThemeService, + public dialog: MatDialog, + private snackBar: MatSnackBar, + private messageService: MessageService, + private profilService: ProfilService ) { } + + + ngOnInit(): void + { + this.messageService + .get( "user/findOne/"+this.profilService.getId()) + .subscribe( retour => this.ngOnInitCallback(retour), err => this.ngOnInitCallback(err) ) + } + + + ngOnInitCallback(retour: any) + { + if(retour.status !== "success") { + console.log(retour); + } + else { + this.advertiser = retour.data; + } + } + + + onModifier() + { + const config = { + width: '25%', + data: { advertiser: this.advertiser } + }; + this.dialog + .open(PopupUpdateAdvertiserComponent, config) + .afterClosed() + .subscribe(retour => { + + if((retour === null) || (retour === undefined)) + { + const config = { duration: 1000, panelClass: "custom-class" }; + this.snackBar.open( "Opération annulé", "", config); + } + else + { + this.advertiser = retour; + } + }); + } + +} diff --git a/userAndAdvertiser/src/app/advertiser/myProfil/popup-update-advertiser/popup-update-advertiser.component.html b/userAndAdvertiser/src/app/advertiser/myProfil/popup-update-advertiser/popup-update-advertiser.component.html new file mode 100644 index 0000000..4951e5c --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/myProfil/popup-update-advertiser/popup-update-advertiser.component.html @@ -0,0 +1,65 @@ +
+
+ + +
+
+ +
+ + +

+ + + + Entreprise + +
+ + + + Pseudo + +
+ + +
+ + +
+ Modifier mot de passe: + +
+ + +
+ + + Nouveau mot de passe + + +
+ + + Confirmation nouveau mot de passe + + +
+

+ + +
+ + +
+ {{errorMessage}} +
+ + +
+ + +
+ +
+
diff --git a/userAndAdvertiser/src/app/advertiser/myProfil/popup-update-advertiser/popup-update-advertiser.component.scss b/userAndAdvertiser/src/app/advertiser/myProfil/popup-update-advertiser/popup-update-advertiser.component.scss new file mode 100644 index 0000000..1968e90 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/myProfil/popup-update-advertiser/popup-update-advertiser.component.scss @@ -0,0 +1,33 @@ +.boite { + font-size: small; +} + +button { + font-size: small; +} + +img { + margin: 0px 0px 10px 0px; + width: 5vw; + height: 5vw; + border: solid 2px black; + border-radius: 50%; + font-size: xxx-large; +} + +// ------------------------------------------------------------------------- + +// aura +::ng-deep .mat-checkbox-ripple .mat-ripple-element { + background-color: grey !important; +} + +// contenu coche +::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background { + background-color: black !important; +} + +// indeterminate +::ng-deep .mat-checkbox .mat-checkbox-frame { + background-color: white !important; +} diff --git a/userAndAdvertiser/src/app/advertiser/myProfil/popup-update-advertiser/popup-update-advertiser.component.spec.ts b/userAndAdvertiser/src/app/advertiser/myProfil/popup-update-advertiser/popup-update-advertiser.component.spec.ts new file mode 100644 index 0000000..dde7ef9 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/myProfil/popup-update-advertiser/popup-update-advertiser.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupUpdateAdvertiserComponent } from './popup-update-advertiser.component'; + +describe('PopupUpdateAdvertiserComponent', () => { + let component: PopupUpdateAdvertiserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupUpdateAdvertiserComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupUpdateAdvertiserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/advertiser/myProfil/popup-update-advertiser/popup-update-advertiser.component.ts b/userAndAdvertiser/src/app/advertiser/myProfil/popup-update-advertiser/popup-update-advertiser.component.ts new file mode 100644 index 0000000..b93fdef --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/myProfil/popup-update-advertiser/popup-update-advertiser.component.ts @@ -0,0 +1,124 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {MessageService} from "../../../utils/message/message.service"; +import {ProfilService} from "../../../utils/profil/profil.service"; + + + +@Component({ + selector: 'app-popup-update-advertiser', + templateUrl: './popup-update-advertiser.component.html', + styleUrls: ['./popup-update-advertiser.component.scss'] +}) +export class PopupUpdateAdvertiserComponent implements OnInit +{ + advertiserCopy; + newPassword: string = ""; + confirmNewPassword: string = "" ; + changePassword: boolean = false ; + hasError: boolean = false; + errorMessage: string = "" ; + + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data, + private messageService: MessageService, + private profilService: ProfilService ) { } + + + ngOnInit(): void + { + const advertiser0 = this.data.advertiser; + this.advertiserCopy = { + _id: advertiser0._id, + login: advertiser0.login, + hashPass: advertiser0.hashPass, + email: advertiser0.email, + role: { + name: advertiser0.role.name, + permission: advertiser0.role.permission, + isAccepted: advertiser0.role.isAccepted, + }, + profileImageUrl: advertiser0.profileImageUrl, + dateOfBirth: advertiser0.dateOfBirth, + gender: advertiser0.gender, + interests: [], + company: advertiser0.company, + isActive: advertiser0.isActive, + createdAt: advertiser0.createdAt, + updatedAt: advertiser0.updatedAt, + lastConnexion: new Date() + }; + for(let interest of advertiser0.interests) this.advertiserCopy.interests.push(interest); + } + + + onValider() + { + this.checkField(); + if(!this.hasError) + { + if(this.changePassword) this.advertiserCopy.hashPass = this.newPassword; + const data = { + login: this.advertiserCopy.login, + hashPass: this.advertiserCopy.hashPass, + email: this.advertiserCopy.email, + profileImageUrl: this.advertiserCopy.profileImageUrl, + company: this.advertiserCopy.company + }; + this.messageService + .put("user/update/"+this.profilService.getId(), data) + .subscribe( ret => this.onValiderCallback(ret), err => this.onValiderCallback(err) ); + } + } + + + onValiderCallback(retour: any) + { + if(retour.status !== "success") { + console.log(retour); + this.dialogRef.close(null); + } + else { + this.profilService.setProfileImageUrl(this.advertiserCopy.profileImageUrl); + this.dialogRef.close(this.advertiserCopy); + } + } + + + checkField() + { + if(this.advertiserCopy.login.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'pseudo'" ; + this.hasError = true; + } + else if(this.advertiserCopy.email.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'email'" ; + this.hasError = true; + } + else if(!this.isValidEmail(this.advertiserCopy.email)) { + this.errorMessage = "Email invalide" ; + this.hasError = true; + } + else if((this.changePassword) && (this.newPassword.length === 0)) { + this.errorMessage = "Veuillez remplir le champ 'mot de passe'" ; + this.hasError = true; + } + else if((this.changePassword) && (this.newPassword !== this.confirmNewPassword)) { + this.errorMessage = "Le mot de passe est différent de sa confirmation" ; + this.hasError = true; + } + else { + this.errorMessage = "" ; + this.hasError = false; + } + } + + + isValidEmail(email) + { + let re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); + } + +} diff --git a/userAndAdvertiser/src/app/advertiser/pages-popularity/pages-popularity.component.html b/userAndAdvertiser/src/app/advertiser/pages-popularity/pages-popularity.component.html new file mode 100644 index 0000000..62ae0e1 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/pages-popularity/pages-popularity.component.html @@ -0,0 +1,94 @@ +
+
+ + + + + + + + +
+ +
Filtre
+ +
+ +
+ +

+ +
+ +
+ +
+ + + + +
+ + +
+ + +
+
+ + + + + + + + + début + + +   -   + + + + fin + + +   -   + + + + pas d'affichage + + +   -   + + + + unité du pas d'affichage + + jour + semaine + mois + +
+ + + + + + + {{coupleNameViews.name}}, + + + {{coupleNameViews.name}} + + + + +
diff --git a/userAndAdvertiser/src/app/advertiser/pages-popularity/pages-popularity.component.scss b/userAndAdvertiser/src/app/advertiser/pages-popularity/pages-popularity.component.scss new file mode 100644 index 0000000..00fb9e3 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/pages-popularity/pages-popularity.component.scss @@ -0,0 +1,53 @@ +.myContainer { + font-size: small; + max-width: 100vw; + height: 100vh; + overflow-x: hidden; + overflow-y: scroll; +} + +input { + font-size: small; + width: 140px; +} + +.filtersContainer { + background-color: white; + width: 60%; + margin: 50px 50px 50px 50px; + padding: 20px 20px 20px 20px; +} + +.chartContainer { + background-color: white; + border: solid 1px black; + padding: 10px 10px 10px 10px; + margin: 50px 50px 50px 50px; +} + + +// --------------------------------------------- +// periode + +.periode { + padding: 10px 10px 0px 10px; +} + +.periode .titleContainer { + text-align: right; + border-right: solid 1px #dcdcdc; + font-weight: bold; +} + +.btnToutSelectionner { + font-size: small; +} +.btnToutDeselectionner { + font-size: small; +} + +// ------------------------------------------------------------------------- + +::ng-deep .mat-pseudo-checkbox-checked { + background-color: black !important; +} diff --git a/userAndAdvertiser/src/app/advertiser/pages-popularity/pages-popularity.component.spec.ts b/userAndAdvertiser/src/app/advertiser/pages-popularity/pages-popularity.component.spec.ts new file mode 100644 index 0000000..f9ff236 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/pages-popularity/pages-popularity.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PagesPopularityComponent } from './pages-popularity.component'; + +describe('SubjectsPopularityComponent', () => { + let component: PagesPopularityComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PagesPopularityComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PagesPopularityComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/advertiser/pages-popularity/pages-popularity.component.ts b/userAndAdvertiser/src/app/advertiser/pages-popularity/pages-popularity.component.ts new file mode 100644 index 0000000..298ec8d --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/pages-popularity/pages-popularity.component.ts @@ -0,0 +1,304 @@ +import { Component, OnInit } from '@angular/core'; +import {FormControl} from "@angular/forms"; +import {ChartDataSets} from "chart.js"; +import {Label} from "ng2-charts"; +import { Router} from "@angular/router"; +import {HttpParams} from "@angular/common/http"; +import {ThemeService} from "../../utils/theme/theme.service"; +import {MessageService} from "../../utils/message/message.service"; + + + +interface CoupleNameViews { + name: string, + views: Date[], +} + + + +@Component({ + selector: 'app-subjects-popularity', + templateUrl: './pages-popularity.component.html', + styleUrls: ['./pages-popularity.component.scss'] +}) +export class PagesPopularityComponent implements OnInit +{ + formControl: FormControl = new FormControl(); + allCoupleNameViews: CoupleNameViews[] = []; + + allInterests: string[] = []; + + startDate: Date = null; + endDate: Date = null; + step: number = 1; + stepUnity: string = "jour" ; + + oneDay: number = 24*60*60*1000; + oneWeek: number = 7*24*60*60*1000; + + lineChartData: ChartDataSets[] = []; + lineChartLabels: Label[] = []; + chartOptions: any = { + responsive: true, + scales: { + yAxes: [{ display: true, scaleLabel: { display: true, labelString: "vues" } }], + xAxes: [{ scaleLabel: { display: true, labelString: "temps" } }], + } + }; + + isDisplayable: boolean = false; + + + constructor( private router: Router, + public themeService: ThemeService, + private messageService: MessageService ) {} + + + // ----------------------------------------------------------------------------------------------------- + + + ngOnInit(): void + { + // Sera excuté si on est sur la page 'adsPopularity' + // Remplie l'attribut 'allCoupleNameViews' + if(this.router.url.includes("ads")) + { + let params = new HttpParams(); + params = params.append("isActive", true); + this.messageService + .get("ad/findAll", params ) + .subscribe(ret => this.afterReceivingAds(ret), err => this.afterReceivingAds(err)); + } + + // Sera excuté si on est sur la page 'subjectsPopularity' + // Remplie l'attribut 'allCoupleNameViews' + else if(this.router.url.includes("subjects")) + { + this.messageService + .get("misc/getInterests") + .subscribe( retour => { + + if(retour.status !== "success") { + console.log(retour); + } + else { + this.allInterests = retour.data.map(x => x.interest); + this.allInterests.sort(); + this.messageService + .get("video/findAll") + .subscribe(ret => this.afterReceivingVideos(ret), err => this.afterReceivingVideos(err)); + } + }); + } + } + + + // Callback: Sera excuté si on est sur la page 'adsPopularity' + afterReceivingAds(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + const allAdverts = retour.data; + for(let advert of allAdverts) + { + let couple = {name: advert.title, views: advert.views } + this.allCoupleNameViews.push(couple); + } + + this.formControl = new FormControl(this.allCoupleNameViews); + this.onApplyFilter(); + } + } + + + // Callback: Sera excuté si on est sur la page 'subjectsPopularity' + afterReceivingVideos(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + const allVideos = retour.data; + let myMap: Map = new Map(); + + // parcours des interest de chaque video + for(let video of allVideos) + { + const key = video.interest; + if(!myMap.has(key)) myMap.set(key, video.watchedDates); + else { + let tabDate = myMap.get(key); + for(let date0 of video.watchedDates) tabDate = this.insertInOrder(tabDate, date0); + myMap.set(key, tabDate); + } + } + + // parcours les interest qui n'ont pas p été vu dans les videos + for(let interest of this.allInterests) + { + if(!myMap.has(interest)) myMap.set(interest, []); + } + + // parcours de la map pour remplir 'allCoupleNameViews' + for(const [key, value] of myMap.entries()) + { + let couple = {name: key, views: value } + this.allCoupleNameViews.push(couple); + } + + this.formControl = new FormControl(this.allCoupleNameViews); + this.onApplyFilter(); + } + } + + + // ----------------------------------------------------------------------------------------------------- + + + // Applique le filtre + onApplyFilter(): void + { + // --- initialisation --- + this.lineChartData = []; + this.lineChartLabels = []; + + if(this.step <= 0) this.step = 0; + if((this.endDate === null) || (this.endDate === undefined)) this.endDate = new Date(); + if((this.startDate === null) || (this.startDate === undefined)) this.startDate = new Date(this.endDate.getTime() - this.oneWeek); // date d'il y a une semaine + + const startTime = this.startDate.getTime(); + const endTime = this.endDate.getTime(); + + + // --- remplissage de 'lineChartLabels' --- + let dataWithZeros = []; + let time = startTime; + const intervals = []; + while(time <= endTime) + { + dataWithZeros.push(0); + this.lineChartLabels.push(this.getLabel(new Date(time))); + intervals.push(time); + time = this.addStep(time); + } + intervals.push(time); + + + // --- remplissage de 'lineChartLabels' --- + for(let coupleNameViews of this.formControl.value) + { + let data = dataWithZeros.slice(); + let label = coupleNameViews.name; + let index = 0; + + for(let date0 of coupleNameViews.views) + { + const time0 = (new Date(date0)).getTime(); + + if(time0 > endTime) break; + + if((startTime <= time0) && (time0 <= endTime)) + { + while((index < intervals.length) && (time0 >= intervals[index])) index += 1; + index = index - 1; + data[index] += 1; + } + } + + this.lineChartData.push({"data": data.slice(), "label": label}); + } + this.isDisplayable = true; + } + + + onNewStartDate(event): void { + this.startDate = new Date(event); + } + + + onNewEndDate(event): void { + this.endDate = new Date(event); + } + + + // Renvoie le bon label pour le graph + getLabel(date0: Date): string + { + if((this.stepUnity === 'jour') && (this.step === 1)) + { + return date0.toLocaleDateString(); + } + else { + const time2 = this.addStep((new Date(date0)).getTime()) - this.oneDay; + let date2 = new Date(time2); + return date0.toLocaleDateString() + " à " + date2.toLocaleDateString(); + } + } + + + // Ajoute le bon pas à la date 'new Date(time)' + addStep(time: number): number + { + let newDate; + + if(this.stepUnity === 'jour') { + newDate = new Date(time + this.step*this.oneDay); + } + else if(this.stepUnity === 'semaine') { + newDate = new Date(time + this.step*this.oneWeek); + } + else + { + const oldDate = new Date(time); + + let newMonth = oldDate.getMonth() + this.step; + const newYear = oldDate.getFullYear() + (newMonth / 12); + newMonth = newMonth % 12; + const day = this.startDate.getDate(); + + if((newMonth === 1) && ([29, 30, 31].includes(day))) { // si fevrier et si jour n'existe pas + newDate = new Date(newYear, newMonth, 28); + } + else if((day === 31) && ([3, 5, 9, 10].includes(newMonth))) { // si 31 et mois à 30 jours + newDate = new Date(newYear, newMonth, 30); + } + else { + newDate = new Date(newYear, newMonth, day); + } + } + + const _1h = 60*60*1000; + if(newDate.getHours() === 23) return newDate.getTime() + _1h; + else if(newDate.getHours() === 1) return newDate.getTime() - _1h; + else return newDate.getTime(); + } + + + // Insere la date0 dans le tableau tabDate par ordre croissant + insertInOrder(tabDate: Date[], date0: Date): Date[] + { + let i = 0; + let n = tabDate.length; + let time0 = (new Date(date0)).getTime(); + + while((i (new Date(tabDate[i])).getTime())) i++; + if(i === n) tabDate.push(date0); + else tabDate.splice(i, 0, date0); + + return tabDate; + } + + + onSelectAll(): void + { + this.formControl = new FormControl(this.allCoupleNameViews); + } + + onDeSelectAll(): void + { + this.formControl = new FormControl([]); + } + +} diff --git a/userAndAdvertiser/src/app/advertiser/utils/dragAndDrop/drag-and-drop.directive.spec.ts b/userAndAdvertiser/src/app/advertiser/utils/dragAndDrop/drag-and-drop.directive.spec.ts new file mode 100644 index 0000000..60cf3d6 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/utils/dragAndDrop/drag-and-drop.directive.spec.ts @@ -0,0 +1,8 @@ +import { DragAndDropDirective } from './drag-and-drop.directive'; + +describe('DragAndDropDirective', () => { + it('should create an instance', () => { + const directive = new DragAndDropDirective(); + expect(directive).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/advertiser/utils/dragAndDrop/drag-and-drop.directive.ts b/userAndAdvertiser/src/app/advertiser/utils/dragAndDrop/drag-and-drop.directive.ts new file mode 100644 index 0000000..b3d1162 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/utils/dragAndDrop/drag-and-drop.directive.ts @@ -0,0 +1,36 @@ +import {Directive, EventEmitter, HostBinding, HostListener, Output} from '@angular/core'; + +@Directive({ + selector: '[appDragAndDrop]' +}) +export class DragAndDropDirective +{ + @HostBinding('class.fileover') fileOver: boolean; + @Output() fileDropped = new EventEmitter(); + + // Dragover listener + @HostListener('dragover', ['$event']) onDragOver(evt) { + evt.preventDefault(); + evt.stopPropagation(); + this.fileOver = true; + } + + // Dragleave listener + @HostListener('dragleave', ['$event']) public onDragLeave(evt) { + evt.preventDefault(); + evt.stopPropagation(); + this.fileOver = false; + } + + // Drop listener + @HostListener('drop', ['$event']) public ondrop(evt) { + evt.preventDefault(); + evt.stopPropagation(); + this.fileOver = false; + let files = evt.dataTransfer.files; + if (files.length > 0) { + this.fileDropped.emit(files); + } + } + +} diff --git a/userAndAdvertiser/src/app/advertiser/utils/navbar-advertiser/navbar-advertiser.component.html b/userAndAdvertiser/src/app/advertiser/utils/navbar-advertiser/navbar-advertiser.component.html new file mode 100644 index 0000000..da5e898 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/utils/navbar-advertiser/navbar-advertiser.component.html @@ -0,0 +1,41 @@ + diff --git a/userAndAdvertiser/src/app/advertiser/utils/navbar-advertiser/navbar-advertiser.component.scss b/userAndAdvertiser/src/app/advertiser/utils/navbar-advertiser/navbar-advertiser.component.scss new file mode 100644 index 0000000..285d629 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/utils/navbar-advertiser/navbar-advertiser.component.scss @@ -0,0 +1,80 @@ +.navbar { + background-color: black; + height: 60px; + font-size: medium; + color: white; +} + + +.navbar-expand-lg { + border-bottom: solid; + border-color: white; + border-bottom-width: 2px; +} + + +// PolyNotFound +.navbar-brand { + font-family: cursive; + font-weight: bold; + font-size: x-large; + margin-left: 15px; + color: white; +} + + +.monLi { + margin: 0px 10px 0px 10px; +} + + +.nav-link { + color: white; +} +.nav-link:hover { + color: grey; +} +.myActiveLink { + text-decoration: underline; +} + + +.btnDeconnexion { + font-size: medium; + margin: 0px 10px 0px 10px +} +.btnDeconnexion:hover { + color: grey; +} + + +img { + border: solid 2px white; + border-radius: 50px; + margin: 0px 10px 0px 15px; + width: 40px; + height: 40px; +} +img:hover { + cursor: pointer; +} + + +// -------------------------------------------------------------------- + + +::ng-deep .mat-slide-toggle-thumb { + background-color: #c8c8c8; +} + +::ng-deep .mat-slide-toggle-bar { + background-color: #ffffff; +} + +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-thumb { + background-color: #ffffff; +} + +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-bar { + background-color: #646464; +} diff --git a/userAndAdvertiser/src/app/advertiser/utils/navbar-advertiser/navbar-advertiser.component.spec.ts b/userAndAdvertiser/src/app/advertiser/utils/navbar-advertiser/navbar-advertiser.component.spec.ts new file mode 100644 index 0000000..fb00a09 --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/utils/navbar-advertiser/navbar-advertiser.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NavbarAdvertiserComponent } from './navbar-advertiser.component'; + +describe('NavbarAdvertiserComponent', () => { + let component: NavbarAdvertiserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ NavbarAdvertiserComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NavbarAdvertiserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/advertiser/utils/navbar-advertiser/navbar-advertiser.component.ts b/userAndAdvertiser/src/app/advertiser/utils/navbar-advertiser/navbar-advertiser.component.ts new file mode 100644 index 0000000..c65b5fe --- /dev/null +++ b/userAndAdvertiser/src/app/advertiser/utils/navbar-advertiser/navbar-advertiser.component.ts @@ -0,0 +1,41 @@ +import { Component } from '@angular/core'; +import {Router} from "@angular/router"; +import {ProfilService} from "../../../utils/profil/profil.service"; +import {MessageService} from "../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-navbar-advertiser', + templateUrl: './navbar-advertiser.component.html', + styleUrls: ['./navbar-advertiser.component.scss'] +}) +export class NavbarAdvertiserComponent +{ + routes: string[] = [ + "/advertiser", // 0 + "/advertiser/adList", // 1 + "/advertiser/adsPopularity", // 2 + "/advertiser/subjectsPopularity", // 3 + "/advertiser/myProfil" // 4 + ]; + + url = this.router.url; + + constructor( private router: Router, + public profilService: ProfilService, + private messageService: MessageService ) { } + + onDeconnexion(): void + { + this.messageService + .delete('user/logout') + .subscribe(retour => this.onDeconnexionCallback(retour), err => this.onDeconnexionCallback(err)); + } + + onDeconnexionCallback(retour: any): void + { + if(retour.status !== "success") console.log(retour); + } + +} diff --git a/userAndAdvertiser/src/app/app-routing.module.ts b/userAndAdvertiser/src/app/app-routing.module.ts new file mode 100644 index 0000000..d1888ec --- /dev/null +++ b/userAndAdvertiser/src/app/app-routing.module.ts @@ -0,0 +1,41 @@ +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import {PageLoginComponent} from "./beforeConnexion/login/page-login/page-login.component"; +import {PageRegisterComponent} from "./beforeConnexion/register/page-register/page-register.component"; +import {PageSearchComponent} from "./user/search/page-search/page-search.component"; +import {PageMyPlaylistsComponent} from "./user/myPlaylists/page-my-playlists/page-my-playlists.component"; +import {PageProfilUserComponent} from "./user/myProfil/page-profil-user/page-profil-user.component"; +import {PageWatchingVideoComponent} from "./user/watching/page-watching-video/page-watching-video.component"; +import {PageHistoryUserComponent} from "./user/history/page-history-user/page-history-user.component"; +import {PageAdListAdvertiserComponent} from "./advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component"; +import {PagesPopularityComponent} from "./advertiser/pages-popularity/pages-popularity.component"; +import {PageProfilAdvertiserComponent} from "./advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component"; + + +const routes: Routes = [ + // Before connexion + { path: '', component: PageLoginComponent }, + { path: 'login', component: PageLoginComponent }, + { path: 'register', component: PageRegisterComponent }, + + // User + { path: 'user', component: PageSearchComponent }, + { path: 'user/search', component: PageSearchComponent }, + { path: 'user/myPlaylists', component: PageMyPlaylistsComponent }, + { path: 'user/history', component: PageHistoryUserComponent }, + { path: 'user/myProfil', component: PageProfilUserComponent }, + { path: 'user/watching', component: PageWatchingVideoComponent }, + + // Advertiser + { path: 'advertiser', component: PageAdListAdvertiserComponent }, + { path: 'advertiser/adList', component: PageAdListAdvertiserComponent }, + { path: 'advertiser/myProfil', component: PageProfilAdvertiserComponent }, + { path: 'advertiser/adsPopularity', component: PagesPopularityComponent }, + { path: 'advertiser/subjectsPopularity', component: PagesPopularityComponent }, +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/userAndAdvertiser/src/app/app.component.html b/userAndAdvertiser/src/app/app.component.html new file mode 100644 index 0000000..d5d92f3 --- /dev/null +++ b/userAndAdvertiser/src/app/app.component.html @@ -0,0 +1,2 @@ + + diff --git a/userAndAdvertiser/src/app/app.component.scss b/userAndAdvertiser/src/app/app.component.scss new file mode 100644 index 0000000..22a5665 --- /dev/null +++ b/userAndAdvertiser/src/app/app.component.scss @@ -0,0 +1,24 @@ +::ng-deep snack-bar-container.custom-class { + //background: yellow; +} +::ng-deep .custom-class .mat-simple-snackbar { + //color: green; + justify-content: center; +} + + +// aura +::ng-deep .mat-checkbox-ripple .mat-ripple-element { + background-color: grey !important; +} + +// contenu coche +::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background { + background-color: black !important; +} + +// indeterminate +::ng-deep .mat-checkbox .mat-checkbox-frame { + border: solid 1px black !important; + background-color: white !important; +} diff --git a/userAndAdvertiser/src/app/app.component.spec.ts b/userAndAdvertiser/src/app/app.component.spec.ts new file mode 100644 index 0000000..ab837bf --- /dev/null +++ b/userAndAdvertiser/src/app/app.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + RouterTestingModule + ], + declarations: [ + AppComponent + ], + }).compileComponents(); + }); + + it('should create the app', () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app).toBeTruthy(); + }); + + it(`should have as title 'userAndAdvertiser'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.componentInstance; + expect(app.title).toEqual('userAndAdvertiser'); + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.nativeElement as HTMLElement; + expect(compiled.querySelector('.content span')?.textContent).toContain('userAndAdvertiser app is running!'); + }); +}); diff --git a/userAndAdvertiser/src/app/app.component.ts b/userAndAdvertiser/src/app/app.component.ts new file mode 100644 index 0000000..945afdf --- /dev/null +++ b/userAndAdvertiser/src/app/app.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'] +}) +export class AppComponent { + title = 'userAndAdvertiser'; +} diff --git a/userAndAdvertiser/src/app/app.module.ts b/userAndAdvertiser/src/app/app.module.ts new file mode 100644 index 0000000..dc1e214 --- /dev/null +++ b/userAndAdvertiser/src/app/app.module.ts @@ -0,0 +1,131 @@ +import { NgModule } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; + +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {MatSlideToggleModule} from "@angular/material/slide-toggle"; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {HttpClientModule} from "@angular/common/http"; +import {MatIconModule} from "@angular/material/icon"; +import {MatDialogModule} from "@angular/material/dialog"; +import {MatButtonModule} from "@angular/material/button"; +import {MatInputModule} from "@angular/material/input"; +import {MatCheckboxModule} from "@angular/material/checkbox"; +import {MatFormFieldModule} from "@angular/material/form-field"; +import {MatDividerModule} from "@angular/material/divider"; +import {MatSnackBarModule} from "@angular/material/snack-bar"; +import {MatGridListModule} from "@angular/material/grid-list"; +import {MatTableModule} from "@angular/material/table"; +import {MatSortModule} from "@angular/material/sort"; +import {MatChipsModule} from "@angular/material/chips"; +import {MatSelectModule} from "@angular/material/select"; +import {MatAutocompleteModule} from "@angular/material/autocomplete"; +import {MatRadioModule} from "@angular/material/radio"; +import {MatPaginatorModule} from "@angular/material/paginator"; +import {MatDatepickerModule} from "@angular/material/datepicker"; +import { ChartsModule } from 'ng2-charts'; +import {DragAndDropComponent} from "./advertiser/adList/drag-and-drop/drag-and-drop.component"; +import {InputInterestsAdComponent} from "./advertiser/adList/input-interests-ad/input-interests-ad.component"; +import {PageAdListAdvertiserComponent} from "./advertiser/adList/page-ad-list-advertiser/page-ad-list-advertiser.component"; +import {PopupAddOrUpdateAdComponent} from "./advertiser/adList/popup-add-or-update-ad/popup-add-or-update-ad.component"; +import {PopupDeleteAdAdvertiserComponent} from "./advertiser/adList/popup-delete-ad-advertiser/popup-delete-ad-advertiser.component"; +import {PopupVisualizeAdAdvertiserComponent} from "./advertiser/adList/popup-visualize-ad-advertiser/popup-visualize-ad-advertiser.component"; +import {PopupVisualizeImagesAdvertiserComponent} from "./advertiser/adList/popup-visualize-images-advertiser/popup-visualize-images-advertiser.component"; +import {NavbarAdvertiserComponent} from "./advertiser/utils/navbar-advertiser/navbar-advertiser.component"; +import {DragAndDropDirective} from "./advertiser/utils/dragAndDrop/drag-and-drop.directive"; +import {PageProfilAdvertiserComponent} from "./advertiser/myProfil/page-profil-advertiser/page-profil-advertiser.component"; +import {PopupUpdateAdvertiserComponent} from "./advertiser/myProfil/popup-update-advertiser/popup-update-advertiser.component"; +import {PagesPopularityComponent} from "./advertiser/pages-popularity/pages-popularity.component"; +import {NavbarUserComponent} from "./user/utils/components/navbar-user/navbar-user.component"; +import {PageHistoryUserComponent} from "./user/history/page-history-user/page-history-user.component"; +import {PageMyPlaylistsComponent} from "./user/myPlaylists/page-my-playlists/page-my-playlists.component"; +import {PlaylistListComponent} from "./user/myPlaylists/playlist-list/playlist-list.component"; +import {PopupCreateOrUpdatePlaylistComponent} from "./user/myPlaylists/popup-create-or-update-playlist/popup-create-or-update-playlist.component"; +import {PopupDeletePlaylistComponent} from "./user/myPlaylists/popup-delete-playlist/popup-delete-playlist.component"; +import {VideoListComponent} from "./user/myPlaylists/video-list/video-list.component"; +import {InputInterestsProfilComponent} from "./user/myProfil/input-interests-profil/input-interests-profil.component"; +import {PageProfilUserComponent} from "./user/myProfil/page-profil-user/page-profil-user.component"; +import {PopupUpdateUserComponent} from "./user/myProfil/popup-update-user/popup-update-user.component"; +import {PageSearchComponent} from "./user/search/page-search/page-search.component"; +import {VideoGridComponent} from "./user/search/video-grid/video-grid.component"; +import {PageWatchingVideoComponent} from "./user/watching/page-watching-video/page-watching-video.component"; +import {AdvertComponent} from "./user/utils/components/advert/advert.component"; +import {PopupAddVideoToPlaylistsComponent} from "./user/utils/components/popup-add-video-to-playlists/popup-add-video-to-playlists.component"; +import {PageLoginComponent} from "./beforeConnexion/login/page-login/page-login.component"; +import {PopupForgottenPasswordComponent} from "./beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component"; +import {InputInterestsRegisterComponent} from "./beforeConnexion/register/input-interests-register/input-interests-register.component"; +import {PageRegisterComponent} from "./beforeConnexion/register/page-register/page-register.component"; +import {PopupConfirmationComponent} from "./beforeConnexion/register/popup-confirmation/popup-confirmation.component"; +import {NavbarBeforeConnexionComponent} from "./beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component"; +import {MatStepperModule} from "@angular/material/stepper"; + +@NgModule({ + declarations: [ + AppComponent, + DragAndDropDirective, + NavbarBeforeConnexionComponent, + PageLoginComponent, + PopupForgottenPasswordComponent, + InputInterestsRegisterComponent, + PageRegisterComponent, + PopupConfirmationComponent, + NavbarAdvertiserComponent, + DragAndDropComponent, + InputInterestsAdComponent, + PageAdListAdvertiserComponent, + PopupAddOrUpdateAdComponent, + PopupDeleteAdAdvertiserComponent, + PopupVisualizeAdAdvertiserComponent, + PopupVisualizeImagesAdvertiserComponent, + PageProfilAdvertiserComponent, + PopupUpdateAdvertiserComponent, + PagesPopularityComponent, + NavbarUserComponent, + PageHistoryUserComponent, + PageMyPlaylistsComponent, + PlaylistListComponent, + PopupCreateOrUpdatePlaylistComponent, + PopupDeletePlaylistComponent, + VideoListComponent, + InputInterestsProfilComponent, + PageProfilUserComponent, + PopupUpdateUserComponent, + PageSearchComponent, + VideoGridComponent, + PageWatchingVideoComponent, + AdvertComponent, + PopupAddVideoToPlaylistsComponent + ], + imports: [ + BrowserModule, + AppRoutingModule, + BrowserAnimationsModule, + MatSlideToggleModule, + FormsModule, + HttpClientModule, + MatDialogModule, + MatButtonModule, + MatIconModule, + MatInputModule, + MatDividerModule, + MatCheckboxModule, + MatFormFieldModule, + MatSnackBarModule, + MatGridListModule, + MatTableModule, + MatSortModule, + MatChipsModule, + ReactiveFormsModule, + MatAutocompleteModule, + MatSelectModule, + MatRadioModule, + MatPaginatorModule, + MatDatepickerModule, + ChartsModule, + MatStepperModule + ], + providers: [], + bootstrap: [AppComponent] +}) +export class AppModule { } diff --git a/userAndAdvertiser/src/app/beforeConnexion/login/page-login/page-login.component.html b/userAndAdvertiser/src/app/beforeConnexion/login/page-login/page-login.component.html new file mode 100644 index 0000000..675270e --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/login/page-login/page-login.component.html @@ -0,0 +1,36 @@ +
+
+ + + +
+
+ + +
+

StreamNotFound

+ User Icon +
+ + +
+ + + +
+ {{errorMessage}} +
+ +
+ + + + +
+
+ + +
+
diff --git a/userAndAdvertiser/src/app/beforeConnexion/login/page-login/page-login.component.scss b/userAndAdvertiser/src/app/beforeConnexion/login/page-login/page-login.component.scss new file mode 100644 index 0000000..8924202 --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/login/page-login/page-login.component.scss @@ -0,0 +1,271 @@ +html { + background-color: #56baed; +} + +body { + font-family: "Poppins", sans-serif; + height: 100vh; +} + +a { + color: #5E89FF; + display:inline-block; + text-decoration: none; + font-weight: 400; +} + +h2 { + text-align: center; + font-size: 16px; + font-weight: 600; + text-transform: uppercase; + display:inline-block; + margin: 40px 8px 10px 8px; + color: #cccccc; +} + + + +/* STRUCTURE */ + +.wrapper { + display: flex; + align-items: center; + flex-direction: column; + justify-content: center; + width: 100%; + min-height: 80%; + padding: 20px; +} + +#formContent { + -webkit-border-radius: 10px 10px 10px 10px; + border-radius: 10px 10px 10px 10px; + background: #fff; + padding: 30px; + width: 90%; + max-width: 450px; + position: relative; + padding: 0px; + -webkit-box-shadow: 0 30px 60px 0 rgba(0,0,0,0.3); + box-shadow: 0 30px 60px 0 rgba(0,0,0,0.3); + text-align: center; +} + +#formFooter { + background-color: #f6f6f6; + border-top: 1px solid #dce8f1; + padding: 25px; + text-align: center; + -webkit-border-radius: 0 0 10px 10px; + border-radius: 0 0 10px 10px; +} + + + +/* TABS */ + +h2.inactive { + color: #cccccc; +} + +h2.active { + color: #0d0d0d; + border-bottom: 2px solid #5fbae9; +} + + + +/* FORM TYPOGRAPHY*/ + +input[type=button], input[type=submit], input[type=reset] { + background-color: #5E89FF; + border: none; + color: white; + padding: 15px 80px; + text-align: center; + text-decoration: none; + display: inline-block; + text-transform: uppercase; + font-size: 13px; + -webkit-box-shadow: 0 10px 30px 0 rgba(95,186,233,0.4); + box-shadow: 0 10px 30px 0 rgba(95,186,233,0.4); + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; + margin: 5px 20px 40px 20px; + -webkit-transition: all 0.3s ease-in-out; + -moz-transition: all 0.3s ease-in-out; + -ms-transition: all 0.3s ease-in-out; + -o-transition: all 0.3s ease-in-out; + transition: all 0.3s ease-in-out; +} + +input[type=button]:hover, input[type=submit]:hover, input[type=reset]:hover { + background-color: #39ace7; +} + +input[type=button]:active, input[type=submit]:active, input[type=reset]:active { + -moz-transform: scale(0.95); + -webkit-transform: scale(0.95); + -o-transform: scale(0.95); + -ms-transform: scale(0.95); + transform: scale(0.95); +} + +input[type=text], input[type=password] { + background-color: #f6f6f6; + border: none; + color: #0d0d0d; + padding: 15px 32px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 16px; + margin: 5px; + width: 85%; + border: 2px solid #f6f6f6; + -webkit-transition: all 0.5s ease-in-out; + -moz-transition: all 0.5s ease-in-out; + -ms-transition: all 0.5s ease-in-out; + -o-transition: all 0.5s ease-in-out; + transition: all 0.5s ease-in-out; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} + + + +input[type=text]:focus, input[type=password]:focus { + background-color: #fff; + border-bottom: 2px solid #5fbae9; +} + +input[type=text]::placeholder, input[type=password]::placeholder { + color: #cccccc; +} + +.bg{ + margin: 0; + padding: 0; + height: 100vh; + width: 100vw; + overflow-y: hidden; + overflow-x: hidden; +} + +/* ANIMATIONS */ + +/* Simple CSS3 Fade-in-down Animation */ +.fadeInDown { + -webkit-animation-name: fadeInDown; + animation-name: fadeInDown; + -webkit-animation-duration: 1s; + animation-duration: 1s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; +} + +@-webkit-keyframes fadeInDown { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +@keyframes fadeInDown { + 0% { + opacity: 0; + -webkit-transform: translate3d(0, -100%, 0); + transform: translate3d(0, -100%, 0); + } + 100% { + opacity: 1; + -webkit-transform: none; + transform: none; + } +} + +/* Simple CSS3 Fade-in Animation */ +@-webkit-keyframes fadeIn { from { opacity:0; } to { opacity:1; } } +@-moz-keyframes fadeIn { from { opacity:0; } to { opacity:1; } } +@keyframes fadeIn { from { opacity:0; } to { opacity:1; } } + +.fadeIn { + opacity:0; + -webkit-animation:fadeIn ease-in 1; + -moz-animation:fadeIn ease-in 1; + animation:fadeIn ease-in 1; + + -webkit-animation-fill-mode:forwards; + -moz-animation-fill-mode:forwards; + animation-fill-mode:forwards; + + -webkit-animation-duration:1s; + -moz-animation-duration:1s; + animation-duration:1s; +} + +.fadeIn.first { + -webkit-animation-delay: 0.4s; + -moz-animation-delay: 0.4s; + animation-delay: 0.4s; +} + +.fadeIn.second { + -webkit-animation-delay: 0.6s; + -moz-animation-delay: 0.6s; + animation-delay: 0.6s; +} + +.fadeIn.third { + -webkit-animation-delay: 0.8s; + -moz-animation-delay: 0.8s; + animation-delay: 0.8s; +} + +.fadeIn.fourth { + -webkit-animation-delay: 1s; + -moz-animation-delay: 1s; + animation-delay: 1s; +} + +/* Simple CSS3 Fade-in Animation */ +.underlineHover:after { + display: block; + left: 0; + bottom: -10px; + width: 0; + height: 2px; + //background-color: #5E89FF; + background-color: #5E89FF; + content: ""; + transition: width 0.2s; +} + +.underlineHover:hover { + color: #0d0d0d; +} + +.underlineHover:hover:after{ + width: 100%; +} + +h1{ + color: black; +} + +/* OTHERS */ + +*:focus { + outline: none; +} + +#icon { + width:30%; +} diff --git a/userAndAdvertiser/src/app/beforeConnexion/login/page-login/page-login.component.spec.ts b/userAndAdvertiser/src/app/beforeConnexion/login/page-login/page-login.component.spec.ts new file mode 100644 index 0000000..a4ee677 --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/login/page-login/page-login.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageLoginComponent } from './page-login.component'; + +describe('PageLoginComponent', () => { + let component: PageLoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageLoginComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageLoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/beforeConnexion/login/page-login/page-login.component.ts b/userAndAdvertiser/src/app/beforeConnexion/login/page-login/page-login.component.ts new file mode 100644 index 0000000..555e496 --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/login/page-login/page-login.component.ts @@ -0,0 +1,101 @@ +import {Component, OnInit} from '@angular/core'; +import {Router} from "@angular/router"; +import {MatDialog} from "@angular/material/dialog"; +import {PopupForgottenPasswordComponent} from "../popup-forgotten-password/popup-forgotten-password.component"; +import {MatSnackBar} from "@angular/material/snack-bar"; +import {ProfilService} from "../../../utils/profil/profil.service"; +import {MessageService} from "../../../utils/message/message.service"; +import {ThemeService} from "../../../utils/theme/theme.service"; + + + +@Component({ + selector: 'app-page-login', + templateUrl: './page-login.component.html', + styleUrls: ['./page-login.component.scss'] +}) +export class PageLoginComponent implements OnInit +{ + email: string = "" ; + password: string = "" ; + hasError: boolean = false; + errorMessage: string = ""; + + + constructor( private messageService: MessageService, + private router: Router, + public themeService: ThemeService, + public dialog: MatDialog, + private snackBar: MatSnackBar, + private profilService: ProfilService) { } + + + ngOnInit(): void {} + + + onSeConnecter(): void + { + this.checkError(); + + if(!this.hasError) + { + let data = { + email: this.email, + hashPass: this.password + }; + this.messageService + .post('user/auth', data) + .subscribe( retour => this.onSeConnecterCallback(retour), err => this.onSeConnecterCallback(err)); + } + } + + + onSeConnecterCallback(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + this.errorMessage = retour.error.reason; + this.hasError = true; + } + else { + this.profilService.setId(retour.data.id); + this.profilService.setProfileImageUrl(retour.data.profileImageUrl); + if(retour.data.role.name === "user") this.router.navigateByUrl( '/user/search'); + else if(retour.data.role.name === "advertiser") this.router.navigateByUrl( '/advertiser/adList'); + else if(retour.data.role.name === "admin" || retour.data.role.name === "superAdmin") this.router.navigateByUrl( '/admin/userList'); + } + } + + + onForgottenPassword(): void + { + this.dialog + .open(PopupForgottenPasswordComponent, {width: '30%'}) + .afterClosed() + .subscribe(result => { + if((result !== null) && (result !== undefined)) + { + const config = { duration: 5000, panelClass: "custom-class" }; + this.snackBar.open( "Un mail de réinitialisation de mot de passe vous a été envoyé.", "", config); + } + }); + } + + + checkError(): void + { + if(this.email === "") { + this.errorMessage = "Veuillez remplir le champ email" ; + this.hasError = true; + } + else if(this.password === "") { + this.errorMessage = "Veuillez remplir le champ mot de passe" ; + this.hasError = true; + } + else { + this.errorMessage = "" ; + this.hasError = false; + } + } + +} diff --git a/userAndAdvertiser/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.html b/userAndAdvertiser/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.html new file mode 100644 index 0000000..c34b58e --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.html @@ -0,0 +1,24 @@ +

Récupération du mot de passe

+ +
+ + +
+ + Email + + +
+ + +
+ {{errorMessage}} +
+ + + + + + + + diff --git a/userAndAdvertiser/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.scss b/userAndAdvertiser/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.scss new file mode 100644 index 0000000..fa75013 --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.scss @@ -0,0 +1,12 @@ +h4 { + text-align: center; +} + +.myDiv { + text-align: center; + font-size: small; +} + +.myError { + text-align: center; +} diff --git a/userAndAdvertiser/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.spec.ts b/userAndAdvertiser/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.spec.ts new file mode 100644 index 0000000..ebf101c --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupForgottenPasswordComponent } from './popup-forgotten-password.component'; + +describe('PopupForgottenPasswordComponent', () => { + let component: PopupForgottenPasswordComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupForgottenPasswordComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupForgottenPasswordComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.ts b/userAndAdvertiser/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.ts new file mode 100644 index 0000000..1ff70ce --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/login/popup-forgotten-password/popup-forgotten-password.component.ts @@ -0,0 +1,47 @@ +import { Component } from '@angular/core'; +import {MatDialogRef} from "@angular/material/dialog"; + + + +@Component({ + selector: 'app-popup-forgotten-password', + templateUrl: './popup-forgotten-password.component.html', + styleUrls: ['./popup-forgotten-password.component.scss'] +}) +export class PopupForgottenPasswordComponent +{ + email: string; + hasError: boolean = false; + errorMessage: string = ""; + + + constructor(public dialogRef: MatDialogRef) {} + + + // Click sur valider + onValidate() + { + if(this.email.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'email'." ; + this.hasError = true; + } + else if(!this.isValidEmail(this.email)) { + this.errorMessage = "Email invalide." ; + this.hasError = true; + } + else { + this.errorMessage = "" ; + this.hasError = false; + this.dialogRef.close(true); + } + } + + + // Indique si email a bien le format d'un email + isValidEmail(email): boolean + { + let re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); + } + +} diff --git a/userAndAdvertiser/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.html b/userAndAdvertiser/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.html new file mode 100644 index 0000000..2a7c484 --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.html @@ -0,0 +1,43 @@ + + + + + Centres d'intérêt + + + + + + + {{interest}} + + + + + + + + + + + + {{interest}} + + + + + + diff --git a/userAndAdvertiser/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.scss b/userAndAdvertiser/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/userAndAdvertiser/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.spec.ts b/userAndAdvertiser/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.spec.ts new file mode 100644 index 0000000..9917b1a --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InputInterestsRegisterComponent } from './input-interests-register.component'; + +describe('InputInterestsRegisterComponent', () => { + let component: InputInterestsRegisterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ InputInterestsRegisterComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InputInterestsRegisterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.ts b/userAndAdvertiser/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.ts new file mode 100644 index 0000000..8dba84e --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/register/input-interests-register/input-interests-register.component.ts @@ -0,0 +1,121 @@ +import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; +import {COMMA, ENTER} from "@angular/cdk/keycodes"; +import {FormControl} from "@angular/forms"; +import {Observable} from "rxjs"; +import {map, startWith} from "rxjs/operators"; +import {MatChipInputEvent} from "@angular/material/chips"; +import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete"; +import {MessageService} from "../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-input-interests-register', + templateUrl: './input-interests-register.component.html', + styleUrls: ['./input-interests-register.component.scss'] +}) +export class InputInterestsRegisterComponent implements OnInit +{ + selectable = true; + removable = true; + separatorKeysCodes: number[] = [ENTER, COMMA]; + formControl = new FormControl(); + filteredInterests: Observable; + @Input() myInterests: string[] = []; + allInterests: string[] = []; + @Output() eventEmitter = new EventEmitter(); + @ViewChild('tagInput') tagInput: ElementRef; + interestsNotSelected: string[] = []; + + + constructor( private messageService: MessageService ) {} + + + ngOnInit(): void + { + this.filteredInterests = this.formControl.valueChanges.pipe( + startWith(null), + map((fruit: string | null) => fruit ? this._filter(fruit) : this.interestsNotSelected.slice())); + + this.messageService + .get("misc/getInterests") + .subscribe( retour => { + + if(retour.status !== "success") { + console.log(retour); + } + else { + this.allInterests = []; + for(let elt of retour.data) + { + this.allInterests.push(elt.interest); + this.interestsNotSelected.push(elt.interest); + } + } + }); + } + + + add(event: MatChipInputEvent): void + { + const value = (event.value || '').trim(); + const index = this.interestsNotSelected.indexOf(value); + if (value && (index !== -1) && (!this.myInterests.includes(value))) + { + this.myInterests.push(value); + event.chipInput!.clear(); + this.formControl.setValue(null); + this.eventEmitter.emit(this.myInterests); + this.interestsNotSelected.splice(index, 1); + } + } + + + remove(interest: string): void + { + // supprimer 'interest' de 'myInterest' + const index = this.myInterests.indexOf(interest); + if (index >= 0) this.myInterests.splice(index, 1); + this.eventEmitter.emit(this.myInterests); + + // remmettre 'interest' dans 'interestsNotSelected' + if(!this.interestsNotSelected.includes(interest)) + { + const indexOfAutres = this.interestsNotSelected.indexOf("Autres"); + if(indexOfAutres !== -1) + { + this.interestsNotSelected.splice(indexOfAutres, 1); + if(interest !== "Autres") this.interestsNotSelected.push(interest); + this.interestsNotSelected.sort(); + this.interestsNotSelected.push("Autres"); + } + else { + this.interestsNotSelected.push(interest); + if(interest !== "Autres") this.interestsNotSelected.sort(); + } + } + } + + + selected(event: MatAutocompleteSelectedEvent): void + { + const value = event.option.viewValue; + if(!this.myInterests.includes(value)) + { + this.myInterests.push(value); + const index = this.interestsNotSelected.indexOf(value); + this.interestsNotSelected.splice(index, 1); + } + this.tagInput.nativeElement.value = ''; + this.formControl.setValue(null); + this.eventEmitter.emit(this.myInterests); + } + + + private _filter(value: string): string[] + { + const filterValue = value.toLowerCase(); + return this.interestsNotSelected.filter(fruit => fruit.toLowerCase().includes(filterValue)); + } + +} diff --git a/userAndAdvertiser/src/app/beforeConnexion/register/page-register/page-register.component.html b/userAndAdvertiser/src/app/beforeConnexion/register/page-register/page-register.component.html new file mode 100644 index 0000000..fb6e3ac --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/register/page-register/page-register.component.html @@ -0,0 +1,162 @@ +
+
+ + + + + + + + + +
+ + + Utilisateur standard    + Annonceur + + + +
+ +
+
+
+ + + + +
+ + + + +
+ {{errorMessage}} +
+ + +
+ + +
+
+
+ +
+ +
+
+ + + + + + + +
+
+ + +
+

Compte

+ + + + Pseudo + + +
+ + + + Mot de passe + + +
+ + + + Confirmation mot de passe + + +
+ + +
+

Informations personelles

+ + + + Email + + +
+ + + + Homme     + Femme + +

+ + + + Date de naissance + + + + + +
+ +
+
+ + + + + + + + + + + Entreprise + + +
+ + + + Pseudo + + +
+ + + + Email + + +
+ + + + Mot de passe + + +
+ + + + Confirmation mot de passe + + + +
diff --git a/userAndAdvertiser/src/app/beforeConnexion/register/page-register/page-register.component.scss b/userAndAdvertiser/src/app/beforeConnexion/register/page-register/page-register.component.scss new file mode 100644 index 0000000..5f0dc53 --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/register/page-register/page-register.component.scss @@ -0,0 +1,47 @@ +.myContainer { + width: 100vw; + height: 100vh; +} + + +mat-stepper { + width: 60%; + margin: 10vh auto; + border: solid 1px black; + border-radius: 20px; +} + + +.leftCol { + border-right: solid 1px #dcdcdc; +} + + +.myRow { + margin: 15px 0px 15px 0px; +} +.myLabel { + text-align: right; + padding: 0px 5px 0px 0px; + margin: 0px; + font-weight: bold; +} +.myValue { + text-align: left; + padding: 0px 0px 0px 5px; + margin: 0px; +} + + +// ------------------------------------------------------------------------- + + +::ng-deep .mat-radio-inner-circle { + color: black !important; + background-color: black !important; +} + +::ng-deep .mat-radio-outer-circle{ + color: black !important; + border: solid 1px gray !important; +} diff --git a/userAndAdvertiser/src/app/beforeConnexion/register/page-register/page-register.component.spec.ts b/userAndAdvertiser/src/app/beforeConnexion/register/page-register/page-register.component.spec.ts new file mode 100644 index 0000000..5cff194 --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/register/page-register/page-register.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageRegisterComponent } from './page-register.component'; + +describe('PageRegisterComponent', () => { + let component: PageRegisterComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageRegisterComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageRegisterComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/beforeConnexion/register/page-register/page-register.component.ts b/userAndAdvertiser/src/app/beforeConnexion/register/page-register/page-register.component.ts new file mode 100644 index 0000000..788c8be --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/register/page-register/page-register.component.ts @@ -0,0 +1,134 @@ +import { Component } from '@angular/core'; +import {PopupConfirmationComponent} from "../popup-confirmation/popup-confirmation.component"; +import {MessageService} from "../../../utils/message/message.service"; +import {Router} from "@angular/router"; +import {MatDialog} from "@angular/material/dialog"; +import {ThemeService} from "../../../utils/theme/theme.service"; + + + +@Component({ + selector: 'app-page-register', + templateUrl: './page-register.component.html', + styleUrls: ['./page-register.component.scss'] +}) +export class PageRegisterComponent +{ + password: string = ""; + confirmPassword: string = ""; + hasError: boolean = false; + errorMessage: string = ""; + user = { + _id: "", + login: "", + hashPass: "", + email: "", + role: { + name: "user", + permission: 0, + isAccepted: false, + }, + profileImageUrl: "", + dateOfBirth: null, + gender: "man", + interests: [], + company: "", + isActive: true, + createdAt: new Date(), + updatedAt: new Date(), + lastConnexion: null + }; + + + constructor( private messageService: MessageService, + private router: Router, + public dialog: MatDialog, + public themeService: ThemeService ) { } + + + // Envoie de l'utilisateur au backend + onEnregistrer(): void + { + this.checkField(); + if(!this.hasError) + { + let data: any = Object.assign({}, this.user); + if(this.user.role.name === "user") data.role = "user" ; + else data.role = "advertiser"; + data.hashPass = this.password; + this.messageService + .post('user/create', data) + .subscribe(retour => this.onEnregistrerCallback(retour), err => this.onEnregistrerCallback(err)); + } + } + + + // Gestion de la réponse du backend + onEnregistrerCallback(retour): void + { + if(retour.status !== "success") { + console.log(retour); + } + else + { + const config = { + width: '25%', + data: {roleName: this.user.role.name} + }; + this.dialog + .open(PopupConfirmationComponent, config) + .afterClosed() + .subscribe(result => this.router.navigateByUrl( '/login' )); + } + } + + + // Check les champs saisies par l'utilisateur + checkField(): void + { + if((this.user.role.name === 'advertiser') && (this.user.company.length === 0)) { + this.errorMessage = "Veuillez remplir le champ 'entreprise'."; + this.hasError = true; + } + else if(this.user.login.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'pseudo'."; + this.hasError = true; + } + else if(this.user.email.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'email'."; + this.hasError = true; + } + else if(!this.isValidEmail(this.user.email)) { + this.errorMessage = "Email invalide."; + this.hasError = true; + } + else if(this.password.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'mot de passe'."; + this.hasError = true; + } + else if(this.password !== this.confirmPassword) { + this.errorMessage = "Le mot de passe est différent de sa confirmation."; + this.hasError = true; + } + else { + this.errorMessage = "" ; + this.hasError = false; + } + } + + + // Indique si email a bien le format d'un email + isValidEmail(email): boolean + { + let re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); + } + + + // Récupère la liste des centres d'intérets (car celle-ci est remplie à l'aide d'un component intermédiaire) + onEventInputInterests(myInterets: string[]): void + { + this.user.interests = myInterets; + } + +} diff --git a/userAndAdvertiser/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.html b/userAndAdvertiser/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.html new file mode 100644 index 0000000..1cd51fe --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.html @@ -0,0 +1,11 @@ +

+ Votre inscription a bien été effectuée. +

+ +

+ Votre inscription est en cours de validation. +

+ +
+ +
diff --git a/userAndAdvertiser/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.scss b/userAndAdvertiser/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.scss new file mode 100644 index 0000000..85730e0 --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.scss @@ -0,0 +1,7 @@ +p { + font-size: small; +} + +div { + font-size: small; +} diff --git a/userAndAdvertiser/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.spec.ts b/userAndAdvertiser/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.spec.ts new file mode 100644 index 0000000..d6f9908 --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupConfirmationComponent } from './popup-confirmation.component'; + +describe('PopupConfirmationComponent', () => { + let component: PopupConfirmationComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupConfirmationComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupConfirmationComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.ts b/userAndAdvertiser/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.ts new file mode 100644 index 0000000..59e3325 --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/register/popup-confirmation/popup-confirmation.component.ts @@ -0,0 +1,13 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; + +@Component({ + selector: 'app-popup-confirmation', + templateUrl: './popup-confirmation.component.html', + styleUrls: ['./popup-confirmation.component.scss'] +}) +export class PopupConfirmationComponent +{ + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data) {} +} diff --git a/userAndAdvertiser/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.html b/userAndAdvertiser/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.html new file mode 100644 index 0000000..d4ad9f5 --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.html @@ -0,0 +1,40 @@ + +
+ +
+ + + + + + +
+ +
diff --git a/userAndAdvertiser/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.scss b/userAndAdvertiser/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.scss new file mode 100644 index 0000000..e1fefaa --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.scss @@ -0,0 +1,79 @@ +.navbar { + background-color: black; + height: 60px; + font-size: medium; + color: white; +} + + +.navbar-expand-lg { + border-bottom: solid; + border-color: white; + border-bottom-width: 2px; +} + + +// PolyNotFound +.navbar-brand { + font-family: cursive; + font-weight: bold; + font-size: x-large; + margin-left: 15px; + color: white; +} + + +// Recherche, Mes Playlists, Historique +.nav-link { + color: white; +} +.nav-link:hover { + color: grey; +} + + +// Bonton deconnexion +.btnDeconnexion { + font-size: medium; + margin: 0px 10px 0px 10px +} +.btnDeconnexion:hover { + color: grey; +} + + +.monLi { + margin: 0px 10px 0px 10px; +} + + +img { + border: solid 2px white; + border-radius: 50px; + margin: 0px 10px 0px 15px; + width: 40px; + height: 40px; +} +img:hover { + cursor: pointer; +} + + +// -------------------------------------------------------------------- + + +::ng-deep .mat-slide-toggle-thumb { + background-color: #c8c8c8; +} + +::ng-deep .mat-slide-toggle-bar { + background-color: #ffffff; +} + +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-thumb { + background-color: #ffffff; +} + +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-bar { + background-color: #646464; +} diff --git a/userAndAdvertiser/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.spec.ts b/userAndAdvertiser/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.spec.ts new file mode 100644 index 0000000..f3f7f27 --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NavbarBeforeConnexionComponent } from './navbar-before-connexion.component'; + +describe('NavbarBeforeConnexionComponent', () => { + let component: NavbarBeforeConnexionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ NavbarBeforeConnexionComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NavbarBeforeConnexionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.ts b/userAndAdvertiser/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.ts new file mode 100644 index 0000000..4a3f05e --- /dev/null +++ b/userAndAdvertiser/src/app/beforeConnexion/utils/navbar-before-connexion/navbar-before-connexion.component.ts @@ -0,0 +1,11 @@ +import {Component, Input} from '@angular/core'; + +@Component({ + selector: 'app-navbar-before-connexion', + templateUrl: './navbar-before-connexion.component.html', + styleUrls: ['./navbar-before-connexion.component.scss'] +}) +export class NavbarBeforeConnexionComponent +{ + @Input() pour = "login"; +} diff --git a/userAndAdvertiser/src/app/user/history/page-history-user/page-history-user.component.html b/userAndAdvertiser/src/app/user/history/page-history-user/page-history-user.component.html new file mode 100644 index 0000000..97f7344 --- /dev/null +++ b/userAndAdvertiser/src/app/user/history/page-history-user/page-history-user.component.html @@ -0,0 +1,70 @@ +
+
+ + +

+ + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Aperçu +
+ + +
+
Titre {{video.title}} Date + {{video.date | date:'dd/LL/YYYY à HH:mm:ss'}} + Source {{video.source}} Action + +
Aucune vidéo ne correspond au filtre: "{{input.value}}"
+
+ +
+

+ +
+
diff --git a/userAndAdvertiser/src/app/user/history/page-history-user/page-history-user.component.scss b/userAndAdvertiser/src/app/user/history/page-history-user/page-history-user.component.scss new file mode 100644 index 0000000..bbd894d --- /dev/null +++ b/userAndAdvertiser/src/app/user/history/page-history-user/page-history-user.component.scss @@ -0,0 +1,46 @@ +.myContainer { + max-width: 100vw; + height: 100vh; + overflow-x: hidden; +} + +table { + width: 80%; + margin: 0 auto; +} + + +th.mat-sort-header-sorted { + color: black; +} + + +input { + width: 35%; + font-size: large; +} + +// ------------------------------------------------------- + +.imgsContainer { + position: relative; + width: 20vw; + height: 15vh; + cursor: pointer; +} + +.imgPlay { + position: absolute; + margin-left: 9vw; + width: 3vw; + margin-top: 5vh; + height: 6vh; + padding: 0px 0px 0px 0px; +} + +.imgVideo { + border: solid 1px black; + width: 20vw; + height: 15vh; + padding: 0px 0px 0px 0px; +} diff --git a/userAndAdvertiser/src/app/user/history/page-history-user/page-history-user.component.spec.ts b/userAndAdvertiser/src/app/user/history/page-history-user/page-history-user.component.spec.ts new file mode 100644 index 0000000..9fd31c3 --- /dev/null +++ b/userAndAdvertiser/src/app/user/history/page-history-user/page-history-user.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageHistoryUserComponent } from './page-history-user.component'; + +describe('PageHistoriqueComponent', () => { + let component: PageHistoryUserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageHistoryUserComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageHistoryUserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/history/page-history-user/page-history-user.component.ts b/userAndAdvertiser/src/app/user/history/page-history-user/page-history-user.component.ts new file mode 100644 index 0000000..5485ec1 --- /dev/null +++ b/userAndAdvertiser/src/app/user/history/page-history-user/page-history-user.component.ts @@ -0,0 +1,113 @@ +import {AfterViewInit, Component, ViewChild} from '@angular/core'; +import {MatTableDataSource} from "@angular/material/table"; +import {MatSort} from "@angular/material/sort"; +import {MatPaginator} from "@angular/material/paginator"; +import {Router} from "@angular/router"; +import {MessageService} from "../../../utils/message/message.service"; +import {ThemeService} from "../../../utils/theme/theme.service"; + + + +@Component({ + selector: 'app-page-history-user', + templateUrl: './page-history-user.component.html', + styleUrls: ['./page-history-user.component.scss'] +}) +export class PageHistoryUserComponent implements AfterViewInit +{ + displayedColumns: string[] = [ 'aperçu', 'title', 'date', 'source', 'action' ]; + dataSource ; + @ViewChild(MatSort) sort: MatSort; + @ViewChild(MatPaginator) paginator: MatPaginator; + + + constructor( public themeService: ThemeService, + private messageService: MessageService, + private router: Router ) { } + + + // charge la page + ngAfterViewInit(): void + { + this.messageService + .get("user/history") + .subscribe(ret => this.ngAfterViewInitCallback(ret), err => this.ngAfterViewInitCallback(err)); + } + + + ngAfterViewInitCallback(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + const tabVideoHistory = retour.data.map( video => { + return { + _id: video._id, + videoId: video.videoId, + imageUrl: video.imageUrl, + title: video.title, + date: video.watchedDate, + source: video.source, + } + }); + this.dataSource = new MatTableDataSource(tabVideoHistory); + this.dataSource.sort = this.sort; + this.dataSource.paginator = this.paginator; + this.dataSource = this.dataSource; + } + } + + + // Applique le filtre + applyFilter(event: Event): void + { + const filterValue = (event.target as HTMLInputElement).value; + this.dataSource.filter = filterValue.trim().toLowerCase(); + } + + + // Supprime la video + onDelete(video: any): void + { + this.messageService + .put("video/update/"+video._id, { watchedDates: []}) + .subscribe(ret => this.onDeleteCallback(ret, video), err => this.onDeleteCallback(err, video)) + } + + + onDeleteCallback(retour: any, video: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + const index = this.dataSource.data.indexOf(video); + this.dataSource.data.splice(index, 1); + this.dataSource.data = this.dataSource.data; + this.dataSource = this.dataSource; + } + } + + + onVideo(video): void + { + this.messageService + .put("video/update/"+video._id, {watchedDate: true}) + .subscribe(ret => this.onVideoCallback(ret), err => this.onVideoCallback(err)); + + const params = { + videoId: video.videoId, + source: video.source, + from: "history", + }; + this.router.navigate(['/user/watching'], { queryParams: params }); + } + + + onVideoCallback(retour: any): void + { + if(retour.status !== "success") console.log(retour); + } + +} diff --git a/userAndAdvertiser/src/app/user/myPlaylists/page-my-playlists/page-my-playlists.component.html b/userAndAdvertiser/src/app/user/myPlaylists/page-my-playlists/page-my-playlists.component.html new file mode 100644 index 0000000..c92a060 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/page-my-playlists/page-my-playlists.component.html @@ -0,0 +1,35 @@ +
+
+ + +
+ +
+
+ + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ +
+
diff --git a/userAndAdvertiser/src/app/user/myPlaylists/page-my-playlists/page-my-playlists.component.scss b/userAndAdvertiser/src/app/user/myPlaylists/page-my-playlists/page-my-playlists.component.scss new file mode 100644 index 0000000..fad665f --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/page-my-playlists/page-my-playlists.component.scss @@ -0,0 +1,48 @@ +.lightTheme { + border-color: black; +} +.darkTheme { + border-color: white; +} +.myContainer { + text-align: center; + max-width: 100vw; + height: 100vh; + overflow-x: hidden; +} + +// Liste des vidéos ------------------------------------------------- + +.celluleListeVideo { + margin: 0px 0px 0px 0px; +} + +// Liste des playlists --------------------------------------------- + +.celluleListePlaylist { + margin: 0px 0px 0px 0px; +} + +// Pub ------------------------------------------------------------- + +.cellulePub { + padding: 0px 10px 0px 10px; + width: 100%; + text-align: center; + justify-content: center; +} + +.conteneurPub { + //height: 85vh; + text-align: center; + justify-content: center; + vertical-align: middle; + display: block; + width: 75%; + margin-left: auto; + margin-right: auto; + position: absolute; + top: 50%; + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} diff --git a/userAndAdvertiser/src/app/user/myPlaylists/page-my-playlists/page-my-playlists.component.spec.ts b/userAndAdvertiser/src/app/user/myPlaylists/page-my-playlists/page-my-playlists.component.spec.ts new file mode 100644 index 0000000..2dba23b --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/page-my-playlists/page-my-playlists.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageMyPlaylistsComponent } from './page-my-playlists.component'; + +describe('PageMesPlaylistsComponent', () => { + let component: PageMyPlaylistsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageMyPlaylistsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageMyPlaylistsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/myPlaylists/page-my-playlists/page-my-playlists.component.ts b/userAndAdvertiser/src/app/user/myPlaylists/page-my-playlists/page-my-playlists.component.ts new file mode 100644 index 0000000..0b94523 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/page-my-playlists/page-my-playlists.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit } from '@angular/core'; +import {HttpParams} from "@angular/common/http"; +import {ThemeService} from "../../../utils/theme/theme.service"; +import {MessageService} from "../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-page-my-playlists', + templateUrl: './page-my-playlists.component.html', + styleUrls: ['./page-my-playlists.component.scss'] +}) +export class PageMyPlaylistsComponent implements OnInit +{ + ad; // pub + playlist: any; // la playlist sélectionnée + + + constructor( public themeService: ThemeService, + private messageService: MessageService ) { } + + + ngOnInit(): void + { + let params = new HttpParams(); + params = params.append("quantity", 1); + this.messageService + .get("user/ad", params) + .subscribe(ret => this.adCallback(ret), err => this.adCallback(err)); + } + + + adCallback(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + this.ad = retour.data[0]; + } + } + + + transmitPlaylistToVideoList(playlist): void + { + if ((playlist === null) || (playlist === undefined)) { + this.playlist = playlist; + } + else { + this.messageService + .get("playlist/findOne/" + playlist.id) + .subscribe(ret => this.afterReceivingPlaylistWithVideo(ret, playlist), err => this.afterReceivingPlaylistWithVideo(err, playlist)); + } + } + + + afterReceivingPlaylistWithVideo(retour: any, playlist): void + { + if(retour.status !== "success") { + console.log(retour); + this.playlist = playlist; + } + else { + this.playlist = retour.data; + } + } + +} diff --git a/userAndAdvertiser/src/app/user/myPlaylists/playlist-list/playlist-list.component.html b/userAndAdvertiser/src/app/user/myPlaylists/playlist-list/playlist-list.component.html new file mode 100644 index 0000000..2185a77 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/playlist-list/playlist-list.component.html @@ -0,0 +1,48 @@ +
+
+ + +
+
+ +
+
+ + + + +
+
+
+ +
+
+ +
+
+ {{playlist.name}}
+ {{playlist.videoIds.length}} vidéo + {{playlist.videoIds.length}} vidéos +
+
+ +
+
+ +
+
+
+ + + + +
+ +
+ +
+
diff --git a/userAndAdvertiser/src/app/user/myPlaylists/playlist-list/playlist-list.component.scss b/userAndAdvertiser/src/app/user/myPlaylists/playlist-list/playlist-list.component.scss new file mode 100644 index 0000000..0376ee3 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/playlist-list/playlist-list.component.scss @@ -0,0 +1,94 @@ +.myContainer { + background-color: white ; + text-align: center; + width: 35vw; + margin: 1vh 0vh 3vh 0vh; + padding: 0px; + border: solid 2px black; + border-radius: 10px; + box-shadow: 10px 5px 5px black; +} + +// SearchBar ----------------------------------------------------------- + +.searchBarContainer { + text-align: center; + margin: 0px 0px 0px 0px; + padding: 10px 0px 10px 0px; + //background-color: #dcdcdc; + background: linear-gradient(top, rgba(38,38,38,0.8), #e6e6e6 25%, #fff 38%, #c5c5c5 87%, rgba(38,38,38,0.8)); + background: -webkit-linear-gradient(top, #c5c5c5, #e6e6e6 25%, #fff 38%, #c5c5c5 87%, #c5c5c5); + font-size: large; + border-bottom: solid 1px black; + border-top-left-radius: 10px; + border-top-right-radius: 10px; +} + +.inputSearchBar { + width: 70%; + border-radius: 5px; +} + + +// Liste des playlists ------------------------------------------------- + +.playlistListContainer { + max-width: 100%; + height: 60vh; + overflow-y: scroll; + padding: 0px; + overflow-x: hidden; +} + +.playlistContainer { + max-width: 100%; + padding: 0px; + overflow-x: hidden; +} + + +.btnPlaylist { + background-color: white; + padding: 20px; + border-bottom: solid 1px black; + //width: 100%; + width: 35vw; + overflow-x: hidden; +} +.btnPlaylist:hover { + background-color: #f0f0f0; +} + +.btnPlaylistFocus { + background-color: #e6e6e6; +} +.btnPlaylistFocus:hover { + background-color: #e6e6e6; +} + + +.playListCount { + color: gray; + font-style: italic; +} + +// Bouton creer playlist ------------------------------------------------- + +.btnCreerPlaylistContainer { + margin: 0px 0px 0px 0px; + background-color: #dcdcdc; + font-size: large; + border-top: solid 1px black; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; +} + +.btnCreerPlaylist { + margin: 0px 0px 0px 0px; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; + background: linear-gradient(top, rgba(38,38,38,0.8), #e6e6e6 25%, #fff 38%, #c5c5c5 87%, rgba(38,38,38,0.8)); + background: -webkit-linear-gradient(top, #c5c5c5, #e6e6e6 25%, #fff 38%, #c5c5c5 87%, #c5c5c5); + //background: linear-gradient(180deg, #e6e6e6 0%, rgba(0,0,0,0.25) 49%, rgba(38,38,38,0.6) 51%, rgba(0,0,0,0.25) 100%); +} + diff --git a/userAndAdvertiser/src/app/user/myPlaylists/playlist-list/playlist-list.component.spec.ts b/userAndAdvertiser/src/app/user/myPlaylists/playlist-list/playlist-list.component.spec.ts new file mode 100644 index 0000000..9308f2c --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/playlist-list/playlist-list.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PlaylistListComponent } from './playlist-list.component'; + +describe('PlaylistListComponent', () => { + let component: PlaylistListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PlaylistListComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PlaylistListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/myPlaylists/playlist-list/playlist-list.component.ts b/userAndAdvertiser/src/app/user/myPlaylists/playlist-list/playlist-list.component.ts new file mode 100644 index 0000000..9d9dbc2 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/playlist-list/playlist-list.component.ts @@ -0,0 +1,161 @@ +import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; +import {MatDialog} from "@angular/material/dialog"; +import {MatSnackBar} from "@angular/material/snack-bar"; +import {PopupCreateOrUpdatePlaylistComponent} from "../popup-create-or-update-playlist/popup-create-or-update-playlist.component"; +import {PopupDeletePlaylistComponent} from "../popup-delete-playlist/popup-delete-playlist.component"; +import {ThemeService} from "../../../utils/theme/theme.service"; +import {MessageService} from "../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-playlist-list', + templateUrl: './playlist-list.component.html', + styleUrls: ['./playlist-list.component.scss'] +}) +export class PlaylistListComponent implements OnInit +{ + allPlaylists: any[] = []; // toutes les playlists + @Output() eventEmitter = new EventEmitter(); // pour envoyer au parent la playlist selectionner + search: string = "" ; // contenu de la barre de recherche + tabPlaylist: any[] = []; // playlist affichées + playlistFocusedOn: any; + + + constructor( public themeService: ThemeService, + public dialog: MatDialog, + public snackBar: MatSnackBar, + private messageService: MessageService ) { } + + + ngOnInit(): void + { + this.messageService + .get("playlist/findAll") + .subscribe( retour => this.ngOnInitCallback(retour), err => this.ngOnInitCallback(err) ); + } + + + ngOnInitCallback(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } else { + const aux = retour.data.filter( x => x.isActive === true); + this.allPlaylists = aux.map(x => { + x["_id"] = x.id ; + return x; + }); + this.tabPlaylist = [].concat(this.allPlaylists); + } + } + + + // s'execute lorsqu'on écrit sur la barre de recherche + whileSearch() + { + this.tabPlaylist = []; + for(let playlist of this.allPlaylists) + { + if(playlist.name.includes(this.search)) this.tabPlaylist.push(playlist); + } + } + + + // click sur créer playlist + onCreatePlaylist(): void + { + const config = { + data: { + action: "create", + tabPlaylist: this.tabPlaylist, + } + }; + this.dialog + .open(PopupCreateOrUpdatePlaylistComponent, config ) + .afterClosed() + .subscribe(playlist => { + + const config = { duration: 1500, panelClass: "custom-class" }; + if((playlist === null) || (playlist === undefined)) { + this.snackBar.open("Opération annulée", "", config); + } + else { + playlist["_id"] = playlist.id; + this.allPlaylists.push(playlist); + this.tabPlaylist.push(playlist); + this.snackBar.open(`La playlist '${playlist.name}' a bien été créée ✔`, "", config); + } + }); + } + + + // click sur update playlist + onUpdatePlaylist(playlistToUpdate): void + { + const config = { + data: { + action: "update", + tabPlaylist: this.tabPlaylist, + playlistName: playlistToUpdate.name, + playlistId: playlistToUpdate._id + } + }; + this.dialog + .open(PopupCreateOrUpdatePlaylistComponent, config) + .afterClosed() + .subscribe(newName => { + + const config = { duration: 1500, panelClass: "custom-class" }; + if((newName === null) || (newName === undefined)) { + this.snackBar.open("Opération annulée", "", config); + } + else { + let index = this.allPlaylists.findIndex( elt => (elt._id === playlistToUpdate._id)); + this.allPlaylists[index].name = newName; + index = this.tabPlaylist.findIndex( elt => (elt._id === playlistToUpdate._id)); + this.tabPlaylist[index].name = newName; + this.snackBar.open(`La playlist '${playlistToUpdate.name}' a bien été mise à jour ✔`, "", config); + this.eventEmitter.emit(this.tabPlaylist[index]); + this.playlistFocusedOn = this.tabPlaylist[index] + } + }); + } + + + // click sur supprimer playlist + onDeletePlaylist(playlist): void + { + const config = {data: playlist}; + this.dialog + .open(PopupDeletePlaylistComponent, config) + .afterClosed() + .subscribe(retour => { + + const config = { duration: 1500, panelClass: "custom-class" }; + if((retour === null) || (retour === undefined)) { + this.snackBar.open("Opération annulée", "", config); + } + else { + let index = this.allPlaylists.indexOf(playlist); + if(index >= 0) this.allPlaylists.splice(index, 1); + + index = this.tabPlaylist.indexOf(playlist); + if(index >= 0) this.tabPlaylist.splice(index, 1); + + this.eventEmitter.emit(null); + this.playlistFocusedOn = null; + this.snackBar.open(`La playlist '${playlist.name}' a bien été suprimée ✔`, "", config); + } + }); + } + + + // retourne la class CSS de conteneur de playlist + getClassOfPlaylistContainer(playlist): string + { + if(playlist === this.playlistFocusedOn) return "row btnPlaylist btnPlaylistFocus" ; + else return "row btnPlaylist" ; + } + +} diff --git a/userAndAdvertiser/src/app/user/myPlaylists/popup-create-or-update-playlist/popup-create-or-update-playlist.component.html b/userAndAdvertiser/src/app/user/myPlaylists/popup-create-or-update-playlist/popup-create-or-update-playlist.component.html new file mode 100644 index 0000000..d88fa34 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/popup-create-or-update-playlist/popup-create-or-update-playlist.component.html @@ -0,0 +1,19 @@ +
+ +
+ + Nom de la playlist + + + {{errorMessage}} +
+ +
+ + +
+ +
diff --git a/userAndAdvertiser/src/app/user/myPlaylists/popup-create-or-update-playlist/popup-create-or-update-playlist.component.scss b/userAndAdvertiser/src/app/user/myPlaylists/popup-create-or-update-playlist/popup-create-or-update-playlist.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/userAndAdvertiser/src/app/user/myPlaylists/popup-create-or-update-playlist/popup-create-or-update-playlist.component.spec.ts b/userAndAdvertiser/src/app/user/myPlaylists/popup-create-or-update-playlist/popup-create-or-update-playlist.component.spec.ts new file mode 100644 index 0000000..640bdbc --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/popup-create-or-update-playlist/popup-create-or-update-playlist.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupCreateOrUpdatePlaylistComponent } from './popup-create-or-update-playlist.component'; + +describe('PopupCreatePlaylistComponent', () => { + let component: PopupCreateOrUpdatePlaylistComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupCreateOrUpdatePlaylistComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupCreateOrUpdatePlaylistComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/myPlaylists/popup-create-or-update-playlist/popup-create-or-update-playlist.component.ts b/userAndAdvertiser/src/app/user/myPlaylists/popup-create-or-update-playlist/popup-create-or-update-playlist.component.ts new file mode 100644 index 0000000..1be479b --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/popup-create-or-update-playlist/popup-create-or-update-playlist.component.ts @@ -0,0 +1,90 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {MessageService} from "../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-popup-create-or-update-playlist', + templateUrl: './popup-create-or-update-playlist.component.html', + styleUrls: ['./popup-create-or-update-playlist.component.scss'] +}) +export class PopupCreateOrUpdatePlaylistComponent implements OnInit +{ + name: string = "" ; + hasError: boolean = false; + tabNomPlaylist: string[] = []; + errorMessage: string = "" ; + action: string = ""; + + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data, + private messageService: MessageService) { } + + + ngOnInit(): void + { + this.action = this.data.action; + this.tabNomPlaylist = this.data.tabPlaylist.map( playlist0 => playlist0.name ); + if(this.action === "update") this.name = this.data.playlistName; + } + + + onValider(): void + { + this.checkError(); + if(!this.hasError) + { + if(this.action === "create") + { + this.messageService + .post("playlist/create", {name: this.name}) + .subscribe(retour => this.onValiderCallback(retour), err => this.onValiderCallback(err)); + } + else if(this.action === "update") + { + this.messageService + .put("playlist/update/"+this.data.playlistId, {name: this.name}) + .subscribe(retour => this.onValiderCallback(retour), err => this.onValiderCallback(err)); + } + } + } + + + onValiderCallback(retour): void + { + if(retour.status !== "success") { + console.log(retour); + this.dialogRef.close(null); + } + else { + if(this.action === "create") this.dialogRef.close(retour.data); + else if(this.action === "update") this.dialogRef.close(this.name); + } + } + + + checkError(): void + { + if(this.name === "") { + this.errorMessage = "Le nom ne peut pas être vide" ; + this.hasError = true; + } + else if(this.tabNomPlaylist.includes(this.name)){ + this.errorMessage = "Ce nom est déjà utilisé" ; + this.hasError = true; + } + else { + this.hasError = false; + this.errorMessage = "" ; + } + } + + + onAnnuler(): void + { + this.dialogRef.close(null); + } + +} diff --git a/userAndAdvertiser/src/app/user/myPlaylists/popup-delete-playlist/popup-delete-playlist.component.html b/userAndAdvertiser/src/app/user/myPlaylists/popup-delete-playlist/popup-delete-playlist.component.html new file mode 100644 index 0000000..0335139 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/popup-delete-playlist/popup-delete-playlist.component.html @@ -0,0 +1,8 @@ + + Êtes-vous sûr de vouloir supprimer {{playlist.name}} ? + + + + + + diff --git a/userAndAdvertiser/src/app/user/myPlaylists/popup-delete-playlist/popup-delete-playlist.component.scss b/userAndAdvertiser/src/app/user/myPlaylists/popup-delete-playlist/popup-delete-playlist.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/userAndAdvertiser/src/app/user/myPlaylists/popup-delete-playlist/popup-delete-playlist.component.spec.ts b/userAndAdvertiser/src/app/user/myPlaylists/popup-delete-playlist/popup-delete-playlist.component.spec.ts new file mode 100644 index 0000000..83d1cf7 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/popup-delete-playlist/popup-delete-playlist.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupDeletePlaylistComponent } from './popup-delete-playlist.component'; + +describe('PopupDeletePlaylistComponent', () => { + let component: PopupDeletePlaylistComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupDeletePlaylistComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupDeletePlaylistComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/myPlaylists/popup-delete-playlist/popup-delete-playlist.component.ts b/userAndAdvertiser/src/app/user/myPlaylists/popup-delete-playlist/popup-delete-playlist.component.ts new file mode 100644 index 0000000..dda9f5b --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/popup-delete-playlist/popup-delete-playlist.component.ts @@ -0,0 +1,41 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {MessageService} from "../../../utils/message/message.service"; + +@Component({ + selector: 'app-popup-delete-playlist', + templateUrl: './popup-delete-playlist.component.html', + styleUrls: ['./popup-delete-playlist.component.scss'] +}) +export class PopupDeletePlaylistComponent implements OnInit +{ + playlist; + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data, + private messageService: MessageService ) { } + + ngOnInit(): void + { + this.playlist = this.data; + } + + onValidate(): void + { + this.messageService + .delete("playlist/delete/"+this.playlist._id) + .subscribe( retour => this.onValidateCallback(retour), err => this.onValidateCallback(err)); + } + + onValidateCallback(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + this.dialogRef.close(null); + } + else { + this.dialogRef.close(true); + } + } + +} diff --git a/userAndAdvertiser/src/app/user/myPlaylists/video-list/video-list.component.html b/userAndAdvertiser/src/app/user/myPlaylists/video-list/video-list.component.html new file mode 100644 index 0000000..1954fee --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/video-list/video-list.component.html @@ -0,0 +1,91 @@ +
+
+ + +
+ + +
+ + + + {{playlist.name}} + + + + + Aucune playlist selectionnée + + +
+ + + + + + +
+
+ + +
+ + + +
+ + + + + + + + + + +
+
+ + +
+
+
+ + + + + + +
+ + + + {{video.title}} + + +
+
+ + + +
+
+ +
+
+ + + + + + +
+
a
+
+ +
+
diff --git a/userAndAdvertiser/src/app/user/myPlaylists/video-list/video-list.component.scss b/userAndAdvertiser/src/app/user/myPlaylists/video-list/video-list.component.scss new file mode 100644 index 0000000..e3af7ce --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/video-list/video-list.component.scss @@ -0,0 +1,83 @@ +.myContainer { + //background-color: white ; + background: linear-gradient(top, rgba(38,38,38,0.8), #e6e6e6 25%, #fff 38%, #c5c5c5 87%, rgba(38,38,38,0.8)); + background: -webkit-linear-gradient(top, #c5c5c5, #e6e6e6 25%, #fff 38%, #c5c5c5 87%, #c5c5c5); + text-align: center; + width: 35vw; + margin: 1vh 0vh 3vh 0vh; + padding: 0px; + border: solid 2px black; + border-radius: 10px; + box-shadow: 10px 5px 5px black; +} + +// TopBorder -------------------------------------------------------- + +.topBorder { + margin: 0px 0px 0px 0px; + //background-color: #dcdcdc; + background: linear-gradient(top, rgba(38,38,38,0.8), #e6e6e6 25%, #fff 38%, #c5c5c5 87%, rgba(38,38,38,0.8)); + background: -webkit-linear-gradient(top, #c5c5c5, #e6e6e6 25%, #fff 38%, #c5c5c5 87%, #c5c5c5); + text-align: left; + padding: 5px 0px 5px 5px; + border-bottom: solid 1px black; + border-top-left-radius: 10px; + border-top-right-radius: 10px; +} + +.spanPlayListTitle { + font-size: large; + font-weight: bold; +} + +// Liste des videos ------------------------------------------------ + +.listVideoContainer { + height: 65vh; + background-color: white; + padding: 0px; + overflow-y: scroll; +} + +.videoContainer { + border-bottom: solid 1px black; + padding: 15px 0px 15px 0px; + width: 100%; +} + +.imgsContainer { + position: relative; + width: 20vw; + height: 15vh; + cursor: pointer; +} + +.imgPlay { + position: absolute; + margin-left: 9vw; + width: 3vw; + margin-top: 5vh; + height: 6vh; + padding: 0px 0px 0px 0px; +} + +.imgVideo { + border: solid 1px black; + width: 20vw; + height: 15vh; + padding: 0px 0px 0px 0px; +} + +// BottomBorder -------------------------------------------------------- + +.bottomBorder { + margin: 0px 0px 0px 0px; + //background-color: #dcdcdc; + background: linear-gradient(top, rgba(38,38,38,0.8), #e6e6e6 25%, #fff 38%, #c5c5c5 87%, rgba(38,38,38,0.8)); + background: -webkit-linear-gradient(top, #c5c5c5, #e6e6e6 25%, #fff 38%, #c5c5c5 87%, #c5c5c5); + border-top: solid 1px black; + border-bottom: solid 1px black; + font-size: large; + border-bottom-left-radius: 10px; + border-bottom-right-radius: 10px; +} diff --git a/userAndAdvertiser/src/app/user/myPlaylists/video-list/video-list.component.spec.ts b/userAndAdvertiser/src/app/user/myPlaylists/video-list/video-list.component.spec.ts new file mode 100644 index 0000000..403cc76 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/video-list/video-list.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VideoListComponent } from './video-list.component'; + +describe('VideoListComponent', () => { + let component: VideoListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ VideoListComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(VideoListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/myPlaylists/video-list/video-list.component.ts b/userAndAdvertiser/src/app/user/myPlaylists/video-list/video-list.component.ts new file mode 100644 index 0000000..25c4edb --- /dev/null +++ b/userAndAdvertiser/src/app/user/myPlaylists/video-list/video-list.component.ts @@ -0,0 +1,92 @@ +import {Component, Input, OnChanges, SimpleChanges} from '@angular/core'; +import {AddVideoToPlaylistsService} from "../../utils/services/addVideoToPlaylists/add-video-to-playlists.service"; +import {MatSnackBar} from "@angular/material/snack-bar"; +import {Router} from "@angular/router"; +import {MessageService} from "../../../utils/message/message.service"; +import {ThemeService} from "../../../utils/theme/theme.service"; +import {ProfilService} from "../../../utils/profil/profil.service"; + + + +@Component({ + selector: 'app-video-list', + templateUrl: './video-list.component.html', + styleUrls: ['./video-list.component.scss'] +}) +export class VideoListComponent implements OnChanges +{ + @Input() playlist: any; + videosInPlaylist: any[] = []; + + + constructor( private messageService: MessageService, + public themeService: ThemeService, + private addVideoToPlaylistsService: AddVideoToPlaylistsService, + private snackBar: MatSnackBar, + private profilService: ProfilService, + private router: Router ) { } + + + ngOnChanges(changes: SimpleChanges): void + { + if((this.playlist !== null) && (this.playlist !== undefined)) this.videosInPlaylist = this.playlist.videos; + } + + + onAddToPlaylist(video: any): void + { + this.addVideoToPlaylistsService.run(video.videoId, video.source, video.interest); + } + + + onDelete(video0: any, indexVideo: number): void + { + const data = { + videoId: { + id: video0._id, + action: "delete" + } + } + this.messageService + .put("playlist/update/"+this.playlist._id, data) + .subscribe( ret => this.onDeleteCallback(ret, indexVideo), err => this.onDeleteCallback(err, indexVideo)); + } + + + onDeleteCallback(retour: any, indexVideo: number): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + this.playlist.videos.splice(indexVideo, 1); + this.videosInPlaylist.splice(indexVideo, 1); + let message = "La video a bien été supprimé de la playlist"; + const config = { duration: 1000, panelClass: "custom-class" }; + this.snackBar.open( message, "", config); + } + } + + + onVideo(video: any): void + { + this.messageService + .put("video/update/"+video._id, {watchedDate: true}) + .subscribe(ret => this.onVideoCallback(ret), err => this.onVideoCallback(err)); + + const params = { + videoId: video.videoId, + source: video.source, + _idPlaylist: this.playlist._id, + from: "myPlaylists", + }; + this.router.navigate(['/user/watching'], { queryParams: params }); + } + + + onVideoCallback(retour: any): void + { + if(retour.status !== "success") console.log(retour); + } + +} diff --git a/userAndAdvertiser/src/app/user/myProfil/input-interests-profil/input-interests-profil.component.html b/userAndAdvertiser/src/app/user/myProfil/input-interests-profil/input-interests-profil.component.html new file mode 100644 index 0000000..be2bd07 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myProfil/input-interests-profil/input-interests-profil.component.html @@ -0,0 +1,39 @@ + + + + + + + + {{interest}} + + + + + + + + + + + + {{interest}} + + + + + + diff --git a/userAndAdvertiser/src/app/user/myProfil/input-interests-profil/input-interests-profil.component.scss b/userAndAdvertiser/src/app/user/myProfil/input-interests-profil/input-interests-profil.component.scss new file mode 100644 index 0000000..7628dd4 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myProfil/input-interests-profil/input-interests-profil.component.scss @@ -0,0 +1,20 @@ +mat-form-field { + width: 100%; + font-size: small; +} + +mat-chip-list { + font-size: small; +} + +mat-chip { + font-size: small; +} + +input { + font-size: small; +} + +mat-option { + font-size: small; +} diff --git a/userAndAdvertiser/src/app/user/myProfil/input-interests-profil/input-interests-profil.component.spec.ts b/userAndAdvertiser/src/app/user/myProfil/input-interests-profil/input-interests-profil.component.spec.ts new file mode 100644 index 0000000..0dd8314 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myProfil/input-interests-profil/input-interests-profil.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InputInterestsProfilComponent } from './input-interests-profil.component'; + +describe('InputInterestsComponent', () => { + let component: InputInterestsProfilComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ InputInterestsProfilComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(InputInterestsProfilComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/myProfil/input-interests-profil/input-interests-profil.component.ts b/userAndAdvertiser/src/app/user/myProfil/input-interests-profil/input-interests-profil.component.ts new file mode 100644 index 0000000..873052a --- /dev/null +++ b/userAndAdvertiser/src/app/user/myProfil/input-interests-profil/input-interests-profil.component.ts @@ -0,0 +1,121 @@ +import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; +import {COMMA, ENTER} from "@angular/cdk/keycodes"; +import {FormControl} from "@angular/forms"; +import {Observable} from "rxjs"; +import {map, startWith} from "rxjs/operators"; +import {MatChipInputEvent} from "@angular/material/chips"; +import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete"; +import {MessageService} from "../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-input-interests-profil', + templateUrl: './input-interests-profil.component.html', + styleUrls: ['./input-interests-profil.component.scss'] +}) +export class InputInterestsProfilComponent implements OnInit +{ + selectable = true; + removable = true; + separatorKeysCodes: number[] = [ENTER, COMMA]; + formControl = new FormControl(); + filteredInterests: Observable; + @Input() myInterests: string[] = []; + allInterests: string[] = []; + @Output() eventEmitter = new EventEmitter(); + @ViewChild('tagInput') tagInput: ElementRef; + interestsNotSelected: string[] = []; + + + constructor( private messageService: MessageService ) {} + + + ngOnInit(): void + { + this.filteredInterests = this.formControl.valueChanges.pipe( + startWith(null), + map((fruit: string | null) => fruit ? this._filter(fruit) : this.interestsNotSelected.slice())); + + this.messageService + .get("misc/getInterests") + .subscribe( retour => { + + if(retour.status !== "success") { + console.log(retour); + } + else { + this.allInterests = []; + for(let elt of retour.data) + { + this.allInterests.push(elt.interest); + this.interestsNotSelected.push(elt.interest); + } + } + }); + } + + + add(event: MatChipInputEvent): void + { + const value = (event.value || '').trim(); + const index = this.interestsNotSelected.indexOf(value); + if (value && (index !== -1) && (!this.myInterests.includes(value))) + { + this.myInterests.push(value); + event.chipInput!.clear(); + this.formControl.setValue(null); + this.eventEmitter.emit(this.myInterests); + this.interestsNotSelected.splice(index, 1); + } + } + + + remove(interest: string): void + { + // supprimer 'interest' de 'myInterest' + const index = this.myInterests.indexOf(interest); + if (index >= 0) this.myInterests.splice(index, 1); + this.eventEmitter.emit(this.myInterests); + + // remmettre 'interest' dans 'interestsNotSelected' + if(!this.interestsNotSelected.includes(interest)) + { + const indexOfAutres = this.interestsNotSelected.indexOf("Autres"); + if(indexOfAutres !== -1) + { + this.interestsNotSelected.splice(indexOfAutres, 1); + if(interest !== "Autres") this.interestsNotSelected.push(interest); + this.interestsNotSelected.sort(); + this.interestsNotSelected.push("Autres"); + } + else { + this.interestsNotSelected.push(interest); + if(interest !== "Autres") this.interestsNotSelected.sort(); + } + } + } + + + selected(event: MatAutocompleteSelectedEvent): void + { + const value = event.option.viewValue; + if(!this.myInterests.includes(value)) + { + this.myInterests.push(value); + const index = this.interestsNotSelected.indexOf(value); + this.interestsNotSelected.splice(index, 1); + } + this.tagInput.nativeElement.value = ''; + this.formControl.setValue(null); + this.eventEmitter.emit(this.myInterests); + } + + + private _filter(value: string): string[] + { + const filterValue = value.toLowerCase(); + return this.interestsNotSelected.filter(fruit => fruit.toLowerCase().includes(filterValue)); + } + +} diff --git a/userAndAdvertiser/src/app/user/myProfil/page-profil-user/page-profil-user.component.html b/userAndAdvertiser/src/app/user/myProfil/page-profil-user/page-profil-user.component.html new file mode 100644 index 0000000..7e69ded --- /dev/null +++ b/userAndAdvertiser/src/app/user/myProfil/page-profil-user/page-profil-user.component.html @@ -0,0 +1,92 @@ +
+
+ + + + + +
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+ +
+ +
+
+ + + + + + +
+ + +
+
Pseudo:
+
{{user.login}}
+
+ + +
+
Mail:
+
{{user.email}}
+
+ + +
+
Sexe:
+
+ Homme + Femme +
+
+ + +
+
Date de création:
+
{{ user.createdAt | date:'dd/LL/YYYY' }}
+
+ +
+
+ + + + + + +
+ + +
+
Date de naissance:
+
{{ user.dateOfBirth | date:'dd/LL/YYYY' }}
+
+ + +
+
Centres d'intérêt:
+
+
+
{{interest}}
+
+
+
+ +
+
diff --git a/userAndAdvertiser/src/app/user/myProfil/page-profil-user/page-profil-user.component.scss b/userAndAdvertiser/src/app/user/myProfil/page-profil-user/page-profil-user.component.scss new file mode 100644 index 0000000..ae34d41 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myProfil/page-profil-user/page-profil-user.component.scss @@ -0,0 +1,80 @@ +.myContainer { + max-width: 100vw; + height: 100vh; + overflow-x: hidden; +} + + +.boite { + margin-left: auto; + margin-right: auto; + width: 70%; + margin-top: 10vh; + border: solid 3px; + border-radius: 10px; + padding: 20px 40px 20px 40px; + background-color: #ffffff; + text-align: center; + box-shadow: 10px 5px 5px black; +} +.lightTheme .boite { + border-color: black; +} +.darkTheme .boite { + border-color: white; +} + +// -------------------------------------------------------------------------------------------- + +img { + margin: 0px 0px 10px 0px; + width: 5vw; + height: 5vw; + border: solid 2px black; + border-radius: 50%; + font-size: xxx-large; +} + +// -------------------------------------------------------------------------------------------- + +.myRow { + margin: 15px 0px 15px 0px; +} +.myLabel { + text-align: right; + padding: 0px 5px 0px 0px; + margin: 0px; + font-weight: bold; +} +.myValue { + text-align: left; + padding: 0px 0px 0px 5px; + margin: 0px; +} + +// -------------------------------------------------------------------------------------------- + +.interestsContainer { + width: 70%; + height: 15vh; + overflow-y: scroll; + border: 1px solid black; +} + +.interest { + border-bottom: 1px solid #dcdcdc; + padding: 5px 5px 5px 5px; +} + +// -------------------------------------------------------------------------------------------- + +.btnContainer { + text-align: center; + margin-top: 40px; +} +.myBtn { + border: solid 1px black; + background-color: white; +} + + diff --git a/userAndAdvertiser/src/app/user/myProfil/page-profil-user/page-profil-user.component.spec.ts b/userAndAdvertiser/src/app/user/myProfil/page-profil-user/page-profil-user.component.spec.ts new file mode 100644 index 0000000..e8722af --- /dev/null +++ b/userAndAdvertiser/src/app/user/myProfil/page-profil-user/page-profil-user.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageProfilUserComponent } from './page-profil-user.component'; + +describe('PageProfilUserComponent', () => { + let component: PageProfilUserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageProfilUserComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageProfilUserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/myProfil/page-profil-user/page-profil-user.component.ts b/userAndAdvertiser/src/app/user/myProfil/page-profil-user/page-profil-user.component.ts new file mode 100644 index 0000000..be4615f --- /dev/null +++ b/userAndAdvertiser/src/app/user/myProfil/page-profil-user/page-profil-user.component.ts @@ -0,0 +1,89 @@ +import { Component, OnInit } from '@angular/core'; +import {MatDialog} from "@angular/material/dialog"; +import {MatSnackBar} from "@angular/material/snack-bar"; +import {PopupUpdateUserComponent} from "../popup-update-user/popup-update-user.component"; +import {ThemeService} from "../../../utils/theme/theme.service"; +import {MessageService} from "../../../utils/message/message.service"; +import {ProfilService} from "../../../utils/profil/profil.service"; + + + +@Component({ + selector: 'app-page-profil-user', + templateUrl: './page-profil-user.component.html', + styleUrls: ['./page-profil-user.component.scss'] +}) +export class PageProfilUserComponent implements OnInit +{ + user = { + _id: "", + login: "", + hashPass: "", + email: "", + role: { + name: "user", + permission: 0, + isAccepted: false, + }, + profileImageUrl: "", + dateOfBirth: null, + gender: "man", + interests: [], + company: "", + isActive: true, + createdAt: new Date(), + updatedAt: new Date(), + lastConnexion: null + }; + + + constructor( public themeService: ThemeService, + public dialog: MatDialog, + private snackBar: MatSnackBar, + private messageService: MessageService, + private profilService: ProfilService ) { } + + + ngOnInit(): void + { + this.messageService + .get( "user/findOne/"+this.profilService.getId()) + .subscribe( retour => this.ngOnInitCallback(retour), err => this.ngOnInitCallback(err) ) + } + + + ngOnInitCallback(retour: any) + { + if(retour.status !== "success") { + console.log(retour); + } + else { + this.user = retour.data; + } + } + + + onModifier() + { + const config = { + width: '70%', + data: { user: this.user } + }; + this.dialog + .open(PopupUpdateUserComponent, config) + .afterClosed() + .subscribe(retour => { + + if((retour === null) || (retour === undefined)) + { + const config = { duration: 1000, panelClass: "custom-class" }; + this.snackBar.open( "Opération annulé", "", config); + } + else + { + this.user = retour; + } + }); + } + +} diff --git a/userAndAdvertiser/src/app/user/myProfil/popup-update-user/popup-update-user.component.html b/userAndAdvertiser/src/app/user/myProfil/popup-update-user/popup-update-user.component.html new file mode 100644 index 0000000..1e583c7 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myProfil/popup-update-user/popup-update-user.component.html @@ -0,0 +1,125 @@ +
+ + +
+
+ +

+ + +
+ + +
+ + +
+
{{errorMessage}}
+
+ + +
+ + +
+ +
+ + + + + + + +
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ + Homme     + Femme + +
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ +
+
+ + + + + + + +
+ + +
+ +
+ +
+
+ +
+
diff --git a/userAndAdvertiser/src/app/user/myProfil/popup-update-user/popup-update-user.component.scss b/userAndAdvertiser/src/app/user/myProfil/popup-update-user/popup-update-user.component.scss new file mode 100644 index 0000000..636928e --- /dev/null +++ b/userAndAdvertiser/src/app/user/myProfil/popup-update-user/popup-update-user.component.scss @@ -0,0 +1,81 @@ +.myContainer { + font-size: small; +} + +button { + font-size: small; +} + +img { + margin: 0px 0px 10px 0px; + width: 5vw; + height: 5vw; + border: solid 2px black; + border-radius: 50%; + font-size: xxx-large; +} + +.inputUrlImage { + width: 80%; + text-align: center; + margin-left: 10%; + margin-right: 10%; + font-size: small; + border-radius: 5px; +} + +input { + font-size: small; +} + +// ------------------------------------------------------------------------- + +.myRow { + margin: 15px 0px 15px 0px; +} +.myLeftLabel { + text-align: right; + padding: 0px 0px 0px 0px; + margin: 0px 0px 0px 0px; + font-weight: bold; +} +.myRightLabel { + text-align: left; + padding: 0px 0px 0px 0px; + margin: 0px 0px 0px 0px; + font-weight: bold; +} +.myValue { + text-align: left; + padding: 0px 0px 0px 0px; + margin: 0px 0px 0px 0px; +} + +// ------------------------------------------------------------------------- + +// aura +::ng-deep .mat-checkbox-ripple .mat-ripple-element { + background-color: grey !important; +} + +// contenu coche +::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background { + background-color: black !important; +} + +// indeterminate +::ng-deep .mat-checkbox .mat-checkbox-frame { + background-color: white !important; +} + +// ------------------------------------------------------------------------- + +::ng-deep .mat-radio-inner-circle { + color: black !important; + background-color: black !important; +} + +::ng-deep .mat-radio-outer-circle{ + color: black !important; + border: solid 1px gray !important; +} diff --git a/userAndAdvertiser/src/app/user/myProfil/popup-update-user/popup-update-user.component.spec.ts b/userAndAdvertiser/src/app/user/myProfil/popup-update-user/popup-update-user.component.spec.ts new file mode 100644 index 0000000..a5126ad --- /dev/null +++ b/userAndAdvertiser/src/app/user/myProfil/popup-update-user/popup-update-user.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupUpdateUserComponent } from './popup-update-user.component'; + +describe('PopupUpdateUserComponent', () => { + let component: PopupUpdateUserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupUpdateUserComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupUpdateUserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/myProfil/popup-update-user/popup-update-user.component.ts b/userAndAdvertiser/src/app/user/myProfil/popup-update-user/popup-update-user.component.ts new file mode 100644 index 0000000..4c91d19 --- /dev/null +++ b/userAndAdvertiser/src/app/user/myProfil/popup-update-user/popup-update-user.component.ts @@ -0,0 +1,132 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {MessageService} from "../../../utils/message/message.service"; +import {ProfilService} from "../../../utils/profil/profil.service"; + + + +@Component({ + selector: 'app-popup-update-user', + templateUrl: './popup-update-user.component.html', + styleUrls: ['./popup-update-user.component.scss'] +}) +export class PopupUpdateUserComponent implements OnInit +{ + userCopy; + newPassword: string = ""; + confirmNewPassword: string = "" ; + changePassword: boolean = false ; + hasError: boolean = false; + errorMessage: string = "" ; + + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data, + private messageService: MessageService, + private profilService: ProfilService ) { } + + + ngOnInit(): void + { + const user0 = this.data.user; + this.userCopy = { + _id: user0._id, + login: user0.login, + hashPass: user0.hashPass, + email: user0.email, + role: { + name: user0.role.name, + permission: user0.role.permission, + isAccepted: user0.role.isAccepted, + }, + profileImageUrl: user0.profileImageUrl, + dateOfBirth: user0.dateOfBirth, + gender: user0.gender, + interests: [], + company: "", + isActive: user0.isActive, + createdAt: user0.createdAt, + updatedAt: user0.updatedAt, + lastConnexion: new Date() + }; + for(let interest of user0.interests) this.userCopy.interests.push(interest); + } + + + onValider() + { + this.checkField(); + if(!this.hasError) + { + if(this.changePassword) this.userCopy.hashPass = this.newPassword; + const data = { + login: this.userCopy.login, + hashPass: this.userCopy.hashPass, + email: this.userCopy.email, + profileImageUrl: this.userCopy.profileImageUrl, + dateOfBirth: this.userCopy.dateOfBirth, + gender: this.userCopy.gender, + interests: this.userCopy.interests, + }; + this.messageService + .put("user/update/"+this.profilService.getId(), data) + .subscribe( ret => this.onValiderCallback(ret), err => this.onValiderCallback(err) ); + } + } + + + onValiderCallback(retour: any) + { + if(retour.status !== "success") { + console.log(retour); + this.dialogRef.close(null); + } + else { + this.profilService.setProfileImageUrl(this.userCopy.profileImageUrl); + this.dialogRef.close(this.userCopy); + } + } + + + checkField() + { + if(this.userCopy.login.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'pseudo'." ; + this.hasError = true; + } + else if(this.userCopy.email.length === 0) { + this.errorMessage = "Veuillez remplir le champ 'email'." ; + this.hasError = true; + } + else if(!this.isValidEmail(this.userCopy.email)) { + this.errorMessage = "Email invalide." ; + this.hasError = true; + } + else if((this.changePassword) && (this.newPassword.length === 0)) { + this.errorMessage = "Veuillez remplir le champ 'mot de passe'" ; + this.hasError = true; + } + else if((this.changePassword) && (this.newPassword !== this.confirmNewPassword)) { + this.errorMessage = "Le mot de passe est différent de sa confirmation" ; + this.hasError = true; + } + else { + this.errorMessage = "" ; + this.hasError = false; + } + } + + + isValidEmail(email) + { + let re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return re.test(email); + } + + + onEventInputInterests(myInterets: string[]) + { + this.userCopy.interests = myInterets; + } + +} diff --git a/userAndAdvertiser/src/app/user/search/page-search/page-search.component.html b/userAndAdvertiser/src/app/user/search/page-search/page-search.component.html new file mode 100644 index 0000000..0039bdd --- /dev/null +++ b/userAndAdvertiser/src/app/user/search/page-search/page-search.component.html @@ -0,0 +1,80 @@ +
+
+ + +
+ +
+ + + + + + +
+ + +
+
+ + +
+
+ + +
+ + +   + + logo + +   + + + +   + + logo + +   + +
+ +
+ + + + + + + + + + +
+ +
+
+ + + +
+ +
+
+ + + +
+ +
+
+ +
+

+ +
+
diff --git a/userAndAdvertiser/src/app/user/search/page-search/page-search.component.scss b/userAndAdvertiser/src/app/user/search/page-search/page-search.component.scss new file mode 100644 index 0000000..f80fc45 --- /dev/null +++ b/userAndAdvertiser/src/app/user/search/page-search/page-search.component.scss @@ -0,0 +1,90 @@ +.lightTheme { + color: black; + border-color: black; +} +.darkTheme { + color: white; + border-color: white; +} +.myContainer { + text-align: center; + max-width: 100vw; + height: 100vh; + overflow-x: hidden; + overflow-y: scroll; +} + +//-------------------------------------------------------------------------------------------- + +.inputSearchBar { + width: 40%; + font-size: large; + border-bottom: 10px; + border-radius: 5px; +} + +//-------------------------------------------------------------------------------------------- + +.celluleGrilleVideo { + border: solid 2px; + border-radius: 5px; + width: 100%; +} +.lightTheme .celluleGrilleVideo{ + border-color: black; + background-color: #f0f0f0; +} +.darkTheme .celluleGrilleVideo{ + border-color: white; + background-color: #646464; +} + +.conteneurVideosGrid { + height: 75vh; + width: 100%; +} + +//-------------------------------------------------------------------------------------------- + +.cellulePub { + padding: 0px 10px 0px 10px; + width: 100%; + text-align: center; + justify-content: center; +} + +.conteneurPub { + height: 75vh; + text-align: center; + justify-content: center; + vertical-align: middle; + display: block; + width: 75%; + margin-left: auto; + margin-right: auto; + position: absolute; + top: 50%; + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} + + + +// ------------------------------------------------------------------------- + + +// aura +::ng-deep .mat-checkbox-ripple .mat-ripple-element { + background-color: grey !important; +} + +// contenu coche +::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background { + background-color: black !important; +} + +// indeterminate +::ng-deep .mat-checkbox .mat-checkbox-frame { + border: solid 1px black !important; + background-color: white !important; +} diff --git a/userAndAdvertiser/src/app/user/search/page-search/page-search.component.spec.ts b/userAndAdvertiser/src/app/user/search/page-search/page-search.component.spec.ts new file mode 100644 index 0000000..79e1a03 --- /dev/null +++ b/userAndAdvertiser/src/app/user/search/page-search/page-search.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageSearchComponent } from './page-search.component'; + +describe('PageSearchComponent', () => { + let component: PageSearchComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageSearchComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageSearchComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/search/page-search/page-search.component.ts b/userAndAdvertiser/src/app/user/search/page-search/page-search.component.ts new file mode 100644 index 0000000..60b9826 --- /dev/null +++ b/userAndAdvertiser/src/app/user/search/page-search/page-search.component.ts @@ -0,0 +1,118 @@ +import { Component, OnInit } from '@angular/core'; +import {HttpParams} from "@angular/common/http"; +import {ActivatedRoute} from "@angular/router"; +import {MessageService} from "../../../utils/message/message.service"; +import {ThemeService} from "../../../utils/theme/theme.service"; + + + +let TAB_PLATEFORM = [ + { name: "Youtube", isSelected: true }, + { name: "Dailymotion", isSelected: true } +]; + + + +@Component({ + selector: 'app-page-search', + templateUrl: './page-search.component.html', + styleUrls: ['./page-search.component.scss'] +}) +export class PageSearchComponent implements OnInit +{ + tabPlateform = TAB_PLATEFORM; + tabVideo: any[] = []; + search: string = ""; + ad1: any; + ad2: any; + sources: string = "" ; + indexPage: number = 0; + + + constructor( private messageService: MessageService, + public themeService: ThemeService, + private activatedRoute: ActivatedRoute ) { } + + + ngOnInit(): void + { + // parametre de la route + this.activatedRoute + .queryParams + .subscribe(paramsFromOldPage => { + if(paramsFromOldPage.hasOwnProperty("search")) this.search = paramsFromOldPage.search; + if(paramsFromOldPage.hasOwnProperty("sources")) + { + this.sources = paramsFromOldPage.sources; + if(this.sources === "yt") { + this.tabPlateform[0].isSelected = true; + this.tabPlateform[1].isSelected = false; + } + else if(this.sources === "dm") { + this.tabPlateform[0].isSelected = false; + this.tabPlateform[1].isSelected = true; + } + else if(this.sources === "yt,dm") { + this.tabPlateform[0].isSelected = true; + this.tabPlateform[1].isSelected = true; + } + } + if(paramsFromOldPage.hasOwnProperty("indexPage")) this.indexPage = parseInt(paramsFromOldPage.indexPage, 10); + this.onSearch(); + }); + + // Ask for ads + let params = new HttpParams(); + params = params.append("quantity", 2); + this.messageService + .get("user/ad", params) + .subscribe(ret => this.adCallback(ret), err => this.adCallback(err)); + } + + + adCallback(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + this.ad1 = retour.data[0]; + this.ad2 = retour.data[1]; + } + } + + + onSearch() + { + let params = new HttpParams(); + params = params.append('q', this.search); + + if(this.tabPlateform[0].isSelected && this.tabPlateform[1].isSelected) this.sources = "yt,dm" ; + else if((!this.tabPlateform[0].isSelected) && this.tabPlateform[1].isSelected) this.sources = "dm" ; + else if(this.tabPlateform[0].isSelected && (!this.tabPlateform[1].isSelected)) this.sources = "yt" ; + else this.sources = "" ; + params = params.append('sources', this.sources); + + this.messageService + .get("video/search", params) + .subscribe(ret => this.onSearchCallback(ret), err => this.onSearchCallback(err)); + } + + + onSearchCallback(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + this.tabVideo = retour.data; + } + } + + + onEnterOnSearchBar(event) + { + if(event.key === 'Enter') this.onSearch(); + } + +} diff --git a/userAndAdvertiser/src/app/user/search/video-grid/video-grid.component.html b/userAndAdvertiser/src/app/user/search/video-grid/video-grid.component.html new file mode 100644 index 0000000..841d5c3 --- /dev/null +++ b/userAndAdvertiser/src/app/user/search/video-grid/video-grid.component.html @@ -0,0 +1,76 @@ + + + + +
+ + +
+ + + +
+ + +
+ + + + + + + + ytb + dlm + + + + +
+ {{tronquage(tabVideo[indexPage+k].title)}} +
+ + {{tabVideo[indexPage+k].views | number: '1.0-0'}} vues. + Il y a {{dateToElapsedTime(tabVideo[indexPage+k].publishedAt)}}. + +
+
+ + + + + + +
+ + +
+
+
+
+ + + + + +
+ + +   + + + + + {{page}}  + + + {{page}}  + +   + + + + +
diff --git a/userAndAdvertiser/src/app/user/search/video-grid/video-grid.component.scss b/userAndAdvertiser/src/app/user/search/video-grid/video-grid.component.scss new file mode 100644 index 0000000..6819fd8 --- /dev/null +++ b/userAndAdvertiser/src/app/user/search/video-grid/video-grid.component.scss @@ -0,0 +1,84 @@ +mat-grid-list { + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; + border: none; +} + +mat-grid-tile { + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; +} + +.myCell { + margin: 7px 0px 0px 0px; + padding: 0px 0px 0px 0px; + background-color: white; + border: solid 1px black; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + cursor: pointer; +} +.myCell:hover { + background-color: #d2d2d2; +} + + +// --------------------------------------------------------------------------------------------- + + +.imgsContainer { + //width: 20vw; + width: 18vw; + height: 15vh; +} + +.imgPlay { + position: absolute; + margin-left: 8vw; + width: 2.5vw; + margin-top: 5vh; + height: 5vh; + padding: 0px 0px 0px 0px; +} + +.imgVideo { + width: 18vw; + height: 15vh; + padding: 0px 0px 0px 0px; +} + + +// --------------------------------------------------------------------------------------------- + + +.mat-grid-list-info-video { + margin: 0px 0px 0px 0px; + font-size: small; +} + +.mat-grid-tile-info-video { + border: none; + font-size: x-small; + //background: linear-gradient(top, rgba(38,38,38,0.8), #e6e6e6 25%, #fff 38%, #c5c5c5 87%, rgba(38,38,38,0.8)); + //background: -webkit-linear-gradient(top, #c5c5c5, #e6e6e6 25%, #fff 38%, #c5c5c5 87%, #c5c5c5); +} + +mat-icon { + text-align: right; +} + + +// --------------------------------------------------------------------------------------------- + + +.paginatorContainer { + margin: 0px 0px 0px 0px; + padding: 5px 0px 0px 0px; + text-align: center; + background-color: white; +} + +.btnPaginator { + margin: 0px 0px 0px 0px; + padding: 0px 0px 0px 0px; +} diff --git a/userAndAdvertiser/src/app/user/search/video-grid/video-grid.component.spec.ts b/userAndAdvertiser/src/app/user/search/video-grid/video-grid.component.spec.ts new file mode 100644 index 0000000..17cea62 --- /dev/null +++ b/userAndAdvertiser/src/app/user/search/video-grid/video-grid.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VideoGridComponent } from './video-grid.component'; + +describe('VideoGridComponent', () => { + let component: VideoGridComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ VideoGridComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(VideoGridComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/search/video-grid/video-grid.component.ts b/userAndAdvertiser/src/app/user/search/video-grid/video-grid.component.ts new file mode 100644 index 0000000..11dea65 --- /dev/null +++ b/userAndAdvertiser/src/app/user/search/video-grid/video-grid.component.ts @@ -0,0 +1,128 @@ +import {Component, Input, OnChanges, SimpleChanges} from '@angular/core'; +import {AddVideoToPlaylistsService} from "../../utils/services/addVideoToPlaylists/add-video-to-playlists.service"; +import {Router} from "@angular/router"; +import {MessageService} from "../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-video-grid', + templateUrl: './video-grid.component.html', + styleUrls: ['./video-grid.component.scss'] +}) +export class VideoGridComponent implements OnChanges +{ + @Input() tabVideo: any[] = []; + @Input() search: string = ""; + @Input() sources: string = ""; + @Input() indexPage: number = 0; + tabPage: number[] = []; + + + constructor( private addVideoToPlaylistsService: AddVideoToPlaylistsService, + private router: Router, + private messageService: MessageService ) {} + + + ngOnChanges(changes: SimpleChanges): void + { + if(this.tabVideoExists()) + { + const nbVideo = this.tabVideo.length; + let nbPage = Math.floor(nbVideo/9); + if((nbVideo%9) !== 0) nbPage += 1; + this.tabPage = []; + for(let i=1 ; i<=nbPage ; i++) this.tabPage.push(i); + } + } + + + onAddToPlaylist(video): void + { + this.addVideoToPlaylistsService.run(video.videoId, video.source, video.interest); + } + + + tronquage(str: string) + { + if(str.length < 30) return str; + else return str.substring(0, 27) + "..." ; + } + + + onVideo(video): void + { + const data = { source: video.source, interest: video.interest }; + this.messageService + .post("video/create/"+video.videoId, data) + .subscribe(ret => this.onVideoCallback(ret,video), err => this.onVideoCallback(err,video)); + } + + + onVideoCallback(retour: any, video): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + const params = { + videoId: video.videoId, + source: video.source, + from: "search", + search: this.search, + sources: this.sources, + indexPage: this.indexPage + }; + this.router.navigate(['/user/watching'], { queryParams: params }); + } + } + + + dateToElapsedTime(date0): string + { + const ellapsedTimeInMilliSeconds = (new Date()).getTime() - (new Date(date0)).getTime(); + + // seconde + const ellapsedTimeInSeconds = Math.trunc(ellapsedTimeInMilliSeconds / 1000); + if(ellapsedTimeInSeconds < 60) { + if(ellapsedTimeInSeconds <= 1)return ellapsedTimeInSeconds + " seconde" ; + else return ellapsedTimeInSeconds + " secondes" ; + } + // minute + const ellapsedTimeInMinutes = Math.trunc(ellapsedTimeInSeconds / 60); + if(ellapsedTimeInMinutes < 60) { + if(ellapsedTimeInMinutes <= 1) return ellapsedTimeInMinutes + " minute" ; + else return ellapsedTimeInMinutes + " minutes" ; + } + // heure + const ellapsedTimeInHours = Math.trunc(ellapsedTimeInMinutes / 60); + if(ellapsedTimeInHours < 24) { + if(ellapsedTimeInHours <= 1) return ellapsedTimeInHours + " heure" ; + else return ellapsedTimeInHours + " heures" ; + } + // jour + const ellapsedTimeInDays = Math.trunc(ellapsedTimeInHours / 24); + if(ellapsedTimeInDays < 31) { + if(ellapsedTimeInDays <= 1) return ellapsedTimeInDays + " jour" ; + else return ellapsedTimeInDays + " jours" ; + } + // mois + const ellapsedTimeInMonths = Math.trunc(ellapsedTimeInDays / 31); + if(ellapsedTimeInMonths < 12) { + return ellapsedTimeInMonths + " mois" ; + } + // an + const ellapsedTimeInYears = Math.trunc(ellapsedTimeInMonths / 12); + if(ellapsedTimeInYears <= 1) return ellapsedTimeInYears + " an" ; + else return ellapsedTimeInYears + " ans" ; + } + + + tabVideoExists(): boolean + { + if((this.tabVideo === null) || (this.tabVideo === undefined)) return false; + else if(this.tabVideo.length === 0) return false; + else return true; + } + +} diff --git a/userAndAdvertiser/src/app/user/utils/components/advert/advert.component.html b/userAndAdvertiser/src/app/user/utils/components/advert/advert.component.html new file mode 100644 index 0000000..b1c034a --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/components/advert/advert.component.html @@ -0,0 +1,26 @@ +
+ + + + +
+ + + + + + + + diff --git a/userAndAdvertiser/src/app/user/utils/components/advert/advert.component.scss b/userAndAdvertiser/src/app/user/utils/components/advert/advert.component.scss new file mode 100644 index 0000000..ab03155 --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/components/advert/advert.component.scss @@ -0,0 +1,41 @@ +.myContainer { + margin: 0; + position: absolute; + top: 50%; + -ms-transform: translateY(-50%); + transform: translateY(-50%); +} + +#imgFromSearchOrMyPlaylists { + max-width: 100%; + max-height: 100%; + border: solid 3px black; + vertical-align: middle; + cursor: pointer; +} + +.helper { + display: inline-block; + height: 100%; + vertical-align: middle; +} + +// ------------------------------------------------------------------------------ + +#imgFromWatchingLeft { + width: 14vw; + height: 70vh; + border: solid 3px black; + position: fixed; + left: 1vw; + cursor: pointer; +} + +#imgFromWatchingRight { + width: 15vw; + height: 70vh; + border: solid 3px black; + position: fixed; + right: 1vw; + cursor: pointer; +} diff --git a/userAndAdvertiser/src/app/user/utils/components/advert/advert.component.spec.ts b/userAndAdvertiser/src/app/user/utils/components/advert/advert.component.spec.ts new file mode 100644 index 0000000..08b7e86 --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/components/advert/advert.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdvertComponent } from './advert.component'; + +describe('PubComponent', () => { + let component: AdvertComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdvertComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AdvertComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/utils/components/advert/advert.component.ts b/userAndAdvertiser/src/app/user/utils/components/advert/advert.component.ts new file mode 100644 index 0000000..00b8b44 --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/components/advert/advert.component.ts @@ -0,0 +1,40 @@ +import {Component, Input, OnChanges, SimpleChanges} from '@angular/core'; + + + +@Component({ + selector: 'app-advert', + templateUrl: './advert.component.html', + styleUrls: ['./advert.component.scss'] +}) +export class AdvertComponent implements OnChanges +{ + @Input() ad: any; + @Input() from: string = "search"; + image: any; + imageExist: boolean = false; + + + constructor() { } + + + ngOnChanges(changes: SimpleChanges): void + { + if((this.ad !== null) && (this.ad !== undefined)) + { + const nbImages = this.ad.images.length; + const indexImage = Math.floor(Math.random() * nbImages); + this.image = this.ad.images[indexImage]; + this.imageExist = true; + } + } + + + onClick(): void + { + if((this.ad.url !== "") && (this.ad.url !== null) && (this.ad.url !== undefined)) { + document.location.href = this.ad.url; + } + } + +} diff --git a/userAndAdvertiser/src/app/user/utils/components/navbar-user/navbar-user.component.html b/userAndAdvertiser/src/app/user/utils/components/navbar-user/navbar-user.component.html new file mode 100644 index 0000000..605e192 --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/components/navbar-user/navbar-user.component.html @@ -0,0 +1,41 @@ + diff --git a/userAndAdvertiser/src/app/user/utils/components/navbar-user/navbar-user.component.scss b/userAndAdvertiser/src/app/user/utils/components/navbar-user/navbar-user.component.scss new file mode 100644 index 0000000..285d629 --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/components/navbar-user/navbar-user.component.scss @@ -0,0 +1,80 @@ +.navbar { + background-color: black; + height: 60px; + font-size: medium; + color: white; +} + + +.navbar-expand-lg { + border-bottom: solid; + border-color: white; + border-bottom-width: 2px; +} + + +// PolyNotFound +.navbar-brand { + font-family: cursive; + font-weight: bold; + font-size: x-large; + margin-left: 15px; + color: white; +} + + +.monLi { + margin: 0px 10px 0px 10px; +} + + +.nav-link { + color: white; +} +.nav-link:hover { + color: grey; +} +.myActiveLink { + text-decoration: underline; +} + + +.btnDeconnexion { + font-size: medium; + margin: 0px 10px 0px 10px +} +.btnDeconnexion:hover { + color: grey; +} + + +img { + border: solid 2px white; + border-radius: 50px; + margin: 0px 10px 0px 15px; + width: 40px; + height: 40px; +} +img:hover { + cursor: pointer; +} + + +// -------------------------------------------------------------------- + + +::ng-deep .mat-slide-toggle-thumb { + background-color: #c8c8c8; +} + +::ng-deep .mat-slide-toggle-bar { + background-color: #ffffff; +} + +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-thumb { + background-color: #ffffff; +} + +::ng-deep .mat-slide-toggle.mat-checked:not(.mat-disabled) .mat-slide-toggle-bar { + background-color: #646464; +} diff --git a/userAndAdvertiser/src/app/user/utils/components/navbar-user/navbar-user.component.spec.ts b/userAndAdvertiser/src/app/user/utils/components/navbar-user/navbar-user.component.spec.ts new file mode 100644 index 0000000..5d03960 --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/components/navbar-user/navbar-user.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NavbarUserComponent } from './navbar-user.component'; + +describe('NavbarUserComponent', () => { + let component: NavbarUserComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ NavbarUserComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NavbarUserComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/utils/components/navbar-user/navbar-user.component.ts b/userAndAdvertiser/src/app/user/utils/components/navbar-user/navbar-user.component.ts new file mode 100644 index 0000000..ec8576e --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/components/navbar-user/navbar-user.component.ts @@ -0,0 +1,41 @@ +import {Component} from '@angular/core'; +import {Router} from "@angular/router"; +import {ProfilService} from "../../../../utils/profil/profil.service"; +import {MessageService} from "../../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-navbar-user', + templateUrl: './navbar-user.component.html', + styleUrls: ['./navbar-user.component.scss'] +}) +export class NavbarUserComponent +{ + routes: string[] = [ + "/user", // 0 + "/user/search", // 1 + "/user/myPlaylists", // 2 + "/user/history", // 3 + "/user/myProfil" // 4 + ]; + + url = this.router.url; + + constructor( private router: Router, + public profilService: ProfilService, + private messageService: MessageService ) { } + + onDeconnexion(): void + { + this.messageService + .delete('user/logout') + .subscribe(retour => this.onDeconnexionCallback(retour), err => this.onDeconnexionCallback(err)); + } + + onDeconnexionCallback(retour: any): void + { + if(retour.status !== "success") console.log(retour); + } + +} diff --git a/userAndAdvertiser/src/app/user/utils/components/popup-add-video-to-playlists/popup-add-video-to-playlists.component.html b/userAndAdvertiser/src/app/user/utils/components/popup-add-video-to-playlists/popup-add-video-to-playlists.component.html new file mode 100644 index 0000000..115b281 --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/components/popup-add-video-to-playlists/popup-add-video-to-playlists.component.html @@ -0,0 +1,42 @@ +

Ajouter dans

+ + + + + +
+
+ {{playlist.name}} +
+
+ + + + + +
+ +
+ +
+ + Nom playlist + + + +
+ + + + + + {{errorMessage}} + + + + diff --git a/userAndAdvertiser/src/app/user/utils/components/popup-add-video-to-playlists/popup-add-video-to-playlists.component.scss b/userAndAdvertiser/src/app/user/utils/components/popup-add-video-to-playlists/popup-add-video-to-playlists.component.scss new file mode 100644 index 0000000..a6f9d32 --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/components/popup-add-video-to-playlists/popup-add-video-to-playlists.component.scss @@ -0,0 +1,39 @@ +h3 { + text-align: center; + margin-bottom: 10px +} + +.conteneurPlaylists { + margin-top: 10px; + margin-bottom: 10px; +} + +.conteneurBtnCreerPlaylist { + margin-top: 10px; + margin-bottom: 10px; +} + +.conteneurInputNewPlaylist { + margin-top: 10px; + margin-bottom: 10px; +} + + +// ------------------------------------------------------------------------- + + +// aura +::ng-deep .mat-checkbox-ripple .mat-ripple-element { + background-color: grey !important; +} + +// contenu coche +::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background { + background-color: black !important; +} + +// indeterminate +::ng-deep .mat-checkbox .mat-checkbox-frame { + border-color: black !important; + background-color: white !important; +} diff --git a/userAndAdvertiser/src/app/user/utils/components/popup-add-video-to-playlists/popup-add-video-to-playlists.component.spec.ts b/userAndAdvertiser/src/app/user/utils/components/popup-add-video-to-playlists/popup-add-video-to-playlists.component.spec.ts new file mode 100644 index 0000000..5ace846 --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/components/popup-add-video-to-playlists/popup-add-video-to-playlists.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PopupAddVideoToPlaylistsComponent } from './popup-add-video-to-playlists.component'; + +describe('PopupAddVideoToPlaylistsComponent', () => { + let component: PopupAddVideoToPlaylistsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PopupAddVideoToPlaylistsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PopupAddVideoToPlaylistsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/utils/components/popup-add-video-to-playlists/popup-add-video-to-playlists.component.ts b/userAndAdvertiser/src/app/user/utils/components/popup-add-video-to-playlists/popup-add-video-to-playlists.component.ts new file mode 100644 index 0000000..cfa0119 --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/components/popup-add-video-to-playlists/popup-add-video-to-playlists.component.ts @@ -0,0 +1,141 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {MessageService} from "../../../../utils/message/message.service"; + + + +@Component({ + selector: 'app-popup-add-video-to-playlists', + templateUrl: './popup-add-video-to-playlists.component.html', + styleUrls: ['./popup-add-video-to-playlists.component.scss'] +}) +export class PopupAddVideoToPlaylistsComponent implements OnInit +{ + _idVideo: string = ""; + videoId: string = ""; + source: string = ""; + interest: string = ""; + + tabPlaylistAndBool = []; + + goToCreatePlaylist = false; + newPlaylistName = ""; + hasError: boolean = false; + tabNomPlaylist: string[] = []; + errorMessage: string = "" ; + + + constructor( public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data, + private messageService: MessageService) { } + + + ngOnInit(): void + { + this._idVideo = this.data._idVideo; + this.videoId = this.data.videoId; + this.source = this.data.source; + this.interest = this.data.interest; + + for(let playlist of this.data.playlists) + { + if(playlist.videoIds.includes(this._idVideo)) playlist["isSelected"] = true; + else playlist["isSelected"] = false; + this.tabPlaylistAndBool.push(playlist); + this.tabNomPlaylist.push(playlist.name); + } + } + + + onValider(): void + { + this.checkError(); + if(!this.hasError) + { + // --- Existing playlists --- + let listeDesPlaylistsSelected = "" ; + let listeDesPlaylistsNotSelected = "" ; + for(let playlist of this.tabPlaylistAndBool) + { + if(playlist.isSelected) listeDesPlaylistsSelected += playlist.id + "," ; + else listeDesPlaylistsNotSelected += playlist.id + "," ; + } + if(listeDesPlaylistsSelected.endsWith(",")) listeDesPlaylistsSelected = listeDesPlaylistsSelected.slice(0, listeDesPlaylistsSelected.length-1); + if(listeDesPlaylistsNotSelected.endsWith(",")) listeDesPlaylistsNotSelected = listeDesPlaylistsNotSelected.slice(0, listeDesPlaylistsNotSelected.length-1); + + if(listeDesPlaylistsSelected !== "") + { + const data1 = { videoId: { id: this._idVideo, action: "add" } }; + this.messageService + .put( "playlist/update/"+listeDesPlaylistsSelected, data1) + .subscribe( ret => this.callbackForExistingPlaylists(ret), err => this.callbackForExistingPlaylists(err)); + } + if(listeDesPlaylistsNotSelected !== "") + { + const data2 = { videoId: { id: this._idVideo, action: "delete" } }; + this.messageService + .put( "playlist/update/"+listeDesPlaylistsNotSelected, data2) + .subscribe( ret => this.callbackForExistingPlaylists(ret), err => this.callbackForExistingPlaylists(err)); + } + + + // --- New playlists --- + if(this.goToCreatePlaylist) + { + const data3 = { + name: this.newPlaylistName, + video: {videoId: this.videoId, interest: this.interest, source: this.source} + }; + this.messageService + .post("playlist/create", data3) + .subscribe( ret => this.callbackForNewPlaylist(ret), err => this.callbackForNewPlaylist(err)); + } + + + // --- Finalement --- + this.dialogRef.close("success"); + } + } + + + callbackForExistingPlaylists(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + this.dialogRef.close(null); + } + } + + + callbackForNewPlaylist(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + this.dialogRef.close(null); + } + } + + + onAnnuler(): void + { + this.dialogRef.close("annulation"); + } + + + checkError(): void + { + if(this.goToCreatePlaylist && (this.newPlaylistName === "")) { + this.errorMessage = "Le nom ne peut pas être vide" ; + this.hasError = true; + } + else if(this.goToCreatePlaylist && this.tabNomPlaylist.includes(this.newPlaylistName)){ + this.errorMessage = "Ce nom est déjà utilisé" ; + this.hasError = true; + } + else { + this.hasError = false; + this.errorMessage = "" ; + } + } + +} diff --git a/userAndAdvertiser/src/app/user/utils/services/addVideoToPlaylists/add-video-to-playlists.service.spec.ts b/userAndAdvertiser/src/app/user/utils/services/addVideoToPlaylists/add-video-to-playlists.service.spec.ts new file mode 100644 index 0000000..6097218 --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/services/addVideoToPlaylists/add-video-to-playlists.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { AddVideoToPlaylistsService } from './add-video-to-playlists.service'; + +describe('PlaylistService', () => { + let service: AddVideoToPlaylistsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(AddVideoToPlaylistsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/utils/services/addVideoToPlaylists/add-video-to-playlists.service.ts b/userAndAdvertiser/src/app/user/utils/services/addVideoToPlaylists/add-video-to-playlists.service.ts new file mode 100644 index 0000000..e9df3c4 --- /dev/null +++ b/userAndAdvertiser/src/app/user/utils/services/addVideoToPlaylists/add-video-to-playlists.service.ts @@ -0,0 +1,100 @@ +import { Injectable } from '@angular/core'; +import {MatDialog} from "@angular/material/dialog"; +import {PopupAddVideoToPlaylistsComponent} from "../../components/popup-add-video-to-playlists/popup-add-video-to-playlists.component"; +import {MatSnackBar} from "@angular/material/snack-bar"; +import {MessageService} from "../../../../utils/message/message.service"; + + + +@Injectable({ + providedIn: 'root' +}) +export class AddVideoToPlaylistsService +{ + private _idVideo: string = "" ; + private videoId: string = "" ; + private source: string = "" ; + private interest: string = "" ; + + + constructor( private messageService: MessageService, + public dialog: MatDialog, + private snackBar: MatSnackBar ) { } + + + run(videoId: string, source: string, interest: string): void + { + this.videoId = videoId; + this.source = source; + this.interest = interest; + + const data = { source: this.source, interest: this.interest }; + this.messageService + .post("video/create/"+this.videoId, data) + .subscribe(ret => this.afterCreatingVideo(ret), err => this.afterCreatingVideo(err)); + } + + + private afterCreatingVideo(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else { + this._idVideo = retour.data.id; + this.messageService + .get('playlist/findAll') + .subscribe( ret => this.afterReceivingPlaylists(ret), ret => this.afterReceivingPlaylists(ret) ); + } + } + + + + private afterReceivingPlaylists(retour: any): void + { + if(retour.status !== "success") { + console.log(retour); + } + else + { + const config = { + width: '30%', + data: { + _idVideo: this._idVideo, + videoId: this.videoId, + source: this.source, + interest: this.interest, + playlists: retour.data.filter(x => x.isActive === true) + } + }; + this.dialog + .open(PopupAddVideoToPlaylistsComponent, config) + .afterClosed() + .subscribe(retour => this.afterClosingDialog(retour)); + } + } + + + + private afterClosingDialog(retour): void + { + let message = "" ; + switch (retour) + { + case "error": + message = "Echec de l'opération ❌" ; + break; + case "success": + message = "La vidéo a bien été ajoutée ✔" ; + break; + case "annulation": + case null: + case undefined: + message = "Opération annulée" ; + break; + } + const config = { duration: 1000, panelClass: "custom-class" }; + this.snackBar.open( message, "", config); + } + +} diff --git a/userAndAdvertiser/src/app/user/watching/page-watching-video/page-watching-video.component.html b/userAndAdvertiser/src/app/user/watching/page-watching-video/page-watching-video.component.html new file mode 100644 index 0000000..9cf8392 --- /dev/null +++ b/userAndAdvertiser/src/app/user/watching/page-watching-video/page-watching-video.component.html @@ -0,0 +1,233 @@ + + + +
+
+ + +
+ +
+ + + + +
+ + +
+
+ + +
+
+ + +
+ + +   + + logo + +   + + + +   + + logo + +   + +
+ +
+ + + + +
+ + +
+ + + +

+ +
+
+ + + + + + + +
+ + +
+ +
+ + +
+
+
+ + +
+ +
+ +
+
+ + + + + + + +
+ + +
+ +
+ + +
+
+
+ + +
+
+
+ +
+
+ + + + + + + + + +
+ + +
+ +
+ + +
+ +
+ +
+ + + +
+ + +
+ ytb + dlm +
  {{video.title}}
+
+ + +
+ +
+ + +
+ Vues: + {{video.views | number: '1.0-0'}} +
+ + +
+ Date de publication: + {{ video.publishedAt | date:'dd/LL/YYYY à HH:mm:ss' }} +
+ + +
+
+ Description + expand_more + expand_less +
+
+ {{video.description}} +
+
+ +
+ +
+ + + + + + + +
+ + +
+ {{playlist.name}} +
+ + + +
+
+
+
+ + +
+
+
{{video0.title}}
+
+ + {{video.views | number: '1.0-0'}} vues +
+ + Publiée le {{ video.publishedAt | date:'dd/LL/YYYY à HH:mm:ss' }} + +
+
+
+
+ + + + + +
+
diff --git a/userAndAdvertiser/src/app/user/watching/page-watching-video/page-watching-video.component.scss b/userAndAdvertiser/src/app/user/watching/page-watching-video/page-watching-video.component.scss new file mode 100644 index 0000000..d3f10e8 --- /dev/null +++ b/userAndAdvertiser/src/app/user/watching/page-watching-video/page-watching-video.component.scss @@ -0,0 +1,159 @@ +.lightTheme { + color: black; + border-color: black; +} +.darkTheme { + color: white; + border-color: white; +} +.myContainer { + text-align: center; + max-width: 100vw; + height: 100vh; + overflow-x: hidden; + overflow-y: scroll; +} + +//-------------------------------------------------------------------------------------------- + +.inputSearchBar { + width: 40%; + font-size: large; + border-bottom: 10px; + border-radius: 5px; +} + +//-------------------------------------------------------------------------------------------- +// --- Playlist --- + +.playlistContainer { + border: solid 1px black; + width: 98%; + border-top-right-radius: 10px; + border-top-left-radius: 10px; +} + +.topBorder { + margin: 0px 0px 0px 0px; + //background-color: #dcdcdc; + background: linear-gradient(top, rgba(38,38,38,0.8), #e6e6e6 25%, #fff 38%, #c5c5c5 87%, rgba(38,38,38,0.8)); + background: -webkit-linear-gradient(top, #c5c5c5, #e6e6e6 25%, #fff 38%, #c5c5c5 87%, #c5c5c5); + text-align: left; + padding: 5px 0px 5px 5px; + border-bottom: solid 1px black; + border-top-right-radius: 10px; + border-top-left-radius: 10px; +} + +.listVideoContainer { + height: 70vh; + background-color: white; + overflow-y: scroll; +} + +.videoCell { + margin: 0px 0px 0px 0px; + padding: 10px 0px 10px 10px; + border-bottom: solid 1px black; + cursor: pointer; +} +.videoCell:hover { + background-color: #f0f0f0; +} + +.videoCellFocus { + background-color: #e6e6e6; +} +.videoCellFocus:hover { + background-color: #e6e6e6; +} + + +// ---- + +.imgsContainer { + position: relative; + width: 13vw; + height: 10vh; + float: left; +} + +.imgVideo { + border: solid 1px black; + width: 13vw; + height: 10vh; + padding: 0px 0px 0px 0px; +} + +.imgPlay { + position: absolute; + margin-left: 6vw; + width: 2vw; + margin-top: 3vh; + height: 4vh; + padding: 0px 0px 0px 0px; +} + +// ---- + +.infoContainer { + display: table-cell; + margin-left: 13vw; + height: 10vh; + padding-left: 5px; + vertical-align: middle; +} + +.titleContainer { + font-weight: bold; + text-align: left; +} + +.viewsContainer { + text-align: left; + display: flex; + align-items: center; + font-size: x-small; + color: grey; +} +mat-icon { + vertical-align: middle; + //font-size: x-small; +} + +.publishedAtContainer { + text-align: left; + font-size: x-small; + color: grey; +} + + +// ---- + +.bottomBorder { + margin: 0px 0px 0px 0px; + background-color: #dcdcdc; + border-top: solid 1px black; + border-bottom: solid 1px black; + font-size: large; +} + + +// ------------------------------------------------------------------------- + + +// aura +::ng-deep .mat-checkbox-ripple .mat-ripple-element { + background-color: grey !important; +} + +// contenu coche +::ng-deep .mat-checkbox-checked.mat-accent .mat-checkbox-background { + background-color: black !important; +} + +// indeterminate +::ng-deep .mat-checkbox .mat-checkbox-frame { + border: solid 1px black !important; + background-color: white !important; +} diff --git a/userAndAdvertiser/src/app/user/watching/page-watching-video/page-watching-video.component.spec.ts b/userAndAdvertiser/src/app/user/watching/page-watching-video/page-watching-video.component.spec.ts new file mode 100644 index 0000000..6790456 --- /dev/null +++ b/userAndAdvertiser/src/app/user/watching/page-watching-video/page-watching-video.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageWatchingVideoComponent } from './page-watching-video.component'; + +describe('PageWatchingVideoComponent', () => { + let component: PageWatchingVideoComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageWatchingVideoComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageWatchingVideoComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/user/watching/page-watching-video/page-watching-video.component.ts b/userAndAdvertiser/src/app/user/watching/page-watching-video/page-watching-video.component.ts new file mode 100644 index 0000000..4115a7a --- /dev/null +++ b/userAndAdvertiser/src/app/user/watching/page-watching-video/page-watching-video.component.ts @@ -0,0 +1,258 @@ +import { Component, OnInit } from '@angular/core'; +import {ActivatedRoute, Router} from "@angular/router"; +import {AddVideoToPlaylistsService} from "../../utils/services/addVideoToPlaylists/add-video-to-playlists.service"; +import {DomSanitizer, SafeResourceUrl} from "@angular/platform-browser"; +import {HttpParams} from "@angular/common/http"; +import {MessageService} from "../../../utils/message/message.service"; +import {ThemeService} from "../../../utils/theme/theme.service"; + + + +let TAB_PLATEFORM = [ + { name: "youtube", isSelected: false }, + { name: "dailymotion", isSelected: false } +]; + + + +@Component({ + selector: 'app-page-watching-video', + templateUrl: './page-watching-video.component.html', + styleUrls: ['./page-watching-video.component.scss'] +}) +export class PageWatchingVideoComponent implements OnInit +{ + tabPlateform = TAB_PLATEFORM; + sources: string = ""; + video = { + title: "", + videoId: "", + views: 0, + publishedAt: null, + description: "", + source: "", + interest: "" + }; + search: string = ""; + + ad1: any; + ad2: any; + from: string = ""; + + playlist: any; + videosInPlaylist: any[] = []; + + paramsFromOldPage ; + + hiddenDescription: boolean = true; + iframeStyle: string = ""; + containerStyle: string = ""; + + + constructor( private messageService: MessageService, + public themeService: ThemeService, + private activatedRoute: ActivatedRoute, + private router: Router, + private _sanitizer: DomSanitizer, + private addVideoToPlaylistsService: AddVideoToPlaylistsService ) { } + + + + ngOnInit(): void + { + // Ask for videos + this.activatedRoute + .queryParams + .subscribe(paramsFromOldPage => { + + this.paramsFromOldPage = paramsFromOldPage; + const videoId = paramsFromOldPage.videoId; + let source = paramsFromOldPage.source; + + let params = new HttpParams(); + if(source === "Youtube") source = "yt"; + else if(source === "Dailymotion") source = "dm" ; + params = params.append("source", source); + this.messageService + .get("video/get/"+videoId, params) + .subscribe(ret => this.findVideoCallback(ret), err => this.findVideoCallback(err)); + }); + + + // Ask for adverts + let params = new HttpParams(); + params = params.append("quantity", 2); + this.messageService + .get("user/ad", params) + .subscribe(ret => this.findAdCallback(ret), err => this.findAdCallback(err)); + + + // Si on vient de la page "search" + if(this.router.url.includes("search")) + { + this.from = "search" ; + this.activatedRoute + .queryParams + .subscribe(paramsFromOldPage => { + if(paramsFromOldPage.hasOwnProperty("search")) this.search = paramsFromOldPage.search; + if(paramsFromOldPage.hasOwnProperty("sources")) { + this.sources = paramsFromOldPage.sources; + if(this.sources === "yt") { + this.tabPlateform[0].isSelected = true; + this.tabPlateform[1].isSelected = false; + } + else if(this.sources === "dm") { + this.tabPlateform[0].isSelected = false; + this.tabPlateform[1].isSelected = true; + } + else if(this.sources === "yt,dm") { + this.tabPlateform[0].isSelected = true; + this.tabPlateform[1].isSelected = true; + } + } + }); + } + // si on vient de la page "myPlaylists" + else if(this.router.url.includes("myPlaylists")) + { + this.from = "myPlaylists"; + this.activatedRoute + .queryParams + .subscribe(paramsFromOldPage => { + const _idPlaylist = paramsFromOldPage._idPlaylist; + this.messageService + .get("playlist/findOne/"+_idPlaylist) + .subscribe(ret => this.afterReceivingPlaylistWithVideo(ret), err => this.afterReceivingPlaylistWithVideo(err)); + }); + + } + // si on vient de la page "history" + else if(this.router.url.includes("history")) this.from = "history"; + + + // style + if(this.from === 'search' || this.from === 'history') { + this.containerStyle = "margin: 0 auto; width: 64vw;" ; + this.iframeStyle = "width: 64vw; height: 60vh;" ; + } + else { + this.containerStyle = "margin: 0 auto; width: 48vw;" ; + this.iframeStyle = "width: 48vw; height: 45vh;" ; + } + } + + + + findVideoCallback(retour: any): void + { + if(retour.status !== "success") { + console.log("findVideoCallback: "); + console.log(retour); + } + else { + this.video = retour.data; + } + } + + + findAdCallback(retour: any): void + { + if(retour.status !== "success") { + console.log("findAdCallback: "); + console.log(retour); + } + else { + this.ad1 = retour.data[0]; + this.ad2 = retour.data[1]; + } + } + + + afterReceivingPlaylistWithVideo(retour: any): void + { + if(retour.status !== "success") { + console.log("afterReceivingPlaylistWithVideo"); + console.log(retour); + } + else { + this.playlist = retour.data; + this.videosInPlaylist = retour.data.videos; + } + } + + + onSearch() + { + if(this.tabPlateform[0].isSelected && this.tabPlateform[1].isSelected) this.sources = "yt,dm" ; + else if((!this.tabPlateform[0].isSelected) && this.tabPlateform[1].isSelected) this.sources = "dm" ; + else if(this.tabPlateform[0].isSelected && (!this.tabPlateform[1].isSelected)) this.sources = "yt" ; + else this.sources = "" ; + let options = { + queryParams: { + search: this.search, + sources: this.sources, + indexPage: 0, + } + }; + this.router.navigate(['/user/search'], options); + } + + + onAddToPlaylist(): void + { + this.addVideoToPlaylistsService.run(this.video.videoId, this.video.source, this.video.interest); + } + + + onRetour(): void + { + let url: string[] = []; + let options = {}; + + if(this.from === 'search') + { + url = ['/user/search']; + options = { + queryParams: { + search: this.paramsFromOldPage.search, + sources: this.paramsFromOldPage.sources, + indexPage: this.paramsFromOldPage.indexPage, + } + }; + } + else if(this.from === 'myPlaylists') url = ["/user/myPlaylists"]; + else if(this.from === 'history') url = ["/user/history"]; + + this.router.navigate(url, options); + } + + + safeUrl(videoId: string, source: string): SafeResourceUrl + { + let videoUrl = "" ; + if(source === 'Youtube') videoUrl = "https://www.youtube.com/embed/" + videoId + "?autoplay=1"; + else if(source === 'Dailymotion') videoUrl = "https://www.dailymotion.com/embed/video/" + videoId + "?autoplay=true"; + return this._sanitizer.bypassSecurityTrustResourceUrl(videoUrl); + } + + + // retourne la classe CSS de videoCell + getClassOfVideoCell(video0): string + { + if(video0 === this.video) return "videoCell videoCellFocus" ; + else return "videoCell" ; + } + + + onEnterOnSearchBar(event) + { + if(event.key === 'Enter') this.onSearch(); + } + + + playlistExists(): boolean + { + return ((this.playlist !== null) && (this.playlist !== undefined)); + } + +} diff --git a/userAndAdvertiser/src/app/utils/message/message.service.spec.ts b/userAndAdvertiser/src/app/utils/message/message.service.spec.ts new file mode 100644 index 0000000..1db761b --- /dev/null +++ b/userAndAdvertiser/src/app/utils/message/message.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { MessageService } from './message.service'; + +describe('MessageService', () => { + let service: MessageService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(MessageService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/utils/message/message.service.ts b/userAndAdvertiser/src/app/utils/message/message.service.ts new file mode 100644 index 0000000..c20d5b1 --- /dev/null +++ b/userAndAdvertiser/src/app/utils/message/message.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import {HttpClient, HttpParams} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {environment} from "../../../environments/environment"; + +@Injectable({ + providedIn: 'root' +}) +export class MessageService +{ + + constructor( private http: HttpClient ) { } + + post(url: string, data: any): Observable + { + const urlComplete = environment.debutUrl + url ; + return this.http.post(urlComplete, data, {withCredentials: true}); + } + + get(url: string, params:HttpParams = new HttpParams()): Observable + { + const urlComplete = environment.debutUrl + url ; + return this.http.get(urlComplete,{ withCredentials: true, params: params }); + } + + put(url: string, data: any): Observable + { + const urlComplete = environment.debutUrl + url ; + return this.http.put(urlComplete, data, {withCredentials: true}); + } + + delete(url: string): Observable + { + const urlComplete = environment.debutUrl + url ; + return this.http.delete(urlComplete,{withCredentials: true}); + } + +} diff --git a/userAndAdvertiser/src/app/utils/profil/profil.service.spec.ts b/userAndAdvertiser/src/app/utils/profil/profil.service.spec.ts new file mode 100644 index 0000000..5cee000 --- /dev/null +++ b/userAndAdvertiser/src/app/utils/profil/profil.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ProfilService } from './profil.service'; + +describe('ProfilService', () => { + let service: ProfilService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ProfilService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/utils/profil/profil.service.ts b/userAndAdvertiser/src/app/utils/profil/profil.service.ts new file mode 100644 index 0000000..4bbe5ea --- /dev/null +++ b/userAndAdvertiser/src/app/utils/profil/profil.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class ProfilService +{ + + getId(): string + { + return localStorage.getItem('id'); + } + + getProfileImageUrl(): string + { + return localStorage.getItem('profileImageUrl'); + } + + setId(id: string): void + { + localStorage.setItem('id', id); + } + + setProfileImageUrl(profileImageUrl: string): void + { + localStorage.setItem('profileImageUrl', profileImageUrl); + } + +} diff --git a/userAndAdvertiser/src/app/utils/theme/theme.service.spec.ts b/userAndAdvertiser/src/app/utils/theme/theme.service.spec.ts new file mode 100644 index 0000000..1c2957b --- /dev/null +++ b/userAndAdvertiser/src/app/utils/theme/theme.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { ThemeService } from './theme.service'; + +describe('ThemeService', () => { + let service: ThemeService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(ThemeService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/userAndAdvertiser/src/app/utils/theme/theme.service.ts b/userAndAdvertiser/src/app/utils/theme/theme.service.ts new file mode 100644 index 0000000..00768e5 --- /dev/null +++ b/userAndAdvertiser/src/app/utils/theme/theme.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class ThemeService +{ + + isLightTheme = true; + + getClassTheme(): string + { + if(this.isLightTheme) return "lightTheme" ; + else return "darkTheme" + } + +} diff --git a/userAndAdvertiser/src/assets/.gitkeep b/userAndAdvertiser/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/userAndAdvertiser/src/assets/darkBackground.webp b/userAndAdvertiser/src/assets/darkBackground.webp new file mode 100644 index 0000000..0d0692b Binary files /dev/null and b/userAndAdvertiser/src/assets/darkBackground.webp differ diff --git a/userAndAdvertiser/src/assets/lightBackground.jpg b/userAndAdvertiser/src/assets/lightBackground.jpg new file mode 100644 index 0000000..164cb51 Binary files /dev/null and b/userAndAdvertiser/src/assets/lightBackground.jpg differ diff --git a/userAndAdvertiser/src/assets/logo.png b/userAndAdvertiser/src/assets/logo.png new file mode 100644 index 0000000..93b9375 Binary files /dev/null and b/userAndAdvertiser/src/assets/logo.png differ diff --git a/userAndAdvertiser/src/assets/logo_plateforms/dailymotion.png b/userAndAdvertiser/src/assets/logo_plateforms/dailymotion.png new file mode 100644 index 0000000..d35ee8a Binary files /dev/null and b/userAndAdvertiser/src/assets/logo_plateforms/dailymotion.png differ diff --git a/userAndAdvertiser/src/assets/logo_plateforms/youtube.png b/userAndAdvertiser/src/assets/logo_plateforms/youtube.png new file mode 100644 index 0000000..5924c8d Binary files /dev/null and b/userAndAdvertiser/src/assets/logo_plateforms/youtube.png differ diff --git a/userAndAdvertiser/src/assets/play.png b/userAndAdvertiser/src/assets/play.png new file mode 100644 index 0000000..194f73b Binary files /dev/null and b/userAndAdvertiser/src/assets/play.png differ diff --git a/userAndAdvertiser/src/assets/profil.png b/userAndAdvertiser/src/assets/profil.png new file mode 100644 index 0000000..b35b2e4 Binary files /dev/null and b/userAndAdvertiser/src/assets/profil.png differ diff --git a/userAndAdvertiser/src/assets/uploadFile.png b/userAndAdvertiser/src/assets/uploadFile.png new file mode 100644 index 0000000..cff9f38 Binary files /dev/null and b/userAndAdvertiser/src/assets/uploadFile.png differ diff --git a/userAndAdvertiser/src/environments/environment.prod.ts b/userAndAdvertiser/src/environments/environment.prod.ts new file mode 100644 index 0000000..8d9f516 --- /dev/null +++ b/userAndAdvertiser/src/environments/environment.prod.ts @@ -0,0 +1,4 @@ +export const environment = { + production: true, + debutUrl: "https://polynotfound.herokuapp.com/api/" +}; diff --git a/userAndAdvertiser/src/environments/environment.ts b/userAndAdvertiser/src/environments/environment.ts new file mode 100644 index 0000000..6c4970f --- /dev/null +++ b/userAndAdvertiser/src/environments/environment.ts @@ -0,0 +1,17 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. + +export const environment = { + production: false, + debutUrl: "http://127.0.0.1:3000/api/" +}; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/plugins/zone-error'; // Included with Angular CLI. diff --git a/userAndAdvertiser/src/favicon.ico b/userAndAdvertiser/src/favicon.ico new file mode 100644 index 0000000..997406a Binary files /dev/null and b/userAndAdvertiser/src/favicon.ico differ diff --git a/userAndAdvertiser/src/index.html b/userAndAdvertiser/src/index.html new file mode 100644 index 0000000..0e892e2 --- /dev/null +++ b/userAndAdvertiser/src/index.html @@ -0,0 +1,13 @@ + + + + + StreamNotFound + + + + + + + + diff --git a/userAndAdvertiser/src/main.ts b/userAndAdvertiser/src/main.ts new file mode 100644 index 0000000..c7b673c --- /dev/null +++ b/userAndAdvertiser/src/main.ts @@ -0,0 +1,12 @@ +import { enableProdMode } from '@angular/core'; +import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; + +import { AppModule } from './app/app.module'; +import { environment } from './environments/environment'; + +if (environment.production) { + enableProdMode(); +} + +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/userAndAdvertiser/src/polyfills.ts b/userAndAdvertiser/src/polyfills.ts new file mode 100644 index 0000000..373f538 --- /dev/null +++ b/userAndAdvertiser/src/polyfills.ts @@ -0,0 +1,65 @@ +/** + * This file includes polyfills needed by Angular and is loaded before the app. + * You can add your own extra polyfills to this file. + * + * This file is divided into 2 sections: + * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. + * 2. Application imports. Files imported after ZoneJS that should be loaded before your main + * file. + * + * The current setup is for so-called "evergreen" browsers; the last versions of browsers that + * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), + * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. + * + * Learn more in https://angular.io/guide/browser-support + */ + +/*************************************************************************************************** + * BROWSER POLYFILLS + */ + +/** + * IE11 requires the following for NgClass support on SVG elements + */ +// import 'classlist.js'; // Run `npm install --save classlist.js`. + +/** + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). + */ +// import 'web-animations-js'; // Run `npm install --save web-animations-js`. + +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + * because those flags need to be set before `zone.js` being loaded, and webpack + * will put import in the top of bundle, so user need to create a separate file + * in this directory (for example: zone-flags.ts), and put the following flags + * into that file, and then add the following code before importing zone.js. + * import './zone-flags'; + * + * The flags allowed in zone-flags.ts are listed here. + * + * The following flags will work for all browsers. + * + * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + * + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + * + * (window as any).__Zone_enable_cross_context_check = true; + * + */ + +/*************************************************************************************************** + * Zone JS is required by default for Angular itself. + */ +import 'zone.js'; // Included with Angular CLI. + + +/*************************************************************************************************** + * APPLICATION IMPORTS + */ diff --git a/userAndAdvertiser/src/styles.scss b/userAndAdvertiser/src/styles.scss new file mode 100644 index 0000000..90d4ee0 --- /dev/null +++ b/userAndAdvertiser/src/styles.scss @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/userAndAdvertiser/src/test.ts b/userAndAdvertiser/src/test.ts new file mode 100644 index 0000000..b4dd603 --- /dev/null +++ b/userAndAdvertiser/src/test.ts @@ -0,0 +1,27 @@ +// This file is required by karma.conf.js and loads recursively all the .spec and framework files + +import 'zone.js/testing'; +import { getTestBed } from '@angular/core/testing'; +import { + BrowserDynamicTestingModule, + platformBrowserDynamicTesting +} from '@angular/platform-browser-dynamic/testing'; + +declare const require: { + context(path: string, deep?: boolean, filter?: RegExp): { + keys(): string[]; + (id: string): T; + }; +}; + +// First, initialize the Angular testing environment. +getTestBed().initTestEnvironment( + BrowserDynamicTestingModule, + platformBrowserDynamicTesting(), + { teardown: { destroyAfterEach: true }}, +); + +// Then we find all the tests. +const context = require.context('./', true, /\.spec\.ts$/); +// And load the modules. +context.keys().map(context); diff --git a/userAndAdvertiser/tsconfig.app.json b/userAndAdvertiser/tsconfig.app.json new file mode 100644 index 0000000..82d91dc --- /dev/null +++ b/userAndAdvertiser/tsconfig.app.json @@ -0,0 +1,15 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": [ + "src/main.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.d.ts" + ] +} diff --git a/userAndAdvertiser/tsconfig.json b/userAndAdvertiser/tsconfig.json new file mode 100644 index 0000000..4a4dc62 --- /dev/null +++ b/userAndAdvertiser/tsconfig.json @@ -0,0 +1,23 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/out-tsc", + "sourceMap": true, + "declaration": false, + "downlevelIteration": true, + "experimentalDecorators": true, + "moduleResolution": "node", + "importHelpers": true, + "target": "es2015", + "module": "es2020", + "lib": [ + "es2018", + "dom" + ] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false + } +} diff --git a/userAndAdvertiser/tsconfig.spec.json b/userAndAdvertiser/tsconfig.spec.json new file mode 100644 index 0000000..092345b --- /dev/null +++ b/userAndAdvertiser/tsconfig.spec.json @@ -0,0 +1,18 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": [ + "jasmine" + ] + }, + "files": [ + "src/test.ts", + "src/polyfills.ts" + ], + "include": [ + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +}