Compare commits

...
This repository has been archived on 2026-05-01. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.

32 commits

Author SHA1 Message Date
7f3b7e936e Update: User.ad randomize 2022-01-10 17:11:43 +01:00
3ff902353d Update: User.create 2022-01-10 12:25:28 +01:00
5e9e049cfc Update: Admin can create Admin or Advertiser 2022-01-10 12:22:03 +01:00
a428e5cff2 Update: Admin can create Admin or Advertiser 2022-01-10 12:07:45 +01:00
Yûki VACHOT
e6b8eef43c Update: Move Cors to server.js 2021-12-29 19:08:52 +01:00
Yûki VACHOT
32ceef171e Update: Add console log origin 2021-12-29 19:02:31 +01:00
Yûki VACHOT
f69ed4b3d4 Update: Npm Cors Doc 2021-12-29 19:01:06 +01:00
Yûki VACHOT
c6e02cf797 Update: Move Cors to Routes 2021-12-29 18:51:56 +01:00
Yûki VACHOT
3db7bfd7af Update: Add sameSite=None Cookie 2021-12-29 18:42:10 +01:00
Yûki VACHOT
96481369da Update: Add sameSite Cookie 2021-12-29 18:39:15 +01:00
Yûki VACHOT
aca78238fe Update: Add Secure 2021-12-29 18:35:33 +01:00
Yûki VACHOT
3febde57bb Update: Remove Allow from res 2021-12-29 18:20:47 +01:00
Yûki VACHOT
a9c959bf3f Update: Add console.log token 2021-12-29 18:10:05 +01:00
Yûki VACHOT
7a9ee00eb5 Update: Add Access Controls 2021-12-29 18:05:06 +01:00
Yûki VACHOT
68e212fce9 Update: Rollback Origin 2021-12-29 17:52:59 +01:00
Yûki VACHOT
e8e66fe505 Update: Allow Origin * 2021-12-29 17:50:45 +01:00
Yûki VACHOT
5ba9036289 Update: Change Cors Origin Admin 2021-12-29 17:36:46 +01:00
Yûki VACHOT
086d54a84b Update: Change Cors Origin False 2021-12-29 17:34:42 +01:00
Yûki VACHOT
b26c75f163 Update: Remove Origin 2021-12-29 17:30:37 +01:00
Yûki VACHOT
a330e71860 Update: Dev & Prod Backend explanations 2021-12-26 18:02:42 +01:00
Yûki VACHOT
5495a8321a Update: Add dev & prod local scripts 2021-12-26 18:00:43 +01:00
Yûki VACHOT
f328af51c5 Update: Cors Test 2021-12-23 13:25:35 +01:00
Yûki VACHOT
ae10848b63 Update: Cors Test 2021-12-23 13:22:11 +01:00
Yûki VACHOT
f26fcdc961 Update: Move Cors inside Routes definition 2021-12-23 13:18:04 +01:00
Yûki VACHOT
a12926f277 Update: Remove *all get request 2021-12-23 13:10:57 +01:00
Yûki VACHOT
ae0fe1f32f Update: Environment for Heroku Production 2021-12-23 13:07:36 +01:00
Yûki VACHOT
7448f6b591 Update: Environment for Heroku Production 2021-12-23 11:56:40 +01:00
965f1c03e9
Update server.js 2021-12-22 21:29:05 +01:00
6ce49daa9a
Update: Cors 2021-12-22 21:06:29 +01:00
Yûki VACHOT
97b6039ce5 Update: package.json 2021-12-22 12:04:39 +01:00
Yûki VACHOT
4f85d63bac Create: Add package.json 2021-12-22 11:17:47 +01:00
Yûki VACHOT
76ac0c292c Create: Backend branch 2021-12-22 11:09:20 +01:00
262 changed files with 133 additions and 12981 deletions

View file

@ -1,17 +0,0 @@
# 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.

View file

@ -1,16 +0,0 @@
# 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

View file

@ -1,6 +0,0 @@
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 . .

View file

@ -1,27 +1,67 @@
# Frontend
# PolyNotFound - Partie Backend en local
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 11.2.7.
## Lancement du Backend
## Development server
### 1.1 Installation des différentes librairies avec npm
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.
Si NodeJS est installé ([téléchargeable ici](https://nodejs.org/en/download/)), il suffit de faire un `npm install`.
## 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`.
### 1.2 Création d'une base de données MongoDB en local avec Docker
## Build
Il faudra **Docker** ([téléchargeable ici](https://docs.docker.com/desktop/#download-and-install))
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.
Puis dans un terminal, pour lancer le serveur MongoDB
## Running unit tests
`docker run -d -p 27017:27017 --ip 127.0.0.1 --name polynotfound-mongodb mongo:latest`
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
_L'image de Mongo sera automatiquement téléchargé si elle n'existe pas en local._
## Running end-to-end tests
1. Si vous avez MongoDB Compass ([téléchargeable ici](https://www.mongodb.com/try/download/compass)):
- Se connecter au serveur `mongodb://localhost:27017/?readPreference=primary&appname=MongoDB%20Compass&ssl=false`
- Create Database :
- Database Name : polynotfound
- Collection Name : users
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
2. Sinon avec MongoShell ([téléchargeable ici](https://www.mongodb.com/try/download/shell)):
- Se connecter au serveur
- Linux: `mongo mongodb://localhost:27017/?readPreference=primary&appname=MongoDB%20Compass&ssl=false`
- Windows: `mongo.exe mongodb://localhost:27017/?readPreference=primary&appname=MongoDB%20Compass&ssl=false`
- Créer la base de données : `use polynotfound`
## Further help
### 1.3 Initialisation des variables d'environnements
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.
5 variables d'environnements sont nécessaires pour lancer le backend correctement.
- **DATABASE** : url de connexion à la base de données (utilisé seulement en production)
- Sur Windows : `set DATABASE=<url_bd>`
- Sur Linux : `export DATABASE=<url_bdd>`
- Token de connexion
- **JWTRS256_PUBLIC_KEY** : clé publique pour les tokens de connexion
- **JWTRS256_PRIVATE_KEY** : clé privée pour les tokens de connexion
Lancer le script de génération des clés `jwtRS256.sh`, un fichier `.env` sera créé.
Il faudra `set` ou `export` ces 2 variables dans vos variables d'environnements.
- Sur Windows :
- `set JWTRS256_PUBLIC_KEY=<public_key>`
- `set JWTRS256_PRIVATE_KEY=<private_key>`
- Sur Linux :
- `export JWTRS256_PUBLIC_KEY=<public_key>`
- `export JWTRS256_PRIVATE_KEY=<private_key>`
- **YOUTUBE_API_KEY** : Clé de connexion à l'API Youtube ([récupérable ici](https://developers.google.com/youtube/v3/getting-started?hl=fr))
- **DAILYMOTION_API_KEY** : Clé de connexion à l'API Dailymotion ([récupérable ici](https://www.dailymotion.com/profile/developer))
_A noté que la clé d'API de Dailymotion n'est pas utilisée mais cela peut changer dans le temps._
Après l'initialisation de ces variables d'environnements, il faudra surement redémarrer votre ordinateur. (Fermer et rouvrir un terminal peut être suffisant dans certain cas)
#### 1.3.1 Lancement en mode développement
Pour lancer le backend en mode développement, il faudra simplement exécuter dans un terminal:
- Windows : `npm dev-win`
- Linux & Mac : `npm dev-nix`
#### 1.3.2 Lancement en mode production
Pour lancer le backend en mode production, il faudra simplement exécuter dans un terminal:
- Windows : `npm prod-win`
- Linux & Mac : `npm prod-nix`

View file

@ -1,134 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"frontend": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/frontend",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": true,
"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": [
"node_modules/jquery/dist/jquery.js",
"node_modules/bootstrap/dist/js/bootstrap.js"
]
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "frontend:build"
},
"configurations": {
"production": {
"browserTarget": "frontend:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "frontend: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",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.scss"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "frontend:serve"
},
"configurations": {
"production": {
"devServerTarget": "frontend:serve:production"
}
}
}
}
}
},
"defaultProject": "frontend"
}

20
config/cors.config.js Normal file
View file

@ -0,0 +1,20 @@
const cors = require('cors');
module.exports.cors = cors;
const whitelist = [
'http://127.0.0.1:4200',
'http://127.0.0.1:4201',
'https://admin-polynotfound.herokuapp.com',
'https://polynotfound.herokuapp.com'
];
module.exports.corsOptions = {
origin: function(origin, callback) {
console.log(whitelist, origin);
if (whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
}

View file

@ -42,7 +42,7 @@ function createSessionCookie(req, res, payload) {
else {
jwtToken = createSessionJWT(payload.id, payload.email, payload.profileImageUrl, payload.role);
}
res.cookie('SESSIONID', jwtToken, {httpOnly:true, secure:false});
res.cookie('SESSIONID', jwtToken, {httpOnly: true, sameSite: 'None', secure: process.env.NODE_ENV === 'production'});
}
function decodeSessionCookie(sessionid) {
@ -81,6 +81,7 @@ function checkLogin(req, res, role=null){
if(typeof req.cookies !== 'undefined'){
const session = getSession(req.cookies.SESSIONID);
const token = getToken(session);
console.log(token);
if(typeof token.email === 'undefined' ||
token.email === -1 ||
typeof token.id === 'undefined' ||

View file

@ -401,7 +401,7 @@ exports.ad = (req, res) => {
{$sort: {watchedDates: -1}},
{$limit: limit},
{$unwind: '$interest'},
{$group: {_id: null, interests: {$push: '$interest'}}}
{$group: {_id: null, interests: {$addToSet: '$interest'}}}
])
.then(data => {
if(typeof data[0] !== 'undefined' &&
@ -410,15 +410,13 @@ exports.ad = (req, res) => {
data[0].interests !== null){
interests = interests.concat(data[0].interests);
}
let match, pick;
let match;
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)}};
}
const pick = {$sample: {size: parseInt(quantity, 10)}};
Ad.aggregate([
match,
pick

View file

@ -396,8 +396,9 @@ exports.update = (req, res) => {
}
update.$push = condition;
const watchedDates = req.body.watchedDates ? req.body.watchedDates : undefined;
update.watchedDates = watchedDates;
const watchedDates = req.body.watchedDates;
condition = watchedDates ? watchedDates : undefined;
update.watchedDates = condition;
const isActive = req.body.isActive;
if(typeof isActive !== 'undefined'){

View file

@ -1,41 +0,0 @@
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

View file

@ -1,37 +0,0 @@
// @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
}
}));
}
};

View file

@ -1,23 +0,0 @@
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));
});
});

View file

@ -1,11 +0,0 @@
import { browser, by, element } from 'protractor';
export class AppPage {
async navigateTo(): Promise<unknown> {
return browser.get(browser.baseUrl);
}
async getTitleText(): Promise<string> {
return element(by.css('app-root .content span')).getText();
}
}

View file

@ -1,13 +0,0 @@
/* 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"
]
}
}

0
app-backend/jwtRS256.sh → jwtRS256.sh Executable file → Normal file
View file

View file

@ -1,44 +0,0 @@
// 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/frontend'),
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
});
};

View file

@ -1,43 +1,23 @@
{
"name": "frontend",
"name": "backend",
"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"
"dev-win": "set NODE_ENV=development && node server.js",
"dev-nix": "export NODE_ENV=development && node server.js",
"prod-win": "set NODE_ENV=production && node server.js",
"prod-nix": "export NODE_ENV=production && node server.js"
},
"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",
@ -45,23 +25,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"
}
}

View file

@ -1,42 +1,43 @@
const users = require("../controllers/user.controller");
//const {cors, corsOptions} = require("../config/cors.config");
module.exports = app => {
let router = require("express").Router();
// Authenticate a User
router.post("/user/auth", users.auth);
router.post("/user/auth", users.auth);
// Logout a User
router.delete("/user/logout", users.logout);
router.delete("/user/logout", users.logout);
// Request password reset with email
router.post("/user/resetPass", users.resetPass);
router.post("/user/resetPass", users.resetPass);
// Create and Save a new User
router.post("/user/create", users.create);
router.post("/user/create", users.create);
// Retrieve all Users if admin
router.get("/user/findAll", users.findAll);
router.get("/user/findAll", users.findAll);
// Find single User from id if admin or session id
router.get("/user/findOne/:id", users.findOne);
router.get("/user/findOne/:id", users.findOne);
// Update a User from id if admin or session id
router.put("/user/update/:id", users.update);
router.put("/user/update/:id", users.update);
// Delete a User from id if admin or session id
router.delete("/user/delete/:id", users.delete);
router.delete("/user/delete/:id", users.delete);
// Delete all Users if superAdmin
router.delete("/user/deleteAll", users.deleteAll);
router.delete("/user/deleteAll", users.deleteAll);
// Get all Roles depending on the User session id
router.get("/user/roles", users.roles);
router.get("/user/roles", users.roles);
// Get 1 or multiple ad adapted to the User session id
router.get("/user/ad", users.ad);
router.get("/user/ad", users.ad);
// Get History
router.get("/user/history", users.history);
router.get("/user/history", users.history);
app.use('/api', router);
};

View file

@ -1,8 +1,32 @@
const path = require('path');
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
const cors = require('cors');
const whitelist = [
'http://127.0.0.1:4200',
'http://127.0.0.1:4201',
'https://admin-polynotfound.herokuapp.com',
'https://polynotfound.herokuapp.com'
];
const corsOptionsDelegate = (req, callback) => {
let corsOptions;
if (whitelist.indexOf(req.header('Origin')) !== -1) {
corsOptions = {
origin: true,
credentials: true
}
} else {
corsOptions = {
origin: false,
credentials: true
}
}
callback(null, corsOptions)
}
app.use(cors(corsOptionsDelegate));
const cookieParser = require('cookie-parser');
app.use(cookieParser());
@ -10,10 +34,7 @@ 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");
const db = require("./models/mongodb.model");
console.log("Db Url: ",db.url);
db.mongoose
.connect(db.url, {
@ -32,13 +53,13 @@ db.mongoose
}
});
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);
require("./routes/user.routes")(app);
require("./routes/playlist.routes")(app);
require("./routes/video.routes")(app);
require("./routes/ad.routes")(app);
require("./routes/misc.routes")(app);
const roles = require("./app-backend/models/objects/role.model");
const roles = require("./models/objects/role.model");
const User = db.users;
const login = 'superAdmin';
const hashPass = 'hashPassSuperAdmin';
@ -70,15 +91,6 @@ User.exists({role: roles.SuperAdmin}, function (err, docs){
}
});
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}`);
});

View file

@ -1,167 +0,0 @@
<div [class]="themeService.getClassTheme()">
<div class="myContainer">
<!-- Navbar -->
<app-navbar-admin></app-navbar-admin><br><br>
<!-- filtre -->
<div class="filtersContainer mat-elevation-z8">
<!-- titre -->
<div style="font-weight: bold; margin-bottom: 10px;">
Filtre
</div>
<mat-divider></mat-divider>
<!-- filtre textuelle-->
<div style="margin: 10px 0px 20px 2%;">
<input class="textFilter" (keyup)="applyFilter($event)" placeholder="Rechercher par mots-clés...">
</div>
<!-- visible + interests + période -->
<div class="row myRow">
<!-- visible -->
<div class="col-2">
<mat-checkbox [(ngModel)]="visible" (change)="onFilter()">visible</mat-checkbox><br>
<mat-checkbox [(ngModel)]="noVisible" (change)="onFilter()">non visible</mat-checkbox>
</div>
<!-- subjects -->
<div class="col-4">
<mat-form-field appearance="fill">
<mat-label>Sujets</mat-label>
<mat-select [formControl]="formControlInterests" multiple>
<mat-select-trigger>
{{formControlInterests.value ? formControlInterests.value[0] : ''}}
<span *ngIf="formControlInterests.value?.length > 1">
(+{{formControlInterests.value.length - 1}} {{formControlInterests.value?.length === 2 ? 'autre' : 'autres'}})
</span>
</mat-select-trigger>
<mat-option *ngFor="let topping of allInterests" [value]="topping">{{topping}}</mat-option>
</mat-select>
</mat-form-field>
<button mat-icon-button (click)="onFilter()">
<mat-icon>keyboard_tab</mat-icon>
</button>
</div>
<!-- période -->
<div class="col-6" style="text-align: right;">
Période de création: &nbsp;
<mat-form-field appearance="fill" style="width: 140px; font-size: small;">
<mat-label>Date de début</mat-label>
<input matInput type="date"
style="font-size: small; width: 140px;"
[ngModel] ="startDate | date:'yyyy-MM-dd'"
(ngModelChange)="onNewStartDate($event); onFilter();">
</mat-form-field>
&nbsp; - &nbsp;
<mat-form-field appearance="fill" style="width: 140px; font-size: small;">
<mat-label>Date de fin</mat-label>
<input matInput type="date"
style="font-size: small; width: 140px;"
[ngModel] ="endDate | date:'yyyy-MM-dd'"
(ngModelChange)="onNewEndDate($event); onFilter();">
</mat-form-field>
</div>
</div>
</div>
<!-- table -->
<table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z8">
<!-- Title Column -->
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Titre </th>
<td mat-cell *matCellDef="let advert">
{{advert.title}}
</td>
</ng-container>
<!-- Advertiser Column -->
<ng-container matColumnDef="company">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Entreprise </th>
<td mat-cell *matCellDef="let advert">
{{advert.company}}
</td>
</ng-container>
<!-- Tags Column -->
<ng-container matColumnDef="interests">
<th mat-header-cell *matHeaderCellDef> Sujets </th>
<td mat-cell *matCellDef="let advert">
<span *ngFor="let objectInterest of advert.interests; let isLast = last;">
<span *ngIf="!isLast"> {{objectInterest.interest}}, </span>
<span *ngIf="isLast"> {{objectInterest.interest}} </span>
</span>
</td>
</ng-container>
<!-- CreatedAt Column -->
<ng-container matColumnDef="createdAt">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Date de création </th>
<td mat-cell *matCellDef="let advert">
{{ advert.createdAt | date:'dd/LL/YYYY à HH:mm:ss' }}
</td>
</ng-container>
<!-- UpdatedAt Column -->
<ng-container matColumnDef="updatedAt">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Dernière modification </th>
<td mat-cell *matCellDef="let advert">
{{ advert.updatedAt | date:'dd/LL/YYYY à HH:mm:ss' }}
</td>
</ng-container>
<!-- CountViews Column -->
<ng-container matColumnDef="countViews">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Vues </th>
<td mat-cell *matCellDef="let advert">
{{advert.countViews}}
</td>
</ng-container>
<!-- IsVisible Column -->
<ng-container matColumnDef="isVisible">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Visible </th>
<td mat-cell *matCellDef="let advert">
<span *ngIf="advert.isVisible"> <mat-icon>check</mat-icon> </span>
<span *ngIf="!advert.isVisible"></span>
</td>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> Actions </th>
<td mat-cell *matCellDef="let advert">
<button mat-icon-button (click)="onVisualizeImages(advert)">
<mat-icon> insert_photo</mat-icon>
</button>
<button mat-icon-button (click)="onDelete(advert)">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<!-- Directives -->
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr class="mat-row" *matNoDataRow>
<td class="mat-cell" colspan="4"> Aucune vidéo ne correspond au filtre: "{{input.value}}" </td>
</tr>
</table>
<div style="width: 94%; margin: auto auto">
<mat-paginator [pageSizeOptions]="[10, 20, 50, 100]" showFirstLastButtons aria-label="Select page of periodic elements"></mat-paginator>
</div>
<br><br>
</div>
</div>

View file

@ -1,74 +0,0 @@
.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;
}

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PageAdListAdminComponent } from './page-ad-list-admin.component';
describe('PageAdListAdminComponent', () => {
let component: PageAdListAdminComponent;
let fixture: ComponentFixture<PageAdListAdminComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PageAdListAdminComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PageAdListAdminComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,262 +0,0 @@
import {AfterViewInit, Component, ViewChild} from '@angular/core';
import {MatSort} from "@angular/material/sort";
import {MatPaginator} from "@angular/material/paginator";
import {ThemeService} from "../../../utils/services/theme/theme.service";
import {MatDialog} from "@angular/material/dialog";
import {MatSnackBar} from "@angular/material/snack-bar";
import {MatTableDataSource} from "@angular/material/table";
import {Advert} from "../../../utils/interfaces/advert";
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 {MessageService} from "../../../utils/services/message/message.service";
import {HttpParams} from "@angular/common/http";
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<Advert>();
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,
}
}
}

View file

@ -1,8 +0,0 @@
<mat-dialog-content class="mat-typography">
Êtes-vous sûr de vouloir supprimer l'annonce <i>{{advert.title}}</i> ?
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="dialogRef.close();">Annuler</button>
<button mat-button (click)="onValidate()" cdkFocusInitial>Valider</button>
</mat-dialog-actions>

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PopupDeleteAdAdminComponent } from './popup-delete-ad-admin.component';
describe('PopupDeleteAdAdminComponent', () => {
let component: PopupDeleteAdAdminComponent;
let fixture: ComponentFixture<PopupDeleteAdAdminComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PopupDeleteAdAdminComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PopupDeleteAdAdminComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,49 +0,0 @@
import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {MessageService} from "../../../utils/services/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<PopupDeleteAdAdminComponent>,
@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);
}
}
}

View file

@ -1,20 +0,0 @@
<div mat-dialog-title class="dialog-title">
<h2></h2>
<button mat-icon-button aria-label="close dialog" mat-dialog-close>
<mat-icon>close</mat-icon>
</button>
</div>
<mat-grid-list cols="12" rowHeight="500">
<mat-grid-tile colspan="1" rowspan="1" (click)="onPrecedent()">
<button> < </button>
</mat-grid-tile>
<mat-grid-tile colspan="10" rowspan="1">
<img [src]="tabImages[index].base64" [alt]="tabImages[index].description">
</mat-grid-tile>
<mat-grid-tile colspan="1" rowspan="1" (click)="onSuivant()">
<button> > </button>
</mat-grid-tile>
</mat-grid-list>

View file

@ -1,14 +0,0 @@
carousel {
width: 100%;
margin: 0 auto;
text-align: center;
justify-content: center
}
.dialog-title {
display: flex;
justify-content: space-between;
align-items: center;
}

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PopupVisualizeImagesAdminComponent } from './popup-visualize-images-admin.component';
describe('PopupVisualizeImagesAdminComponent', () => {
let component: PopupVisualizeImagesAdminComponent;
let fixture: ComponentFixture<PopupVisualizeImagesAdminComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PopupVisualizeImagesAdminComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PopupVisualizeImagesAdminComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,38 +0,0 @@
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<PopupVisualizeImagesAdminComponent>,
@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;
}
}

View file

@ -1,43 +0,0 @@
<div [class]="themeService.getClassTheme()">
<div class="myContainer">
<!-- NavBar -->
<app-navbar-admin></app-navbar-admin>
<!-- Boite -->
<div class="boite">
<!-- Photo de profil -->
<div style="text-align: center">
<img [src]="admin.profileImageUrl"
onerror="this.onerror=null; this.src='assets/profil.png'">
</div>
<!-- login -->
<div class="row myRow">
<div class="col-6 myLabel">Pseudo:</div>
<div class="col-6 myValue"> {{admin.login}} </div>
</div>
<!-- email -->
<div class="row myRow">
<div class="col-6 myLabel">Mail:</div>
<div class="col-6 myValue"> {{admin.email}} </div>
</div>
<!-- createdAt -->
<div class="row myRow">
<div class="col-6 myLabel">Date de création:</div>
<div class="col-6 myValue">{{admin.createdAt | date:'dd/LL/YYYY'}}</div>
</div>
<!-- Modifier profil -->
<div class="btnContainer">
<button mat-button class="myBtn" (click)="onModifier()">Modifier profil</button>
</div>
</div>
</div>
</div>

View file

@ -1,61 +0,0 @@
.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;
}

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PageProfilAdminComponent } from './page-profil-admin.component';
describe('PageProfilAdminComponent', () => {
let component: PageProfilAdminComponent;
let fixture: ComponentFixture<PageProfilAdminComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PageProfilAdminComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PageProfilAdminComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,90 +0,0 @@
import { Component, OnInit } from '@angular/core';
import {User} from "../../../utils/interfaces/user";
import {ThemeService} from "../../../utils/services/theme/theme.service";
import {MatDialog} from "@angular/material/dialog";
import {MatSnackBar} from "@angular/material/snack-bar";
import {PopupUpdateAdminComponent} from "../popup-update-admin/popup-update-admin.component";
import {MessageService} from "../../../utils/services/message/message.service";
import {ProfilService} from "../../../utils/services/profil/profil.service";
@Component({
selector: 'app-page-profil-admin',
templateUrl: './page-profil-admin.component.html',
styleUrls: ['./page-profil-admin.component.scss']
})
export class PageProfilAdminComponent implements OnInit
{
admin: User = {
_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;
}
});
}
}

View file

@ -1,59 +0,0 @@
<div class="myContainer">
<div class="boite">
<!-- photo de profil -->
<div style="text-align: center">
<img [src]="adminCopy.profileImageUrl" onerror="this.onerror=null; this.src='assets/profil.png'"><br>
<input title="lien vers image" type="text" [(ngModel)]="adminCopy.profileImageUrl" style="width: 90%">
</div>
<!-- divider -->
<br><mat-divider></mat-divider><br>
<!-- login -->
<mat-form-field appearance="fill">
<mat-label>Pseudo</mat-label>
<input matInput type="text" [(ngModel)]="adminCopy.login">
</mat-form-field><br>
<!-- divider -->
<mat-divider></mat-divider><br>
<!-- Modifier mot de passe -->
<div>
Modifier mot de passe:
<mat-checkbox [(ngModel)]="changePassword"></mat-checkbox>
</div>
<!-- nouveau mot de passe -->
<div *ngIf="changePassword" style="margin-top: 10px">
<!-- Nouveau mot de passe -->
<mat-form-field appearance="fill">
<mat-label>Nouveau mot de passe</mat-label>
<input matInput type="password" [(ngModel)]="newPassword">
</mat-form-field>
<br>
<!-- Confirmation npuveau mot de passe -->
<mat-form-field appearance="fill">
<mat-label>Confirmation nouveau mot de passe</mat-label>
<input matInput type="password" [(ngModel)]="confirmNewPassword">
</mat-form-field>
</div>
<div *ngIf="!changePassword"><br></div>
<!-- divider -->
<mat-divider></mat-divider><br>
<!-- message d'erreur -->
<div *ngIf="hasError" style="text-align: center; margin-bottom: 20px;">
<span class="mat-error">{{errorMessage}}</span>
</div>
<!-- boutons -->
<div style="width: 100%; text-align: right">
<button mat-button (click)="this.dialogRef.close(null)"> Annuler </button>
<button mat-button (click)="onValider()"> Enregistrer </button>
</div>
</div>
</div>

View file

@ -1,33 +0,0 @@
.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;
}

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PopupUpdateAdminComponent } from './popup-update-admin.component';
describe('PopupUpdateAdminComponent', () => {
let component: PopupUpdateAdminComponent;
let fixture: ComponentFixture<PopupUpdateAdminComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PopupUpdateAdminComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PopupUpdateAdminComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,124 +0,0 @@
import {Component, Inject, OnInit} from '@angular/core';
import {User} from "../../../utils/interfaces/user";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {MessageService} from "../../../utils/services/message/message.service";
import {ProfilService} from "../../../utils/services/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: User;
newPassword: string = "";
confirmNewPassword: string = "" ;
changePassword: boolean = false ;
hasError: boolean = false;
errorMessage: string = "" ;
constructor( public dialogRef: MatDialogRef<PopupUpdateAdminComponent>,
@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);
}
}

View file

@ -1,43 +0,0 @@
<mat-form-field class="example-chip-list" appearance="fill">
<!-- ------------------------------------------------------------------------------------ -->
<mat-label>Centres d'intérêt</mat-label>
<!-- ------------------------------------------------------------------------------------ -->
<mat-chip-list #chipList aria-label="Fruit selection">
<mat-chip
*ngFor="let interest of myInterests"
[selectable]="selectable"
[removable]="removable"
(removed)="remove(interest)">
{{interest}}
<button matChipRemove *ngIf="removable">
<mat-icon>cancel</mat-icon>
</button>
</mat-chip>
<input
placeholder="Tapez un centre d'intérêt et pressez 'Entrer' pour l'inserer"
#tagInput
[formControl]="formControl"
[matAutocomplete]="auto"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="add($event)">
</mat-chip-list>
<!-- ------------------------------------------------------------------------------------ -->
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngFor="let interest of filteredInterests | async" [value]="interest">
{{interest}}
</mat-option>
</mat-autocomplete>
<!-- ------------------------------------------------------------------------------------ -->
</mat-form-field>

View file

@ -1,3 +0,0 @@
mat-form-field {
width: 100%;
}

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InputInterestsAdminComponent } from './input-interests-admin.component';
describe('InputInterestsAdminComponent', () => {
let component: InputInterestsAdminComponent;
let fixture: ComponentFixture<InputInterestsAdminComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ InputInterestsAdminComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(InputInterestsAdminComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,121 +0,0 @@
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 {MessageService} from "../../../utils/services/message/message.service";
import {map, startWith} from "rxjs/operators";
import {MatChipInputEvent} from "@angular/material/chips";
import {MatAutocompleteSelectedEvent} from "@angular/material/autocomplete";
@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<string[]>;
@Input() myInterests: string[] = [];
allInterests: string[] = [];
@Output() eventEmitter = new EventEmitter<string[]>();
@ViewChild('tagInput') tagInput: ElementRef<HTMLInputElement>;
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));
}
}

View file

@ -1,193 +0,0 @@
<div [class]="themeService.getClassTheme()">
<div class="myContainer">
<!-- Navbar -->
<app-navbar-admin></app-navbar-admin><br><br>
<!-- filtre + btnAddUser -->
<div class="row" style="margin: 20px 3% 20px 3%">
<!-- filtre -->
<div class="col-9" style="padding: 0px 0px 0px 0px;">
<div class="filtersContainer mat-elevation-z8">
<!-- titre -->
<div style="font-weight: bold; margin-bottom: 10px;">
Filtre
</div>
<mat-divider></mat-divider>
<!-- filtre textuelle-->
<div style="margin: 10px 0px 20px 2%;">
<input class="textFilter" (keyup)="applyFilter($event)" placeholder="Rechercher par mots-clés...">
</div>
<!-- role + actif + période -->
<div class="row myRow">
<!-- choix role -->
<div class="col-2">
<mat-radio-group [(ngModel)]="roleName">
<mat-radio-button value="user" (click)="this.roleName = 'user'; onFilter()">
Utilisateur
</mat-radio-button><br>
<mat-radio-button value="advertiser" (click)="this.roleName = 'advertiser'; onFilter()">
Annonceur
</mat-radio-button><br>
<mat-radio-button value="admin" (click)="this.roleName = 'admin'; onFilter()">
Admin
</mat-radio-button>
</mat-radio-group>
</div>
<!-- actif -->
<div class="col-2">
<mat-checkbox [(ngModel)]="active" (change)="onFilter()">actif</mat-checkbox><br>
<mat-checkbox [(ngModel)]="noActive" (change)="onFilter()">non actif</mat-checkbox>
</div>
<!-- période -->
<div class="col-8" style="text-align: right;">
Période de dernière connexion: &nbsp;
<mat-form-field appearance="fill" style="width: 140px;">
<mat-label>Date de début</mat-label>
<input matInput type="date"
[ngModel] ="startDate | date:'yyyy-MM-dd'"
(ngModelChange)="onNewStartDate($event); onFilter();">
</mat-form-field>
&nbsp; - &nbsp;
<mat-form-field appearance="fill" style="width: 140px;">
<mat-label>Date de fin</mat-label>
<input matInput type="date"
[ngModel] ="endDate | date:'yyyy-MM-dd'"
(ngModelChange)="onNewEndDate($event); onFilter();">
</mat-form-field>
</div>
</div>
</div>
</div>
<!-- btnAddUser -->
<div class="col-3" style="text-align: right; position: relative;">
<button mat-button class="btnAjouter" (click)="onCreateUser()" style="position: absolute; bottom: 0; right: 0;">
<mat-icon>add_circle</mat-icon> Ajouter un utilisateur
</button>
</div>
</div>
<!-- Table -->
<table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z8">
<!-- IsActive Column -->
<ng-container matColumnDef="isActive">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<mat-icon>power_settings_new</mat-icon>
</th>
<td mat-cell *matCellDef="let user">
<mat-slide-toggle *ngIf="user.role.name !== 'superAdmin'" [(ngModel)]="user.isActive" (click)="onSliderIsActive(user)"></mat-slide-toggle>
<mat-slide-toggle *ngIf="user.role.name === 'superAdmin'" [(ngModel)]="user.isActive" disabled></mat-slide-toggle>
</td>
</ng-container>
<!-- Login Column -->
<ng-container matColumnDef="login">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Pseudo </th>
<td mat-cell *matCellDef="let user">
{{user.login}}
</td>
</ng-container>
<!-- Mail Column -->
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Email </th>
<td mat-cell *matCellDef="let user">
{{user.email}}
</td>
</ng-container>
<!-- DateOfBirth Column -->
<ng-container matColumnDef="dateOfBirth">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Date de naissance </th>
<td mat-cell *matCellDef="let user">
<span *ngIf="((user.dateOfBirth === null) || (user.dateOfBirth === undefined)); else elseDateOfBirth"></span>
<ng-template #elseDateOfBirth> {{ user.dateOfBirth | date:'dd/LL/YYYY' }} </ng-template>
</td>
</ng-container>
<!-- Age Column -->
<ng-container matColumnDef="age">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Âge </th>
<td mat-cell *matCellDef="let user">
<span *ngIf="(user.age < 0) ; else elseAge"></span>
<ng-template #elseAge> {{user.age}} </ng-template>
</td>
</ng-container>
<!-- Sexe Column -->
<ng-container matColumnDef="sexe">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Sexe </th>
<td mat-cell *matCellDef="let user">
<span *ngIf="(user.gender === 'man') ; else elseSexe"> H </span>
<ng-template #elseSexe> F </ng-template>
</td>
</ng-container>
<!-- Interests Column -->
<ng-container matColumnDef="interests">
<th mat-header-cell *matHeaderCellDef> Centres d'intérêt </th>
<td mat-cell *matCellDef="let user">
<span *ngFor="let interest of user.interests; let isLast = last;">
<span *ngIf="!isLast ; else elseInterests"> {{interest}}, </span>
<ng-template #elseInterests> {{interest}} </ng-template>
</span>
</td>
</ng-container>
<!-- CreatedAt Column -->
<ng-container matColumnDef="createdAt">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Date de création </th>
<td mat-cell *matCellDef="let user">
{{ user.createdAt | date:'dd/LL/YYYY à HH:mm:ss' }}
</td>
</ng-container>
<!-- LastConnexion Column -->
<ng-container matColumnDef="lastConnexion">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Dernière connexion </th>
<td mat-cell *matCellDef="let user">
{{ user.lastConnexion | date:'dd/LL/YYYY à HH:mm:ss' }}
</td>
</ng-container>
<!-- IsAccepted Column -->
<ng-container matColumnDef="isAccepted">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Accepté </th>
<td mat-cell *matCellDef="let user">
<mat-slide-toggle [(ngModel)]="user.role.isAccepted" (click)="onSlideIsAccepted(user)"></mat-slide-toggle>
</td>
</ng-container>
<!-- Directives -->
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr class="mat-row" *matNoDataRow>
<td class="mat-cell" colspan="4"> Aucune vidéo ne correspond au filtre: "{{input.value}}" </td>
</tr>
</table>
<div style="width: 94%; margin: auto auto">
<mat-paginator [pageSizeOptions]="[10, 20, 50, 100]" showFirstLastButtons aria-label="Select page of periodic elements"></mat-paginator>
</div>
<br><br>
</div>
</div>

View file

@ -1,99 +0,0 @@
.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;
}

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PageUserListComponent } from './page-user-list.component';
describe('PageUserListComponent', () => {
let component: PageUserListComponent;
let fixture: ComponentFixture<PageUserListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PageUserListComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PageUserListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,233 +0,0 @@
import {AfterViewInit, Component, ViewChild} from '@angular/core';
import {MatSort} from "@angular/material/sort";
import {MatPaginator} from "@angular/material/paginator";
import {ThemeService} from "../../../utils/services/theme/theme.service";
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 {MessageService} from "../../../utils/services/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);
}
}

View file

@ -1,160 +0,0 @@
<div class="myContainer">
<!-- Rôle -->
<div class="matRadioGroupContainer">
<mat-radio-group [(ngModel)]="user.role.name" (change)="hasError=false; errorMessage = '';">
<mat-radio-button value="user"> Utilisateur </mat-radio-button><br>
<mat-radio-button value="advertiser"> Annonceur </mat-radio-button><br>
<mat-radio-button value="admin"> Admin </mat-radio-button>
</mat-radio-group>
</div><br>
<!-- divider -->
<mat-divider></mat-divider><br>
<!-- userBlock or advertiserOrAdminBlock -->
<div *ngIf="user.role.name === 'user'; then userBlock"></div>
<div *ngIf="(user.role.name === 'advertiser' || user.role.name === 'admin'); then advertiserOrAdminBlock"></div>
<!-- divider -->
<mat-divider *ngIf="user.role.name !== ''" style="margin-top: 10px;"></mat-divider><br>
<!-- Error -->
<div *ngIf="hasError" style="text-align: center; margin-bottom: 20px;">
<span class="mat-error">{{errorMessage}}</span>
</div>
<!-- Boutons -->
<div style="width: 100%; text-align: right">
<button mat-button (click)="this.dialogRef.close(null)"> Annuler </button>
<button mat-button (click)="onEnregistrer()" [disabled]="user.role.name === ''" > Enregistrer </button>
</div>
</div>
<!-- ------------------------------------------------------------------------------------------------------------------- -->
<!-- userBlock -->
<ng-template #userBlock>
<!-- photo de profil -->
<div style="text-align: center">
<img [src]="user.profileImageUrl" onerror="this.onerror=null; this.src='assets/profil.png'"><br>
<input title="lien vers image" type="text" [(ngModel)]="user.profileImageUrl" style="width: 90%">
</div><br>
<div class="row">
<!-- Colonne gauche -->
<div class="col-4 leftCol">
<!-- Email -->
<mat-form-field appearance="fill">
<mat-label>Email</mat-label>
<input matInput type="email" [(ngModel)]="user.email" required>
</mat-form-field><br>
<!-- Login -->
<mat-form-field appearance="fill">
<mat-label>Pseudo</mat-label>
<input matInput type="text" [(ngModel)]="user.login" required>
</mat-form-field><br>
<!-- Mot de passe -->
<mat-form-field appearance="fill">
<mat-label>Mot de passe</mat-label>
<input matInput type="password" [(ngModel)]="password" required>
</mat-form-field><br>
<!-- Confirmation mot de passe -->
<mat-form-field appearance="fill">
<mat-label>Confirmation mot de passe</mat-label>
<input matInput type="password" [(ngModel)]="confirmPassword" required>
</mat-form-field>
</div>
<!-- Colonne droite -->
<div class="col-8">
<!-- dateOfBirth -->
<mat-form-field appearance="fill">
<mat-label>Date de naissance</mat-label>
<input matInput type="date"
[ngModel] ="user.dateOfBirth | date:'yyyy-MM-dd'"
(ngModelChange)="user.dateOfBirth = $event">
</mat-form-field><br>
<!-- gender -->
<mat-radio-group [(ngModel)]="user.gender">
<mat-radio-button value="man"> Homme </mat-radio-button>&nbsp; &nbsp;
<mat-radio-button value="woman"> Femme </mat-radio-button>
</mat-radio-group>
<br><br>
<!-- interests -->
<app-input-interests-admin
[myInterests]="user.interests"
(eventEmitter)="onEventInputInterests($event)"></app-input-interests-admin>
</div>
</div>
</ng-template>
<!-- ------------------------------------------------------------------------------------------------------------------- -->
<!-- advertiserOrAdminBlock -->
<ng-template #advertiserOrAdminBlock>
<!-- photo de profil -->
<div style="text-align: center">
<img [src]="user.profileImageUrl" onerror="this.onerror=null; this.src='assets/profil.png'"><br>
<input title="lien vers image" type="text" [(ngModel)]="user.profileImageUrl" style="width: 100%">
</div><br>
<!-- email + login + mdp + confirmation -->
<div class="row">
<!-- email + login -->
<div class="col-6">
<!-- email -->
<mat-form-field appearance="fill">
<mat-label>Email</mat-label>
<input matInput type="text" [(ngModel)]="user.email" required>
</mat-form-field><br>
<!-- login -->
<mat-form-field appearance="fill">
<mat-label>Pseudo</mat-label>
<input matInput type="text" [(ngModel)]="user.login" required>
</mat-form-field><br>
<!-- company -->
<mat-form-field appearance="fill" *ngIf="user.role.name === 'advertiser'">
<mat-label>Entreprise</mat-label>
<input matInput type="text" [(ngModel)]="user.company">
</mat-form-field><br *ngIf="user.role.name === 'advertiser'">
</div>
<!-- mdp + confirmation -->
<div class="col-6">
<!-- mot de passe -->
<mat-form-field appearance="fill">
<mat-label>Mot de passe</mat-label>
<input matInput type="password" [(ngModel)]="password" required>
</mat-form-field><br>
<!-- confirmation mot de passe -->
<mat-form-field appearance="fill">
<mat-label>Confirmation nouveau mot de passe</mat-label>
<input matInput type="password" [(ngModel)]="confirmPassword" required>
</mat-form-field>
</div>
</div>
</ng-template>

View file

@ -1,16 +0,0 @@
.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;
}

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PopupCreateUserComponent } from './popup-create-user.component';
describe('PopupCreateUserComponent', () => {
let component: PopupCreateUserComponent;
let fixture: ComponentFixture<PopupCreateUserComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PopupCreateUserComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PopupCreateUserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,127 +0,0 @@
import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {MessageService} from "../../../utils/services/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<PopupCreateUserComponent>,
@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;
}
}

View file

@ -1,8 +0,0 @@
<mat-dialog-content class="mat-typography">
Êtes-vous sûr de vouloir supprimer <i>{{user.login}}</i> ?
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="dialogRef.close();">Annuler</button>
<button mat-button (click)="onValidate()" cdkFocusInitial>Valider</button>
</mat-dialog-actions>

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PopupDeleteUserComponent } from './popup-delete-user.component';
describe('PopupDeleteUserComponent', () => {
let component: PopupDeleteUserComponent;
let fixture: ComponentFixture<PopupDeleteUserComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PopupDeleteUserComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PopupDeleteUserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,47 +0,0 @@
import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {MessageService} from "../../../utils/services/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<PopupDeleteUserComponent>,
@Inject(MAT_DIALOG_DATA) public data,
private messageService: MessageService ) { }
ngOnInit(): void
{
this.user = this.data.user;
}
onValidate(): void
{
// --- FAUX CODE ---
this.dialogRef.close(true);
// --- VRAI CODE ---
/*
this.messageService
.sendMessage("user/delete", {"advert": this.advert})
.subscribe( retour => {
if(retour.status === "error") {
console.log(retour);
this.dialogRef.close();
}
else {
this.dialogRef.close(true);
}
});
*/
}
}

View file

@ -1,37 +0,0 @@
<nav class="navbar navbar-expand-lg">
<!-- PolyNotFound -->
<a class="navbar-brand" routerLink="/admin/userList"> StreamNotFound </a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<!-- [userList] [adList] -->
<div class="collapse navbar-collapse">
<ul class="navbar-nav">
<li class="nav-item active monLi">
<a *ngIf="(url !== routes[0]) && (url !== routes[1])" [routerLink]="routes[1]" class="nav-link">Liste des utillisateurs</a>
<a *ngIf="(url === routes[0]) || (url === routes[1])" [routerLink]="routes[1]" class="nav-link myActiveLink">Liste des utillisateurs</a>
</li>
<li class="nav-item active monLi">
<a *ngIf="url !== routes[2]" [routerLink]="routes[2]" class="nav-link">Liste des publicités</a>
<a *ngIf="url === routes[2]" [routerLink]="routes[2]" class="nav-link myActiveLink">Liste des publicités</a>
</li>
</ul>
</div>
<!-- Mon profil -->
<img [src]=profilService.getProfileImageUrl()
onerror="this.onerror=null; this.src='assets/profil.png'"
[routerLink]="routes[3]"
alt="">
<!-- Deconnexion -->
<button mat-button class="btnDeconnexion" (click)="onDeconnexion()" routerLink="/">
Déconnexion
</button>
</nav>

View file

@ -1,80 +0,0 @@
.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;
}

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NavbarAdminComponent } from './navbar-admin.component';
describe('NavbarAdminComponent', () => {
let component: NavbarAdminComponent;
let fixture: ComponentFixture<NavbarAdminComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ NavbarAdminComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(NavbarAdminComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,40 +0,0 @@
import {Component} from '@angular/core';
import {Router} from "@angular/router";
import {ProfilService} from "../../../utils/services/profil/profil.service";
import {MessageService} from "../../../utils/services/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);
}
}

View file

@ -1,33 +0,0 @@
<div title="bobo" class="container" appDragAndDrop (fileDropped)="onFileDropped($event)">
<input type="file" #fileDropRef id="fileDropRef" multiple (change)="fileBrowseHandler($event.target.files)" />
<div style="font-weight: bold">Images</div>
<img src="/assets/uploadFile.png" width="50" height="50" style="margin-top: 5px">
<div style="font-style: italic; margin-top: 10px">Glisser déposer</div>
<div style="font-style: italic">ou</div>
<div style="font-style: italic" for="fileDropRef">Cliquer pour selectionner</div>
</div>
<div style="text-align: center;">
<mat-icon [title]=info_image>info</mat-icon>
</div>
<div class="files-list">
<div class="single-file" *ngFor="let file of files; let i = index">
<img src="/assets/uploadFile.png" width="45px" alt="file">
<div class="info">
<h4 class="name">
{{ file?.name }}
</h4>
<p class="size">
{{ formatBytes(file?.size) }}
</p>
<div class="progress-cont">
<div class="progress" [style.width]="file?.progress + '%'">
</div>
</div>
</div>
<button mat-icon-button class="delete" width="20px" alt="file" (click)="deleteFile(i)">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>

View file

@ -1,135 +0,0 @@
.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;
}
}

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DragAndDropComponent } from './drag-and-drop.component';
describe('DragAndDropComponent', () => {
let component: DragAndDropComponent;
let fixture: ComponentFixture<DragAndDropComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DragAndDropComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DragAndDropComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,93 +0,0 @@
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<any>();
/**
* 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<any>) {
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];
}
}

View file

@ -1,43 +0,0 @@
<mat-form-field class="example-chip-list" appearance="fill">
<!-- ------------------------------------------------------------------------------------ -->
<mat-label>Sujets</mat-label>
<!-- ------------------------------------------------------------------------------------ -->
<mat-chip-list #chipList aria-label="Fruit selection">
<mat-chip
*ngFor="let interest of myInterests"
[selectable]="selectable"
[removable]="removable"
(removed)="remove(interest)">
{{interest}}
<button matChipRemove *ngIf="removable">
<mat-icon>cancel</mat-icon>
</button>
</mat-chip>
<input
placeholder="Tapez un centre d'intérêt et pressez 'Entrer' pour l'inserer"
#tagInput
[formControl]="formControl"
[matAutocomplete]="auto"
[matChipInputFor]="chipList"
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
(matChipInputTokenEnd)="add($event)">
</mat-chip-list>
<!-- ------------------------------------------------------------------------------------ -->
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)">
<mat-option *ngFor="let interest of filteredInterests | async" [value]="interest">
{{interest}}
</mat-option>
</mat-autocomplete>
<!-- ------------------------------------------------------------------------------------ -->
</mat-form-field>

View file

@ -1,16 +0,0 @@
mat-form-field {
width: 100%;
font-size: small;
}
mat-chip-list {
font-size: small;
}
mat-chip {
font-size: small;
}
input {
font-size: small;
}

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { InputInterestsAdComponent } from './input-interests-ad.component';
describe('BarTagsComponent', () => {
let component: InputInterestsAdComponent;
let fixture: ComponentFixture<InputInterestsAdComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ InputInterestsAdComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(InputInterestsAdComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,121 +0,0 @@
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/services/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<string[]>;
@Input() myInterests: string[] = [];
allInterests: string[] = [];
@Output() eventEmitter = new EventEmitter<string[]>();
@ViewChild('tagInput') tagInput: ElementRef<HTMLInputElement>;
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));
}
}

View file

@ -1,185 +0,0 @@
<div [class]="themeService.getClassTheme()">
<div class="myContainer">
<!-- Navbar -->
<app-navbar-advertiser></app-navbar-advertiser><br><br>
<!-- filtre + btnAddUser -->
<div class="row" style="margin: 20px 3% 20px 3%">
<!-- filtre -->
<div class="col-10" style="padding: 0px 0px 0px 0px;">
<div class="filtersContainer mat-elevation-z8">
<!-- titre -->
<div style="font-weight: bold; margin-bottom: 10px;">
Filtre
</div>
<mat-divider></mat-divider>
<!-- filtre textuelle-->
<div style="margin: 10px 0px 20px 2%;">
<input class="textFilter" (keyup)="applyFilter($event)" placeholder="filtre...">
</div>
<!-- role + actif + période -->
<div class="row myRow">
<!-- visible -->
<div class="col-2">
<mat-checkbox [(ngModel)]="visible" (change)="onFilter()">visible</mat-checkbox><br>
<mat-checkbox [(ngModel)]="noVisible" (change)="onFilter()">non visible</mat-checkbox>
</div>
<!-- sujets -->
<div class="col-4">
<mat-form-field appearance="fill">
<mat-label>Sujets</mat-label>
<mat-select [formControl]="formControlInterests" multiple>
<mat-select-trigger>
{{formControlInterests.value ? formControlInterests.value[0] : ''}}
<span *ngIf="formControlInterests.value?.length > 1">
(+{{formControlInterests.value.length - 1}} {{formControlInterests.value?.length === 2 ? 'autre' : 'autres'}})
</span>
</mat-select-trigger>
<mat-option *ngFor="let topping of allInterests" [value]="topping">{{topping}}</mat-option>
</mat-select>
</mat-form-field>
<button mat-icon-button (click)="onFilter()">
<mat-icon>keyboard_tab</mat-icon>
</button>
</div>
<!-- période -->
<div class="col-6" style="text-align: right;">
Période de création: &nbsp;
<mat-form-field appearance="fill" style="width: 140px;">
<mat-label>Date de début</mat-label>
<input matInput type="date"
style="font-size: small; width: 140px;"
[ngModel] ="startDate | date:'yyyy-MM-dd'"
(ngModelChange)="onNewStartDate($event); onFilter();">
</mat-form-field>
&nbsp; - &nbsp;
<mat-form-field appearance="fill" style="width: 140px;">
<mat-label>Date de fin</mat-label>
<input matInput type="date"
style="font-size: small; width: 140px;"
[ngModel] ="endDate | date:'yyyy-MM-dd'"
(ngModelChange)="onNewEndDate($event); onFilter();">
</mat-form-field>
</div>
</div>
</div>
</div>
<!-- btnAdd -->
<div class="col-2" style="text-align: right; position: relative;">
<button mat-button class="btnAjouter" (click)="onAdd()" style="position: absolute; bottom: 0; right: 0;">
<mat-icon>add_circle</mat-icon> Ajouter une annonce
</button>
</div>
</div>
<!-- Table -->
<table mat-table [dataSource]="dataSource" matSort class="mat-elevation-z8">
<!-- IsActive Column -->
<ng-container matColumnDef="isVisible">
<th mat-header-cell *matHeaderCellDef mat-sort-header>
<mat-icon>power_settings_new</mat-icon>
</th>
<td mat-cell *matCellDef="let advert">
<mat-slide-toggle [(ngModel)]="advert.isVisible" (click)="onSliderIsVisible(advert)"></mat-slide-toggle>
</td>
</ng-container>
<!-- Title Column -->
<ng-container matColumnDef="title">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Titre </th>
<td mat-cell *matCellDef="let advert">
{{advert.title}}
</td>
</ng-container>
<!-- Subject Column -->
<ng-container matColumnDef="interests">
<th mat-header-cell *matHeaderCellDef> Sujets </th>
<td mat-cell *matCellDef="let advert">
<span *ngFor="let interest of advert.interests; let isLast = last;">
<span *ngIf="!isLast"> {{interest}}, </span>
<span *ngIf="isLast"> {{interest}} </span>
</span>
</td>
</ng-container>
<!-- CreatedAt Column -->
<ng-container matColumnDef="createdAt">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Date de création </th>
<td mat-cell *matCellDef="let advert">
{{ advert.createdAt | date:'dd/LL/YYYY à HH:mm:ss' }}
</td>
</ng-container>
<!-- LastUpdate Column -->
<ng-container matColumnDef="updatedAt">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Dernière modification </th>
<td mat-cell *matCellDef="let advert">
{{ advert.updatedAt | date:'dd/LL/YYYY à HH:mm:ss' }}
</td>
</ng-container>
<!-- Views Column -->
<ng-container matColumnDef="countViews">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Vues </th>
<td mat-cell *matCellDef="let advert">
{{advert.countViews}}
</td>
</ng-container>
<!-- Actions Column -->
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef> Actions </th>
<td mat-cell *matCellDef="let advert">
<button mat-icon-button (click)="onVisualizeImages(advert)">
<mat-icon> insert_photo</mat-icon>
</button>
<button mat-icon-button (click)="onUpdate(advert)">
<mat-icon>edit</mat-icon>
</button>
<button mat-icon-button (click)="onDelete(advert)">
<mat-icon>delete</mat-icon>
</button>
</td>
</ng-container>
<!-- Directives -->
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr class="mat-row" *matNoDataRow>
<td class="mat-cell" colspan="4"> Aucune vidéo ne correspond au filtre: "{{input.value}}" </td>
</tr>
</table>
<div style="width: 94%; margin: auto auto">
<mat-paginator [pageSizeOptions]="[10, 20, 50, 100]" showFirstLastButtons aria-label="Select page of periodic elements"></mat-paginator>
</div>
<br><br>
</div>
</div>

View file

@ -1,87 +0,0 @@
.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;
}

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PageAdListAdvertiserComponent } from './page-ad-list-advertiser.component';
describe('PageAdvertiserComponent', () => {
let component: PageAdListAdvertiserComponent;
let fixture: ComponentFixture<PageAdListAdvertiserComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PageAdListAdvertiserComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PageAdListAdvertiserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,309 +0,0 @@
import {AfterViewInit, Component, OnInit, ViewChild} from '@angular/core';
import {MatSort} from "@angular/material/sort";
import {ThemeService} from "../../../utils/services/theme/theme.service";
import {MatTableDataSource} from "@angular/material/table";
import {AdvertWithCountViews} from "../../../utils/interfaces/advert";
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 {FictitiousAdvertsService} from "../../../utils/services/fictitiousDatas/fictitiousAdverts/fictitious-adverts.service";
import {FormControl} from "@angular/forms";
import {FictitiousUtilsService} from "../../../utils/services/fictitiousDatas/fictitiousUtils/fictitious-utils.service";
import {MessageService} from "../../../utils/services/message/message.service";
import {HttpParams} from "@angular/common/http";
@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: AdvertWithCountViews[] = [];
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,
private fictitiousAdvertsService: FictitiousAdvertsService,
private fictitiousUtilsService: FictitiousUtilsService,
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<AdvertWithCountViews>();
this.onFilter();
}
}
}
applyFilter(event: Event): void
{
const filterValue = (event.target as HTMLInputElement).value;
this.dataSource.filter = filterValue.trim().toLowerCase();
}
onVisualizeImages(advert: AdvertWithCountViews)
{
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: AdvertWithCountViews): 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: AdvertWithCountViews): 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): AdvertWithCountViews
{
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,
}
}
}

View file

@ -1,83 +0,0 @@
<div class="myContainer1">
<div class="myContainer2">
<!-- titre popup -->
<h1 mat-dialog-title>{{title}}</h1>
<mat-divider></mat-divider><br>
<!-- tous les champs -->
<div class="row">
<!-- title + interests + comments + isVisible -->
<div class="col-6">
<!-- Title -->
<mat-form-field class="titleContainer" appearance="fill">
<mat-label> Titre annonce </mat-label>
<input matInput type="text" [(ngModel)]="advert.title" required>
</mat-form-field>
<!-- Interests -->
<app-input-interests-ad [myInterests]="advert.interests" (eventEmitter)="onEventInputTags($event)"></app-input-interests-ad>
<!-- Comments -->
<mat-form-field class="commentContainer" appearance="fill">
<mat-label> Commentaire </mat-label>
<textarea matInput [(ngModel)]="advert.comment" rows="5" style="resize: none;"></textarea>
</mat-form-field><br>
<!-- url -->
<mat-form-field class="commentContainer" appearance="fill">
<mat-label> URL </mat-label>
<input matInput [(ngModel)]="advert.url">
</mat-form-field><br>
<!-- IsVisible -->
<mat-checkbox [(ngModel)]="advert.isVisible"> Visible </mat-checkbox><br><br>
<!-- Images déjà présentes -->
<div *ngIf="advert.images.length !== 0">
<div style="font-weight: bold; margin-bottom: 5px;">
Images déjà associées:
</div>
<div style="margin-left: 20px; padding-left: 2px; border-left: solid 1px #a4a4a4">
<div *ngFor="let image of advert.images" style="padding: 2px 0px 2px 0px;">
<mat-chip [selectable]="true" [removable]="true" style="font-size: small;">
{{image.description}}
<button matChipRemove (click)="onRemoveImgAlreadyPresent(image)">
<mat-icon>cancel</mat-icon>
</button>
</mat-chip>
</div>
</div>
</div>
</div>
<!-- nouvelles images -->
<div class="col-6" style="overflow-x: hidden; overflow-y: scroll; max-height: 70vh;">
<app-drag-and-drop (eventEmitter)="onReceiveNewImages($event)"></app-drag-and-drop>
</div>
</div>
<br><mat-divider></mat-divider>
<!-- message d'erreur -->
<div *ngIf="hasError" style="text-align: center; margin-bottom: 20px;">
<span class="mat-error">{{errorMessage}}</span>
</div>
<!-- Actions -->
<mat-dialog-actions align="end">
<button mat-button (click)="dialogRef.close()">Annuler</button>
<button mat-button (click)="onValidate()">Valider</button>
</mat-dialog-actions>
</div>
</div>

View file

@ -1,90 +0,0 @@
.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;
}

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PopupAddOrUpdateAdComponent } from './popup-add-or-update-ad.component';
describe('PopupAddOrUpdateAdComponent', () => {
let component: PopupAddOrUpdateAdComponent;
let fixture: ComponentFixture<PopupAddOrUpdateAdComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PopupAddOrUpdateAdComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PopupAddOrUpdateAdComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,221 +0,0 @@
import {Component, Inject, OnInit} from '@angular/core';
import {Advert} from "../../../utils/interfaces/advert";
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {MessageService} from "../../../utils/services/message/message.service";
import {ThemeService} from "../../../utils/services/theme/theme.service";
const ADVERT_VIDE: Advert = {
_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<PopupAddOrUpdateAdComponent>,
@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<this.tabOfNewImagesName.length ; i++)
{
let newImagePrepared = {
base64: this.tabOfNewImagesBase64[i],
url: "",
description: this.tabOfNewImagesName[i],
type: 0,
};
this.advert.images.push(newImagePrepared);
}
}
// Met bien en forme les "interests" avant d'être envoyer
prepareAdvertInterests(): void
{
let interests = [];
for (let interest of this.advert.interests) {
for (let videoCategorie of this.allVideoCategorie) {
if (videoCategorie.interest === interest) {
interests.push(videoCategorie);
break;
}
}
}
this.advert.interests = interests;
}
}

View file

@ -1,8 +0,0 @@
<mat-dialog-content class="mat-typography">
Êtes-vous sûr de vouloir supprimer l'annonce <i>{{advert.title}}</i> ?
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button (click)="dialogRef.close();">Annuler</button>
<button mat-button (click)="onValidate()" cdkFocusInitial>Valider</button>
</mat-dialog-actions>

View file

@ -1,25 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PopupDeleteAdAdvertiserComponent } from './popup-delete-ad-advertiser.component';
describe('PopupDeleteAdComponent', () => {
let component: PopupDeleteAdAdvertiserComponent;
let fixture: ComponentFixture<PopupDeleteAdAdvertiserComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PopupDeleteAdAdvertiserComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PopupDeleteAdAdvertiserComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -1,47 +0,0 @@
import {Component, Inject, OnInit} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
import {MessageService} from "../../../utils/services/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<PopupDeleteAdAdvertiserComponent>,
@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);
}
}
}

View file

@ -1,71 +0,0 @@
<div [class]="themeService.getClassTheme()">
<h1 mat-dialog-title>{{advert.title}}</h1>
<!-- ----------------------------------------------------------------------------------------------------------------- -->
<mat-divider></mat-divider>
<mat-dialog-content>
<!-- Images -->
<div class="row myRow">
<div class="col-6 myLabel"> Images: </div>
<div class="col-6 myValue" style="border-left: solid 1px #e6e6e6">
<div *ngFor="let image of advert.images"> {{image.url}} </div>
</div>
</div>
<!-- Tags -->
<div class="row myRow">
<div class="col-6 myLabel"> Centre d'intérêt :</div>
<div class="col-6 myValue" style="border-left: solid 1px #e6e6e6">
<div *ngFor="let tag of advert.interests"> • {{tag}} </div>
</div>
</div>
<!-- Comment -->
<div class="row myRow">
<div class="col-6 myLabel"> Commentaire: </div>
<div class="col-6 myValue"> {{advert.comment}} </div>
</div>
<!-- Views -->
<div class="row myRow">
<label class="col-6 myLabel"> Vues: </label>
<div class="col-6 myValue"> {{advert.views}} </div>
</div>
<!-- Created at -->
<div class="row myRow">
<label class="col-6 myLabel"> Date de création: </label>
<div class="col-6 myValue">
{{ advert.createdAt | date:'dd/LL/YYYY à HH:mm:ss' }}
</div>
</div>
<!-- Last updtade -->
<div class="row myRow">
<label class="col-6 myLabel"> Date de dernière modification: </label>
<div class="col-6 myValue">
{{ advert.updatedAt | date:'dd/LL/YYYY à HH:mm:ss' }}
</div>
</div>
<!-- IsVisible -->
<div class="row myRow">
<label class="col-6 myLabel"> Visibilité: </label>
<div class="col-6 myValue">
<mat-icon *ngIf="advert.isVisible">checked</mat-icon>
<mat-icon *ngIf="!advert.isVisible">close</mat-icon>
</div>
</div>
</mat-dialog-content>
<!-- ----------------------------------------------------------------------------------------------------------------- -->
<mat-dialog-actions align="end">
<button mat-button (click)="dialogRef.close()">Fermer</button>
</mat-dialog-actions>
</div>

Some files were not shown because too many files have changed in this diff Show more