user managment
This commit is contained in:
parent
bafef0f6b3
commit
0c7fc6fdf7
|
|
@ -1,4 +1,5 @@
|
|||
backend/node_modules
|
||||
backend/.Keys.js
|
||||
|
||||
client/.angular
|
||||
client/node_modules
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
module.exports = {
|
||||
db: {
|
||||
connectionString: "mongodb://db:27017",
|
||||
dbName: "jucundus",
|
||||
},
|
||||
session: {
|
||||
sessionCollection: "Session",
|
||||
sessionConfig: {
|
||||
name: "jucundus.sid",
|
||||
cookie: {
|
||||
maxAge: 1000 * 60 * 60 * 24 * 30, // 30 days
|
||||
httpOnly: true, // only accessible by the server
|
||||
secure: true,
|
||||
},
|
||||
resave: false, // don't save session if unmodified
|
||||
saveUninitialized: true,
|
||||
}
|
||||
},
|
||||
jwtOptions: {
|
||||
issuer: "jucundus.com",
|
||||
audience: "yoursite",
|
||||
}
|
||||
};
|
||||
|
|
@ -38,7 +38,6 @@ exports.getPictures = asyncHandler(async (req, res, next) => {
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
exports.getLotsBySale = asyncHandler(async (req, res, next) => {
|
||||
let id = req.params.id
|
||||
|
||||
|
|
@ -165,3 +164,67 @@ exports.AuctionedItem = asyncHandler(async (req, res, next) => {
|
|||
}
|
||||
});
|
||||
|
||||
// DB
|
||||
exports.get = asyncHandler(async (req, res, next) => {
|
||||
|
||||
try{
|
||||
const id = req.params.id;
|
||||
let result = await lotDb.get(id);
|
||||
res.status(200).send(result);
|
||||
}catch(err){
|
||||
console.log(err);
|
||||
return res.status(500).send(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
exports.post = asyncHandler(async (req, res, next) => {
|
||||
|
||||
try{
|
||||
// check if double
|
||||
let Lot = await lotDb.getByIDPlatform(req.body.idPlatform, req.body.platform);
|
||||
if(Sale){
|
||||
return res.status(500).send("Lot already exists");
|
||||
}
|
||||
|
||||
let createLot = await lotDb.post(req.body);
|
||||
|
||||
res.status(204).send();
|
||||
}catch(err){
|
||||
console.log(err);
|
||||
return res.status(500).send(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
exports.put = asyncHandler(async (req, res, next) => {
|
||||
|
||||
try{
|
||||
const id = req.params.id;
|
||||
let updatedDocument = { ...req.body };
|
||||
delete updatedDocument._id;
|
||||
console.log(updatedDocument);
|
||||
let result = await lotDb.put(id, updatedDocument);
|
||||
console.log(result);
|
||||
res.status(200).send(result);
|
||||
}catch(err){
|
||||
console.log(err);
|
||||
return res.status(500).send(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
exports.delete = asyncHandler(async (req, res, next) => {
|
||||
try{
|
||||
const id = req.params.id;
|
||||
|
||||
// Remove the lot
|
||||
await lotDb.remove(id);
|
||||
|
||||
res.status(200).send({"message": "Lots deleted"});
|
||||
}catch(err){
|
||||
console.log(err);
|
||||
return res.status(500).send(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -8,7 +8,7 @@ const lotDb = new LotDb();
|
|||
const agenda = require('../services/agenda');
|
||||
const {Agent} = require('../services/agent');
|
||||
const agent = new Agent();
|
||||
|
||||
const ExcelJS = require('exceljs');
|
||||
|
||||
exports.getSaleInfos = asyncHandler(async (req, res, next) => {
|
||||
let url = req.params.url
|
||||
|
|
@ -218,31 +218,102 @@ exports.postProcessing = asyncHandler(async (req, res, next) => {
|
|||
}
|
||||
|
||||
Lots = await lotDb.getBySaleId(Sale._id.toString(),Sale.platform);
|
||||
|
||||
TimestampInSecond = (timestamp) => {
|
||||
const stringTimestamp = String(timestamp);
|
||||
if (stringTimestamp.length === 13) {
|
||||
return timestamp / 1000;
|
||||
} else if (stringTimestamp.length === 10) {
|
||||
return timestamp;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Create an array to hold the updated lots
|
||||
let updatedLots = [];
|
||||
let bidsDuration = 0;
|
||||
|
||||
// process each lot
|
||||
for (let lot of Lots) {
|
||||
let highestBid, duration, percentageAboveUnderLow, percentageAboveUnderHigh = 0;
|
||||
|
||||
// if bid
|
||||
let nbrBids = 0;
|
||||
if (Array.isArray(lot.Bids)) {
|
||||
|
||||
nbrBids = lot.Bids.length;
|
||||
|
||||
highestBid = lot.Bids.reduce((prev, current) => (prev.amount > current.amount) ? prev : current).amount;
|
||||
let startTime = TimestampInSecond(lot.Bids[0].timestamp);
|
||||
let endTime = TimestampInSecond(lot.Bids[lot.Bids.length-1].timestamp);
|
||||
duration = endTime - startTime;
|
||||
|
||||
// total time of bids
|
||||
bidsDuration += duration;
|
||||
|
||||
duration = duration.toFixed(0);
|
||||
}
|
||||
|
||||
// if auctioned
|
||||
percentageAboveUnderLow = 0;
|
||||
percentageAboveUnderHigh = 0;
|
||||
if (lot.auctioned) {
|
||||
|
||||
if(lot.EstimateLow){
|
||||
percentageAboveUnderLow = ((lot.auctioned.amount - lot.EstimateLow) / lot.EstimateLow) * 100;
|
||||
}
|
||||
|
||||
if(lot.EstimateHigh){
|
||||
percentageAboveUnderHigh = ((lot.auctioned.amount - lot.EstimateHigh) / lot.EstimateHigh) * 100;
|
||||
}
|
||||
}
|
||||
|
||||
let lotPostProcessing = {
|
||||
nbrBids: nbrBids,
|
||||
highestBid: highestBid,
|
||||
duration: duration,
|
||||
percentageAboveUnderLow: percentageAboveUnderLow.toFixed(0),
|
||||
percentageAboveUnderHigh: percentageAboveUnderHigh.toFixed(0)
|
||||
}
|
||||
lot.postProcessing = lotPostProcessing;
|
||||
await lotDb.put(lot._id, lot);
|
||||
|
||||
// Add the updated lot to the array
|
||||
updatedLots.push(lot);
|
||||
}
|
||||
|
||||
// refresh with postprocess datas
|
||||
Lots = updatedLots;
|
||||
|
||||
|
||||
let startTime = 0;
|
||||
if (Array.isArray(Lots[0].Bids)) {
|
||||
startTime = Lots[0].Bids[0].timestamp;
|
||||
startTime = TimestampInSecond(Lots[0].Bids[0].timestamp);
|
||||
}else{
|
||||
startTime = Lots[0].timestamp;
|
||||
startTime = TimestampInSecond(Lots[0].timestamp);
|
||||
}
|
||||
|
||||
let LastBid = [...Lots].reverse().find(lot => lot.auctioned !== undefined);
|
||||
|
||||
let endTime = 0;
|
||||
if (Array.isArray(LastBid.Bids)) {
|
||||
endTime = LastBid.Bids[LastBid.Bids.length-1].timestamp;
|
||||
endTime = TimestampInSecond(LastBid.Bids[LastBid.Bids.length-1].timestamp);
|
||||
}else{
|
||||
endTime = LastBid.timestamp;
|
||||
endTime = TimestampInSecond(LastBid.timestamp);
|
||||
}
|
||||
console.log("Start Time: "+startTime);
|
||||
console.log("End Time: "+endTime);
|
||||
|
||||
let duration = endTime-startTime;
|
||||
let duration = (endTime-startTime).toFixed(0);
|
||||
|
||||
let totalAmount = 0;
|
||||
let unsoldLots = 0;
|
||||
for (let lot of Lots) {
|
||||
if (lot.auctioned) {
|
||||
totalAmount += lot.auctioned.amount;
|
||||
} else {
|
||||
unsoldLots++;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -262,10 +333,15 @@ exports.postProcessing = asyncHandler(async (req, res, next) => {
|
|||
let postProcessing = {
|
||||
nbrLots: Lots.length,
|
||||
duration: duration,
|
||||
bidsDuration: bidsDuration.toFixed(0),
|
||||
durationPerLots: (duration/Lots.length).toFixed(0),
|
||||
totalAmount: totalAmount,
|
||||
averageAmount: (totalAmount/Lots.length).toFixed(2),
|
||||
medianAmount: calculateMedian(amounts).toFixed(2),
|
||||
minAmount: Math.min(...amounts).toFixed(2),
|
||||
maxAmount: Math.max(...amounts).toFixed(2),
|
||||
unsoldLots: unsoldLots,
|
||||
unsoldPercentage: ((unsoldLots/Lots.length)*100).toFixed(2)
|
||||
}
|
||||
|
||||
console.log(postProcessing);
|
||||
|
|
@ -279,4 +355,86 @@ exports.postProcessing = asyncHandler(async (req, res, next) => {
|
|||
return res.status(500).send(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
exports.SaleStatXsl = asyncHandler(async (req, res, next) => {
|
||||
try{
|
||||
const id = req.params.id;
|
||||
|
||||
Sale = await saleDb.get(id);
|
||||
if(!Sale){
|
||||
console.error("Sale not found");
|
||||
return res.status(404).send("Sale not found");
|
||||
}
|
||||
|
||||
Lots = await lotDb.getBySaleId(Sale._id.toString(),Sale.platform);
|
||||
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const worksheet = workbook.addWorksheet('Sale Stats');
|
||||
|
||||
worksheet.columns = [
|
||||
{ header: 'Lot #', key: 'lotNumber', width: 10 },
|
||||
{ header: 'Description', key: 'description', width: 100 },
|
||||
{ header: 'Auctioned Amount', key: 'auctionedAmount', width: 20 },
|
||||
{ header: 'Estimate Low', key: 'estimateLow', width: 20 },
|
||||
{ header: 'Estimate High', key: 'estimateHigh', width: 20 },
|
||||
{ header: 'Bids', key: 'nbrBids', width: 10 },
|
||||
{ header: 'Highest Bid', key: 'highestBid', width: 20 },
|
||||
{ header: 'Duration (in s)', key: 'duration', width: 10 },
|
||||
{ header: '% Above Low', key: 'percentageAboveUnderLow', width: 20 },
|
||||
{ header: '% Above High', key: 'percentageAboveUnderHigh', width: 20 }
|
||||
];
|
||||
|
||||
let row = 2;
|
||||
for (let lot of Lots) {
|
||||
let Row = worksheet.addRow({
|
||||
lotNumber: lot.lotNumber,
|
||||
description: lot.description,
|
||||
auctionedAmount: lot.auctioned?.amount,
|
||||
estimateLow: lot.EstimateLow,
|
||||
estimateHigh: lot.EstimateHigh,
|
||||
nbrBids: lot.postProcessing?.nbrBids,
|
||||
highestBid: lot.postProcessing?.highestBid,
|
||||
duration: lot.postProcessing?.duration,
|
||||
percentageAboveUnderLow: lot.postProcessing?.percentageAboveUnderLow/100,
|
||||
percentageAboveUnderHigh: lot.postProcessing?.percentageAboveUnderHigh/100
|
||||
});
|
||||
|
||||
Row.getCell('C').numFmt = '€0.00';
|
||||
Row.getCell('D').numFmt = '€0.00';
|
||||
Row.getCell('E').numFmt = '€0.00';
|
||||
Row.getCell('B').numFmt = '€0.00';
|
||||
Row.getCell('G').numFmt = '€0.00';
|
||||
|
||||
Row.getCell('I').numFmt = '0%';
|
||||
Row.getCell('J').numFmt = '0%';
|
||||
|
||||
row++;
|
||||
}
|
||||
|
||||
worksheet.addRow({
|
||||
lotNumber: 'Total',
|
||||
auctionedAmount: Sale.postProcessing?.totalAmount,
|
||||
estimateLow: '',
|
||||
estimateHigh: '',
|
||||
nbrBids: '',
|
||||
highestBid: '',
|
||||
duration: Sale.postProcessing?.duration,
|
||||
percentageAboveUnderLow: '',
|
||||
percentageAboveUnderHigh: ''
|
||||
});
|
||||
|
||||
// send the Xls File
|
||||
res.setHeader(
|
||||
"Content-Disposition",
|
||||
"attachment; filename=" + "SaleStats.xlsx"
|
||||
);
|
||||
|
||||
await workbook.xlsx.write(res);
|
||||
return res.status(200).end();
|
||||
|
||||
}catch(err){
|
||||
console.log(err);
|
||||
return res.status(500).send
|
||||
}
|
||||
});
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
const asyncHandler = require("express-async-handler");
|
||||
const moment = require('moment-timezone');
|
||||
const { ObjectId } = require('mongodb');
|
||||
const { UserDb } = require("../services/userDb");
|
||||
const crypto = require('crypto');
|
||||
|
||||
function ClearUserData(user){
|
||||
delete user.salt;
|
||||
delete user.hashed_password;
|
||||
delete user.salt;
|
||||
delete user.isAgent;
|
||||
return user;
|
||||
}
|
||||
|
||||
function ClearUserDataForAdmin(user){
|
||||
delete user.salt;
|
||||
delete user.hashed_password;
|
||||
delete user.salt;
|
||||
return user;
|
||||
}
|
||||
|
||||
// DB
|
||||
exports.get = asyncHandler(async (req, res, next) => {
|
||||
|
||||
try{
|
||||
const userDb = await UserDb.init();
|
||||
const id = req.params.id;
|
||||
let result = await userDb.get(id);
|
||||
if (req.user.isAdmin){
|
||||
res.status(200).send(ClearUserDataForAdmin(result));
|
||||
}else{
|
||||
res.status(200).send(ClearUserData(result));
|
||||
}
|
||||
}catch(err){
|
||||
console.log(err);
|
||||
return res.status(500).send(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
exports.post = asyncHandler(async (req, res, next) => {
|
||||
|
||||
try{
|
||||
const userDb = await UserDb.init();
|
||||
// check if double
|
||||
let User = await userDb.getByEmail(req.body.email);
|
||||
if(User){
|
||||
return res.status(500).send("User already exists");
|
||||
}
|
||||
|
||||
// check password
|
||||
if(!req.body.password){
|
||||
return res.status(500).send("Password not set");
|
||||
}
|
||||
if(req.body.password != req.body.confirmPassword){
|
||||
return res.status(500).send("Passwords do not match");
|
||||
}
|
||||
if(req.body.password.length < 8){
|
||||
return res.status(500).send("Password too short");
|
||||
}
|
||||
|
||||
if(req.body.isAdmin){
|
||||
if(req.user){
|
||||
if(!req.user.isAdmin){
|
||||
return res.status(500).send("You are not allowed to create an admin user");
|
||||
}
|
||||
}else{
|
||||
req.body.isAdmin = false
|
||||
}
|
||||
}
|
||||
|
||||
if(req.body.isAgent){
|
||||
if(req.user){
|
||||
if(!req.user.isAgent){
|
||||
return res.status(500).send("You are not allowed to create an agent user");
|
||||
}
|
||||
}else{
|
||||
req.body.isAgent = false
|
||||
}
|
||||
}
|
||||
|
||||
let salt = crypto.randomBytes(16).toString('hex');
|
||||
let user = {
|
||||
username: req.body.username,
|
||||
hashed_password: crypto.pbkdf2Sync(req.body.password, salt, 310000, 32, 'sha256').toString('hex'),
|
||||
salt: salt,
|
||||
email: req.body.email,
|
||||
isAdmin: req.body.isAdmin,
|
||||
isAgent: req.body.isAgent,
|
||||
}
|
||||
|
||||
let createData = await userDb.post(user);
|
||||
|
||||
res.status(204).send();
|
||||
}catch(err){
|
||||
console.log(err);
|
||||
return res.status(500).send(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
exports.put = asyncHandler(async (req, res, next) => {
|
||||
|
||||
try{
|
||||
const userDb = await UserDb.init();
|
||||
const id = req.params.id;
|
||||
|
||||
const User = await userDb.get(id);
|
||||
if(!User){
|
||||
return res.status(500).send("User not found");
|
||||
}
|
||||
|
||||
// check password
|
||||
let hashed_password = "";
|
||||
let salt = "";
|
||||
if(req.body.password){
|
||||
if(req.body.password != req.body.confirmPassword){
|
||||
return res.status(500).send("Passwords do not match");
|
||||
}
|
||||
if(req.body.password.length < 8){
|
||||
return res.status(500).send("Password too short");
|
||||
}
|
||||
salt = crypto.randomBytes(16).toString('hex');
|
||||
hashed_password = crypto.pbkdf2Sync(req.body.password, salt, 310000, 32, 'sha256').toString('hex');
|
||||
}else{
|
||||
salt = User.salt;
|
||||
hashed_password = User.hashed_password;
|
||||
}
|
||||
|
||||
if(req.body.isAdmin){
|
||||
if(!req.user.isAdmin){
|
||||
return res.status(500).send("You are not allowed to create an admin user");
|
||||
}
|
||||
}
|
||||
if(req.body.isAgent){
|
||||
if(!req.user.isAdmin){
|
||||
return res.status(500).send("You are not allowed to create an agent user");
|
||||
}
|
||||
}
|
||||
|
||||
let user = {
|
||||
username: req.body.username,
|
||||
hashed_password: hashed_password,
|
||||
salt: salt,
|
||||
email: req.body.email,
|
||||
isAdmin: req.body.isAdmin,
|
||||
isAgent: req.body.isAgent,
|
||||
}
|
||||
|
||||
let result = await userDb.put(id, user);
|
||||
console.log(result);
|
||||
res.status(200).send(result);
|
||||
}catch(err){
|
||||
console.log(err);
|
||||
return res.status(500).send(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
exports.delete = asyncHandler(async (req, res, next) => {
|
||||
try{
|
||||
const userDb = await UserDb.init();
|
||||
const id = req.params.id;
|
||||
|
||||
// Remove the sale
|
||||
await userDb.remove(id);
|
||||
|
||||
res.status(200).send({"message": "User deleted"});
|
||||
}catch(err){
|
||||
console.log(err);
|
||||
return res.status(500).send(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// Functions
|
||||
exports.current = asyncHandler(async (req, res, next) => {
|
||||
try{
|
||||
const user = ClearUserData(req.user);
|
||||
res.status(200).send(user);
|
||||
}catch(err){
|
||||
console.log(err);
|
||||
return res.status(500).send(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
exports.getAllUsers = asyncHandler(async (req, res, next) => {
|
||||
try{
|
||||
const userDb = await UserDb.init();
|
||||
let result = await userDb.getAll();
|
||||
result = result.map(user => ClearUserDataForAdmin(user));
|
||||
res.status(200).send(result);
|
||||
}catch(err){
|
||||
console.log(err);
|
||||
return res.status(500).send(err);
|
||||
}
|
||||
|
||||
});
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
const config = require("./config.js");
|
||||
const express = require('express')
|
||||
const app = express()
|
||||
|
||||
|
|
@ -9,17 +10,60 @@ app.use(cors());
|
|||
// Enable preflight requests for all routes
|
||||
app.options('*', cors());
|
||||
|
||||
|
||||
// Session support
|
||||
const session = require('express-session');
|
||||
app.use(session({
|
||||
secret: 'jucundus.ses',
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: { secure: true }
|
||||
}))
|
||||
// const MongoStore = require('connect-mongo');
|
||||
|
||||
// const keys = require("./.Keys.js");
|
||||
|
||||
// const sassionConfig = {
|
||||
// ...config.session.sessionConfig,
|
||||
// secret: keys.session,
|
||||
// store: MongoStore.create({
|
||||
// mongoUrl: `${config.db.connectionString}/${config.db.dbName}`,
|
||||
// collection: config.session.sessionCollection,
|
||||
// stringify: false,
|
||||
// autoReconnect: true,
|
||||
// autoRemove: 'native'
|
||||
// })};
|
||||
|
||||
// app.use(session(sassionConfig));
|
||||
|
||||
// Authentication
|
||||
const passport = require('passport');
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
app.use('/', require('./routes/auth'));
|
||||
|
||||
|
||||
// Agenda Scheduller
|
||||
const agenda = require('./services/agenda');
|
||||
(async function() {
|
||||
|
||||
//lunch sheduller
|
||||
await agenda.start();
|
||||
})();
|
||||
|
||||
|
||||
//create first user
|
||||
const { UserDb } = require('./services/userDb');
|
||||
const userDb = await UserDb.init();
|
||||
userDb.creatFirstUserifEmpty();
|
||||
|
||||
})();
|
||||
|
||||
// Agenda UI
|
||||
var Agendash = require("agendash");
|
||||
app.use("/dash", Agendash(agenda));
|
||||
|
||||
// routes
|
||||
app.use('/api/user', require('./routes/user'));
|
||||
app.use('/api/lot', require('./routes/lot'));
|
||||
app.use('/api/sale', require('./routes/sale'));
|
||||
app.use('/api/favorite', require('./routes/favorite'));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
|
||||
function checkIsConcernedUserOrAdmin(req, res, next) {
|
||||
const user = req.user; // User is set by Passport
|
||||
const userIdParam = req.params.id;
|
||||
|
||||
if (user.isAdmin === true || user._id === userIdParam) {
|
||||
next();
|
||||
} else {
|
||||
res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
}
|
||||
|
||||
function checkIsAdmin(req, res, next) {
|
||||
const user = req.user; // User is set by Passport
|
||||
|
||||
if (user.isAdmin === true) {
|
||||
next();
|
||||
} else {
|
||||
res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
}
|
||||
|
||||
function checkIsAgent(req, res, next) {
|
||||
const user = req.user; // User is set by Passport
|
||||
|
||||
if (user.isAgent === true) {
|
||||
next();
|
||||
} else {
|
||||
res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
}
|
||||
module.exports = { checkIsConcernedUserOrAdmin, checkIsAgent, checkIsAdmin };
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -6,7 +6,7 @@
|
|||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon --watch ./ server.js --ignore node_modules/"
|
||||
"dev": "nodemon --inspect=0.0.0.0 --watch ./ server.js --ignore node_modules/"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
|
@ -15,11 +15,18 @@
|
|||
"@angular/cli": "^17.1.3",
|
||||
"@hokify/agenda": "^6.3.0",
|
||||
"agendash": "^4.0.0",
|
||||
"connect-mongo": "^5.1.0",
|
||||
"cors": "^2.8.5",
|
||||
"exceljs": "^4.4.0",
|
||||
"express": "^4.18.2",
|
||||
"express-async-handler": "^1.2.0",
|
||||
"express-session": "^1.18.0",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"mongodb": "^6.5.0",
|
||||
"node-fetch": "^2.7.0"
|
||||
"node-fetch": "^2.7.0",
|
||||
"passport": "^0.7.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.3"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
var passport = require('passport');
|
||||
var LocalStrategy = require('passport-local');
|
||||
var crypto = require('crypto');
|
||||
const router = require('express').Router()
|
||||
const config = require("../config.js");
|
||||
const keys = require("../.Keys.js");
|
||||
|
||||
/* Configure password authentication strategy.
|
||||
*
|
||||
* The `LocalStrategy` authenticates users by verifying a username and password.
|
||||
* The strategy parses the username and password from the request and calls the
|
||||
* `verify` function.
|
||||
*
|
||||
* The `verify` function queries the database for the user record and verifies
|
||||
* the password by hashing the password supplied by the user and comparing it to
|
||||
* the hashed password stored in the database. If the comparison succeeds, the
|
||||
* user is authenticated; otherwise, not.
|
||||
*/
|
||||
const { UserDb }= require('../services/userDb');
|
||||
|
||||
passport.use(new LocalStrategy({
|
||||
usernameField: 'email',
|
||||
passwordField: 'password'
|
||||
},async function verify(email, password, cb) {
|
||||
try {
|
||||
const userDb = await UserDb.init();
|
||||
const user = await userDb.getByEmail(email);
|
||||
if (!user) {
|
||||
return cb(null, false, { message: 'Incorrect username or password.' });
|
||||
}
|
||||
|
||||
crypto.pbkdf2(password, user.salt, 310000, 32, 'sha256', async function(err, hashedPassword) {
|
||||
if (err) { return cb(err); }
|
||||
if (!crypto.timingSafeEqual(Buffer.from(user.hashed_password, 'hex'), Buffer.from(hashedPassword, 'hex'))) {
|
||||
return cb(null, false, { message: 'Incorrect username or password.' });
|
||||
}
|
||||
return cb(null, user);
|
||||
});
|
||||
} catch (err) {
|
||||
return cb(err);
|
||||
}
|
||||
}));
|
||||
|
||||
/* Configure session management.
|
||||
*
|
||||
* When a login session is established, information about the user will be
|
||||
* stored in the session. This information is supplied by the `serializeUser`
|
||||
* function, which is yielding the user ID and username.
|
||||
*
|
||||
* As the user interacts with the app, subsequent requests will be authenticated
|
||||
* by verifying the session. The same user information that was serialized at
|
||||
* session establishment will be restored when the session is authenticated by
|
||||
* the `deserializeUser` function.
|
||||
*
|
||||
* Since every request to the app needs the user ID and username, in order to
|
||||
* fetch todo records and render the user element in the navigation bar, that
|
||||
* information is stored in the session.
|
||||
*/
|
||||
passport.serializeUser(function(user, cb) {
|
||||
process.nextTick(function() {
|
||||
cb(null, { id: user._id, username: user.email });
|
||||
});
|
||||
});
|
||||
|
||||
passport.deserializeUser(function(user, cb) {
|
||||
process.nextTick(function() {
|
||||
return cb(null, user);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// JWT
|
||||
var jwt = require('jsonwebtoken');
|
||||
var JwtStrategy = require('passport-jwt').Strategy,
|
||||
ExtractJwt = require('passport-jwt').ExtractJwt;
|
||||
var opts = {}
|
||||
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
|
||||
opts.secretOrKey = keys.jwt;
|
||||
opts.issuer = config.jwtOptions.issuer;
|
||||
opts.audience = config.jwtOptions.audience;
|
||||
passport.use(new JwtStrategy(opts, async function(jwt_payload, done) {
|
||||
const userDb = await UserDb.init();
|
||||
try {
|
||||
const user = await userDb.get(jwt_payload.sub);
|
||||
if (user) {
|
||||
return done(null, user);
|
||||
} else {
|
||||
return done(null, false);
|
||||
// or you could create a new account
|
||||
}
|
||||
} catch (err) {
|
||||
return done(err, false);
|
||||
}
|
||||
}));
|
||||
|
||||
router.post('/authenticate', async function(req, res) {
|
||||
passport.authenticate('local', async function(err, user, info) {
|
||||
if (err) { return res.status(500).json({message: err.message}); }
|
||||
if (!user) { return res.status(401).json({message: 'Incorrect email or password.'}); }
|
||||
|
||||
// User found, generate a JWT for the user
|
||||
var token = jwt.sign({ sub: user._id, email: user.email }, opts.secretOrKey, {
|
||||
issuer: opts.issuer,
|
||||
audience: opts.audience,
|
||||
expiresIn: 86400 * 30 // 30 days
|
||||
});
|
||||
res.json({ token: token });
|
||||
//return res.status(500).json({message: 'Incorrect email or password.'})
|
||||
|
||||
})(req, res);
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
const controllers = require('../controllers/favorite')
|
||||
const router = require('express').Router()
|
||||
const passport = require('passport');
|
||||
const { checkIsConcernedUserOrAdmin } = require('../middleware/authMiddleware')
|
||||
|
||||
router.post('/save/', controllers.save)
|
||||
router.get('/getAll/', controllers.getAll)
|
||||
router.post('/save/', passport.authenticate('jwt', { session: false }), controllers.save)
|
||||
router.get('/getAll/', passport.authenticate('jwt', { session: false }), controllers.getAll)
|
||||
|
||||
module.exports = router
|
||||
|
|
@ -1,12 +1,21 @@
|
|||
const controllers = require('../controllers/lot')
|
||||
const router = require('express').Router()
|
||||
const passport = require('passport');
|
||||
const { checkIsAgent, checkIsAdmin } = require('../middleware/authMiddleware')
|
||||
|
||||
router.get('/getInfos/:url', controllers.getInfos)
|
||||
router.get('/getPictures/:url', controllers.getPictures)
|
||||
router.get('/getLotsBySale/:id', controllers.getLotsBySale)
|
||||
router.get('/getInfos/:url', passport.authenticate('jwt', { session: false }), controllers.getInfos)
|
||||
router.get('/getPictures/:url', passport.authenticate('jwt', { session: false }), controllers.getPictures)
|
||||
router.get('/getLotsBySale/:id', passport.authenticate('jwt', { session: false }), controllers.getLotsBySale)
|
||||
|
||||
router.post('/NextItem/', controllers.NextItem)
|
||||
router.post('/AuctionedItem/', controllers.AuctionedItem)
|
||||
router.post('/Bid/', controllers.Bid)
|
||||
// DB
|
||||
router.get('/lot/:id', passport.authenticate('jwt', { session: false }), checkIsAdmin, controllers.get)
|
||||
router.post('/lot/', passport.authenticate('jwt', { session: false }), checkIsAdmin, controllers.post)
|
||||
router.put('/lot/:id', passport.authenticate('jwt', { session: false }), checkIsAdmin, controllers.put)
|
||||
router.delete('/lot/:id', passport.authenticate('jwt', { session: false }), checkIsAdmin, controllers.delete)
|
||||
|
||||
// Live Data
|
||||
router.post('/NextItem/', checkIsAgent, controllers.NextItem)
|
||||
router.post('/AuctionedItem/', checkIsAgent, controllers.AuctionedItem)
|
||||
router.post('/Bid/', checkIsAgent, controllers.Bid)
|
||||
|
||||
module.exports = router
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
const controllers = require('../controllers/sale')
|
||||
const passport = require('passport');
|
||||
const router = require('express').Router()
|
||||
|
||||
// AuctionAgent
|
||||
|
|
@ -13,9 +14,11 @@ router.post('/sale/', controllers.post)
|
|||
router.put('/sale/:id', controllers.put)
|
||||
router.delete('/sale/:id', controllers.delete)
|
||||
|
||||
router.get('/getAll/', controllers.getAll)
|
||||
//router.get('/getAll/', controllers.getAll)
|
||||
router.get('/getAll/', passport.authenticate('jwt', { session: false }), controllers.getAll);
|
||||
router.get('/getByUrl/:url', controllers.getByUrl)
|
||||
router.get('/postProcessing/:id', controllers.postProcessing)
|
||||
router.get('/SaleStatXsl/:id', controllers.SaleStatXsl)
|
||||
|
||||
|
||||
module.exports = router
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
const controllers = require('../controllers/user')
|
||||
const passport = require('passport');
|
||||
const router = require('express').Router()
|
||||
const { checkIsConcernedUserOrAdmin, checkIsAdmin } = require('../middleware/authMiddleware')
|
||||
|
||||
// DB
|
||||
router.get('/user/:id', passport.authenticate('jwt', { session: false }), checkIsConcernedUserOrAdmin, controllers.get);
|
||||
router.post('/user/', controllers.post)
|
||||
router.put('/user/:id', passport.authenticate('jwt', { session: false }), checkIsConcernedUserOrAdmin, controllers.put)
|
||||
router.delete('/user/:id', passport.authenticate('jwt', { session: false }), checkIsConcernedUserOrAdmin, controllers.delete)
|
||||
|
||||
router.get('/current', passport.authenticate('jwt', { session: false }), controllers.current)
|
||||
router.get('/getAllUsers', passport.authenticate('jwt', { session: false }), checkIsAdmin, controllers.getAllUsers)
|
||||
|
||||
module.exports = router
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
const MongoClient = require("mongodb").MongoClient;
|
||||
const connectionString = "mongodb://db:27017";
|
||||
const config = require("../config.js");
|
||||
const connectionString = config.db.connectionString;
|
||||
const client = new MongoClient(connectionString);
|
||||
|
||||
let db;
|
||||
|
|
@ -8,7 +9,7 @@ const connectDb = async () => {
|
|||
if (db) return db;
|
||||
try {
|
||||
const conn = await client.connect();
|
||||
db = conn.db("jucundus");
|
||||
db = conn.db(config.db.dbName);
|
||||
return db;
|
||||
} catch(e) {
|
||||
console.error(e);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
const { ObjectId } = require('mongodb');
|
||||
const connectDb = require("./db");
|
||||
const crypto = require('crypto');
|
||||
|
||||
const UserDb = class
|
||||
{
|
||||
constructor()
|
||||
{
|
||||
}
|
||||
|
||||
static async init() {
|
||||
const userDb = new UserDb();
|
||||
await userDb.getCollection();
|
||||
return userDb;
|
||||
}
|
||||
|
||||
async getCollection()
|
||||
{
|
||||
const db = await connectDb();
|
||||
if (!db) {
|
||||
throw new Error('Database not connected');
|
||||
}
|
||||
this.collection = db.collection("Users");
|
||||
}
|
||||
|
||||
// CRUD
|
||||
async get(id)
|
||||
{
|
||||
let result = await this.collection.findOne({_id: new ObjectId(id)});
|
||||
return result;
|
||||
}
|
||||
|
||||
async post(newDocument)
|
||||
{
|
||||
delete newDocument._id;
|
||||
let result = await this.collection.insertOne(newDocument);
|
||||
return result;
|
||||
}
|
||||
|
||||
async put(id, data)
|
||||
{
|
||||
delete data._id;
|
||||
let result = await this.collection.updateOne({_id: new ObjectId(id)}, {$set: data});
|
||||
return result;
|
||||
}
|
||||
|
||||
async remove(id)
|
||||
{
|
||||
let result = await this.collection.deleteOne({_id: new ObjectId(id)});
|
||||
return result;
|
||||
}
|
||||
|
||||
// Functions
|
||||
|
||||
async getAll()
|
||||
{
|
||||
let result = await this.collection.find({}).toArray();
|
||||
return result;
|
||||
}
|
||||
|
||||
async getByUsername(username)
|
||||
{
|
||||
let result = await this.collection.findOne({username: username});
|
||||
return result;
|
||||
}
|
||||
async getByEmail(email)
|
||||
{
|
||||
let result = await this.collection.findOne({email: email});
|
||||
return result;
|
||||
}
|
||||
|
||||
async creatFirstUserifEmpty( ){
|
||||
const allUsers = await this.getAll();
|
||||
if(allUsers.length == 0){
|
||||
console.log("Creating first user");
|
||||
let salt = crypto.randomBytes(16).toString('hex');
|
||||
let user = {
|
||||
username: "admin",
|
||||
hashed_password: crypto.pbkdf2Sync('admin', salt, 310000, 32, 'sha256').toString('hex'),
|
||||
salt: salt,
|
||||
email: "admin@admin.com",
|
||||
isAdmin: true,
|
||||
isAgent: false,
|
||||
|
||||
}
|
||||
this.post(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { UserDb };
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { Injectable, Inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class apiAuthService {
|
||||
ServeurURL = environment.ServeurURL;
|
||||
ApiURL = this.ServeurURL+"/api";
|
||||
|
||||
|
||||
constructor(private http: HttpClient){}
|
||||
|
||||
// authenticate
|
||||
authenticate(email: string, password: string) {
|
||||
return this.http.post<{token: string}>(this.ServeurURL+'/authenticate', {email, password});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import { Injectable, Inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
|
||||
import { LotInfo } from '../model/lotInfo.interface';
|
||||
import { SaleInfo } from '../model/saleInfo.interface';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class apiFavoriteService {
|
||||
ServeurURL = environment.ServeurURL;
|
||||
ApiURL = this.ServeurURL+"/api";
|
||||
|
||||
constructor(private http: HttpClient){}
|
||||
|
||||
//Favorites
|
||||
saveFavorite(lotInfo: LotInfo, saleInfo: SaleInfo, picture: String, dateTime: string, buyProject: boolean, maxPrice: number, Note: string): Observable<any> {
|
||||
return this.http.post(this.ApiURL+'/favorite/save', {lotInfo, saleInfo, picture, dateTime, buyProject, maxPrice, Note});
|
||||
}
|
||||
|
||||
getAllFavorite(): Observable<any> {
|
||||
return this.http.get(this.ApiURL+'/favorite/getAll');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
import { Injectable, Inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
|
||||
import { LotInfo } from '../model/lotInfo.interface';
|
||||
import { Lot } from '../model/lot.interface';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class apiLotService {
|
||||
ServeurURL = environment.ServeurURL;
|
||||
ApiURL = this.ServeurURL+"/api";
|
||||
|
||||
constructor(private http: HttpClient){}
|
||||
|
||||
// Lot
|
||||
getLotInfo(url: string): Observable<LotInfo> {
|
||||
let encodeUrl = encodeURIComponent(url);
|
||||
return this.http.get<LotInfo>(this.ApiURL+'/lot/getInfos/'+encodeUrl);
|
||||
}
|
||||
|
||||
getPictures(url: string): Observable<String[]> {
|
||||
let encodeUrl = encodeURIComponent(url);
|
||||
return this.http.get<String[]>(this.ApiURL+'/lot/getPictures/'+encodeUrl);
|
||||
}
|
||||
|
||||
getLotsBySale(_id: String): Observable<Lot[]> {
|
||||
return this.http.get<Lot[]>(this.ApiURL+'/lot/getLotsBySale/'+_id);
|
||||
}
|
||||
|
||||
// Lot CRUD
|
||||
getLot(_id: String): Observable<Lot> {
|
||||
return this.http.get<Lot>(this.ApiURL+'/lot/lot/'+_id);
|
||||
}
|
||||
|
||||
saveLot(Lot: Lot): Observable<Lot> {
|
||||
return this.http.post<Lot>(this.ApiURL+'/lot/lot', Lot);
|
||||
}
|
||||
|
||||
updateLot(Lot: Lot): Observable<Lot> {
|
||||
return this.http.put<Lot>(this.ApiURL+'/lot/lot/'+Lot._id, Lot);
|
||||
}
|
||||
|
||||
deleteLot(_id: String): Observable<any> {
|
||||
return this.http.delete<any>(this.ApiURL+'/lot/lot/'+_id);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,34 +1,20 @@
|
|||
import { Injectable, Inject } from '@angular/core';
|
||||
import { Injectable} from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
|
||||
import { LotInfo } from './model/lotInfo.interface';
|
||||
import { SaleInfo } from './model/saleInfo.interface';
|
||||
import { Sale } from './model/sale.interface';
|
||||
import { Lot } from './model/lot.interface';
|
||||
import { SaleInfo } from '../model/saleInfo.interface';
|
||||
import { Sale } from '../model/sale.interface';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class apiService {
|
||||
ApiURL = "http://localhost:3000/api";
|
||||
export class apiSaleService {
|
||||
ServeurURL = environment.ServeurURL;
|
||||
ApiURL = this.ServeurURL+"/api";
|
||||
|
||||
constructor(private http: HttpClient){}
|
||||
|
||||
// Lot
|
||||
getLotInfo(url: string): Observable<LotInfo> {
|
||||
let encodeUrl = encodeURIComponent(url);
|
||||
return this.http.get<LotInfo>(this.ApiURL+'/lot/getInfos/'+encodeUrl);
|
||||
}
|
||||
|
||||
getPictures(url: string): Observable<String[]> {
|
||||
let encodeUrl = encodeURIComponent(url);
|
||||
return this.http.get<String[]>(this.ApiURL+'/lot/getPictures/'+encodeUrl);
|
||||
}
|
||||
|
||||
getLotsBySale(_id: String): Observable<Lot[]> {
|
||||
return this.http.get<Lot[]>(this.ApiURL+'/lot/getLotsBySale/'+_id);
|
||||
}
|
||||
constructor(
|
||||
private http: HttpClient){}
|
||||
|
||||
// Sale
|
||||
getSaleInfos(url: string): Observable<SaleInfo> {
|
||||
|
|
@ -74,24 +60,18 @@ export class apiService {
|
|||
return this.http.delete<any>(this.ApiURL+'/sale/sale/'+_id);
|
||||
}
|
||||
|
||||
// Function DB Sale
|
||||
|
||||
// Function DB Sale
|
||||
|
||||
getAllSale(): Observable<SaleInfo[]> {
|
||||
return this.http.get<SaleInfo[]>(this.ApiURL+'/sale/getAll');
|
||||
return this.http.get<SaleInfo[]>(`${this.ApiURL}/sale/getAll`);
|
||||
}
|
||||
|
||||
postProcessing(_id: String): Observable<any> {
|
||||
return this.http.get<any>(this.ApiURL+'/sale/postProcessing/'+_id);
|
||||
}
|
||||
|
||||
getSaleStatXsl(_id: String): Observable<any> {
|
||||
return this.http.get<Blob>(this.ApiURL+'/sale/SaleStatXsl/'+_id, { responseType: 'blob' as 'json' });
|
||||
}
|
||||
|
||||
|
||||
//Favorites
|
||||
saveFavorite(lotInfo: LotInfo, saleInfo: SaleInfo, picture: String, dateTime: string, buyProject: boolean, maxPrice: number, Note: string): Observable<any> {
|
||||
return this.http.post(this.ApiURL+'/favorite/save', {lotInfo, saleInfo, picture, dateTime, buyProject, maxPrice, Note});
|
||||
}
|
||||
|
||||
getAllFavorite(): Observable<any> {
|
||||
return this.http.get(this.ApiURL+'/favorite/getAll');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { Injectable} from '@angular/core';
|
||||
import { HttpClient} from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
|
||||
import { User } from '../model/user.interface';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class apiUserService {
|
||||
ServeurURL = environment.ServeurURL;
|
||||
ApiURL = this.ServeurURL+"/api";
|
||||
|
||||
constructor(
|
||||
private http: HttpClient){}
|
||||
|
||||
// User CRUD
|
||||
|
||||
getUser(_id: String): Observable<User> {
|
||||
return this.http.get<User>(this.ApiURL+'/user/user/'+_id);
|
||||
}
|
||||
|
||||
saveUser(User: User): Observable<User> {
|
||||
return this.http.post<User>(this.ApiURL+'/user/user/', User);
|
||||
}
|
||||
|
||||
updateUser(User: User): Observable<User> {
|
||||
return this.http.put<User>(this.ApiURL+'/user/user/'+User._id, User);
|
||||
}
|
||||
|
||||
deleteUser(_id: String): Observable<any> {
|
||||
return this.http.delete<any>(this.ApiURL+'/user/user/'+_id);
|
||||
}
|
||||
|
||||
// User function
|
||||
getCurrentUser(): Observable<User> {
|
||||
return this.http.get<User>(this.ApiURL+'/user/current');
|
||||
}
|
||||
|
||||
getAllUsers(): Observable<User[]> {
|
||||
return this.http.get<User[]>(this.ApiURL+'/user/getAllUsers');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import { Injectable, Inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { delay, map } from 'rxjs/operators';
|
||||
import * as jwt_decode from 'jwt-decode';
|
||||
import { apiAuthService } from './api/api.auth.service';
|
||||
import { apiUserService } from './api/api.user.service';
|
||||
import { User } from './model/user.interface';
|
||||
|
||||
import * as moment from 'moment';
|
||||
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { of, EMPTY } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
|
|
@ -12,30 +13,40 @@ import { of, EMPTY } from 'rxjs';
|
|||
})
|
||||
export class AuthenticationService {
|
||||
|
||||
constructor(private http: HttpClient,
|
||||
constructor(
|
||||
private apiAuthService: apiAuthService,
|
||||
private apiUserService: apiUserService,
|
||||
@Inject('LOCALSTORAGE') private localStorage: Storage) {
|
||||
}
|
||||
|
||||
login(email: string, password: string) {
|
||||
return of(true)
|
||||
.pipe(delay(1000),
|
||||
map((/*response*/) => {
|
||||
// set token property
|
||||
// const decodedToken = jwt_decode(response['token']);
|
||||
|
||||
// store email and jwt token in local storage to keep user logged in between page refreshes
|
||||
this.localStorage.setItem('currentUser', JSON.stringify({
|
||||
token: 'aisdnaksjdn,axmnczm',
|
||||
isAdmin: true,
|
||||
email: 'john.doe@gmail.com',
|
||||
id: '12312323232',
|
||||
alias: 'john.doe@gmail.com'.split('@')[0],
|
||||
expiration: moment().add(1, 'days').toDate(),
|
||||
fullName: 'John Doe'
|
||||
}));
|
||||
|
||||
return true;
|
||||
return this.apiAuthService.authenticate(email, password).
|
||||
pipe(map(response => {
|
||||
// If the server returns a token, the login is successful
|
||||
if (response.token) {
|
||||
// Store user details and jwt token in local storage to keep user logged in between page refreshes
|
||||
this.localStorage.setItem('currentUser', JSON.stringify({
|
||||
token: response.token,
|
||||
}));
|
||||
|
||||
this.apiUserService.getCurrentUser().subscribe((user: User) => {
|
||||
console.log(user);
|
||||
const Storeduser = this.localStorage.getItem('currentUser');
|
||||
if(Storeduser) {
|
||||
const currentUser = JSON.parse(Storeduser);
|
||||
currentUser.isAdmin = user.isAdmin;
|
||||
currentUser.email = user.email;
|
||||
currentUser.id = user._id;
|
||||
currentUser.alias = user.email.split('@')[0];
|
||||
currentUser.fullName = user.username;
|
||||
currentUser.expiration = moment().add(30, 'days').toDate();
|
||||
this.localStorage.setItem('currentUser', JSON.stringify(currentUser));
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
|
|
@ -45,16 +56,20 @@ export class AuthenticationService {
|
|||
|
||||
getCurrentUser(): any {
|
||||
// TODO: Enable after implementation
|
||||
// return JSON.parse(this.localStorage.getItem('currentUser'));
|
||||
return {
|
||||
token: 'aisdnaksjdn,axmnczm',
|
||||
isAdmin: true,
|
||||
email: 'john.doe@gmail.com',
|
||||
id: '12312323232',
|
||||
alias: 'john.doe@gmail.com'.split('@')[0],
|
||||
expiration: moment().add(1, 'days').toDate(),
|
||||
fullName: 'John Doe'
|
||||
};
|
||||
const user = this.localStorage.getItem('currentUser');
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
return JSON.parse(user);
|
||||
// return {
|
||||
// token: JSON.parse(user).token,
|
||||
// isAdmin: true,
|
||||
// email: 'john.doe@gmail.com',
|
||||
// id: '12312323232',
|
||||
// alias: 'john.doe@gmail.com'.split('@')[0],
|
||||
// expiration: moment().add(1, 'days').toDate(),
|
||||
// fullName: 'John Doe'
|
||||
// };
|
||||
}
|
||||
|
||||
passwordResetRequest(email: string) {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
export interface Bid {
|
||||
timestamp: string;
|
||||
amount: number;
|
||||
auctioned_type: string;
|
||||
auctioned_type?: string;
|
||||
}
|
||||
|
||||
export interface Auctioned {
|
||||
|
|
@ -12,6 +12,14 @@ export interface Auctioned {
|
|||
sold: boolean;
|
||||
}
|
||||
|
||||
export interface PostProcessing {
|
||||
nbrBids: number;
|
||||
highestBid: number;
|
||||
duration: number;
|
||||
percentageAboveUnderLow: number;
|
||||
percentageAboveUnderHigh: number;
|
||||
}
|
||||
|
||||
export interface Lot {
|
||||
_id: {
|
||||
$oid: string;
|
||||
|
|
@ -20,10 +28,15 @@ export interface Lot {
|
|||
platform: string;
|
||||
timestamp: string;
|
||||
lotNumber: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
EstimateLow?: number;
|
||||
EstimateHigh?: number;
|
||||
RawData?: Object;
|
||||
sale_id: {
|
||||
$oid: string;
|
||||
};
|
||||
Bids?: Bid[];
|
||||
auctioned?: Auctioned;
|
||||
postProcessing?: PostProcessing;
|
||||
}
|
||||
|
|
@ -2,10 +2,15 @@
|
|||
export interface PostProcessing {
|
||||
nbrLots: number;
|
||||
duration: number;
|
||||
bidsDuration: number;
|
||||
durationPerLots: string;
|
||||
totalAmount: number;
|
||||
averageAmount: string;
|
||||
medianAmount: string;
|
||||
minAmount: string;
|
||||
maxAmount: string;
|
||||
unsoldLots: number;
|
||||
unsoldPercentage: string;
|
||||
}
|
||||
|
||||
export interface Sale {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
export interface User {
|
||||
_id: string;
|
||||
email: string;
|
||||
isAdmin: boolean;
|
||||
isAgent?: boolean;
|
||||
username: string;
|
||||
expiration?: string;
|
||||
password?: string;
|
||||
confirmPassword?: string;
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ export class LoginComponent implements OnInit {
|
|||
this.router.navigate(['/']);
|
||||
},
|
||||
error => {
|
||||
this.notificationService.openSnackBar(error.error);
|
||||
this.notificationService.openSnackBar(error.error.message);
|
||||
this.loading = false;
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { apiService } from 'src/app/core/services/api.service';
|
||||
import { apiFavoriteService } from 'src/app/core/services/api/api.favorite.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-favorites-page',
|
||||
|
|
@ -16,7 +16,7 @@ export class FavoritesPageComponent implements OnInit {
|
|||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private apiService: apiService) {}
|
||||
private apiFavoriteService: apiFavoriteService) {}
|
||||
|
||||
openDialog(): void {
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ export class FavoritesPageComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.apiService.getAllFavorite().subscribe((data: any) => {
|
||||
this.apiFavoriteService.getAllFavorite().subscribe((data: any) => {
|
||||
this.dataSource = data;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import * as moment from 'moment-timezone';
|
|||
|
||||
//Services
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
import { apiService } from 'src/app/core/services/api.service';
|
||||
import { apiLotService } from 'src/app/core/services/api/api.lot.service';
|
||||
import { apiFavoriteService } from 'src/app/core/services/api/api.favorite.service';
|
||||
import { apiSaleService } from 'src/app/core/services/api/api.sale.service';
|
||||
|
||||
// Models
|
||||
import { LotInfo } from 'src/app/core/services/model/lotInfo.interface';
|
||||
|
|
@ -36,7 +38,9 @@ export class NewFavoritesPageComponent implements OnInit {
|
|||
private ActivatedRoute: ActivatedRoute,
|
||||
private router: Router,
|
||||
private notificationService: NotificationService,
|
||||
private apiService: apiService,) {
|
||||
private apiLotService: apiLotService,
|
||||
private apiSaleService: apiSaleService,
|
||||
private apiFavoriteService: apiFavoriteService,) {
|
||||
|
||||
this.ActivatedRoute.params.subscribe(params => {
|
||||
this.url = params['url'];
|
||||
|
|
@ -80,11 +84,11 @@ export class NewFavoritesPageComponent implements OnInit {
|
|||
// this.date = moment(this.SaleInfo.date).tz('Europe/Paris').toDate();
|
||||
// this.hour = moment(this.SaleInfo.date).tz('Europe/Paris').format('HH:mm');
|
||||
|
||||
this.apiService.getLotInfo(this.url).subscribe( lotInfo => {
|
||||
this.apiLotService.getLotInfo(this.url).subscribe( lotInfo => {
|
||||
console.log(lotInfo);
|
||||
this.lotInfo = lotInfo;
|
||||
|
||||
this.apiService.getSaleInfos(this.lotInfo.saleInfo.url).subscribe( SaleInfo => {
|
||||
this.apiSaleService.getSaleInfos(this.lotInfo.saleInfo.url).subscribe( SaleInfo => {
|
||||
console.log(SaleInfo);
|
||||
this.SaleInfo = SaleInfo;
|
||||
|
||||
|
|
@ -99,7 +103,7 @@ export class NewFavoritesPageComponent implements OnInit {
|
|||
}
|
||||
);
|
||||
|
||||
this.apiService.getPictures(this.url).subscribe( pictures => {
|
||||
this.apiLotService.getPictures(this.url).subscribe( pictures => {
|
||||
this.images = pictures;
|
||||
this.picture = pictures[0];
|
||||
});
|
||||
|
|
@ -127,7 +131,7 @@ export class NewFavoritesPageComponent implements OnInit {
|
|||
// Europe/Paris is the timezone of the user
|
||||
let dateTime = moment.tz(`${this.date.toISOString().split('T')[0]}T${this.hour}`, 'Europe/Paris').format();
|
||||
|
||||
this.apiService.saveFavorite(this.lotInfo, this.SaleInfo, this.picture, dateTime, this.buyProject, this.maxPrice, this.Note).subscribe( res => {
|
||||
this.apiFavoriteService.saveFavorite(this.lotInfo, this.SaleInfo, this.picture, dateTime, this.buyProject, this.maxPrice, this.Note).subscribe( res => {
|
||||
this.notificationService.openSnackBar("Favorite saved");
|
||||
this.router.navigate(['favorites']);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { apiService } from 'src/app/core/services/api.service';
|
||||
import { apiLotService } from 'src/app/core/services/api/api.lot.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-pictures-page',
|
||||
|
|
@ -20,7 +21,7 @@ export class PicturesPageComponent implements OnInit {
|
|||
|
||||
constructor(
|
||||
private titleService: Title,
|
||||
private apiService: apiService,
|
||||
private apiLotService: apiLotService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -43,7 +44,7 @@ export class PicturesPageComponent implements OnInit {
|
|||
}
|
||||
|
||||
getPictures(): void {
|
||||
this.apiService.getPictures(this.url).subscribe( Pictures => {
|
||||
this.apiLotService.getPictures(this.url).subscribe( Pictures => {
|
||||
this.images = [];
|
||||
const newImages = [...this.images];
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
|
|||
import * as moment from 'moment-timezone';
|
||||
|
||||
//Services
|
||||
import { apiService } from 'src/app/core/services/api.service';
|
||||
import { apiSaleService } from 'src/app/core/services/api/api.sale.service';
|
||||
|
||||
// Models
|
||||
import { SaleInfo } from 'src/app/core/services/model/saleInfo.interface';
|
||||
|
|
@ -23,7 +23,7 @@ export class LoadingSaleDialogComponent implements OnInit {
|
|||
hour: string = "";
|
||||
|
||||
constructor(
|
||||
private apiService: apiService,
|
||||
private apiSaleService: apiSaleService,
|
||||
public dialogRef: MatDialogRef<LoadingSaleDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
this.SaleInfo = {
|
||||
|
|
@ -44,7 +44,7 @@ export class LoadingSaleDialogComponent implements OnInit {
|
|||
this.url = this.data.url;
|
||||
console.log(this.url);
|
||||
|
||||
this.apiService.getSaleInfos(this.url).subscribe( SaleInfo => {
|
||||
this.apiSaleService.getSaleInfos(this.url).subscribe( SaleInfo => {
|
||||
console.log(SaleInfo);
|
||||
this.SaleInfo = SaleInfo;
|
||||
|
||||
|
|
@ -70,7 +70,7 @@ export class LoadingSaleDialogComponent implements OnInit {
|
|||
status: "ready"
|
||||
}
|
||||
|
||||
this.apiService.saveSale(Sale).subscribe( Sale => {
|
||||
this.apiSaleService.saveSale(Sale).subscribe( Sale => {
|
||||
this.dialogRef.close(true);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
.example-card {
|
||||
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Lot</mat-card-title>
|
||||
<mat-card-subtitle>Lot informations</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<mat-list>
|
||||
<mat-list-item>
|
||||
<span matListItemTitle>#</span>
|
||||
<span matListItemLine>{{Lot.lotNumber}}</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<span matListItemTitle>Title</span>
|
||||
<span matListItemLine>{{Lot.title}}</span>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<span matListItemTitle>Description</span>
|
||||
<span matListItemLine [innerHTML]="getSafeDescription()"></span>
|
||||
</mat-list-item>
|
||||
<mat-list-item>
|
||||
<span matListItemTitle>Estimate</span>
|
||||
<span matListItemLine>Low: {{Lot.EstimateLow}} | High: {{Lot.EstimateHigh}} </span>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<table mat-table [dataSource]="bids" class="mat-elevation-z8">
|
||||
|
||||
<!--- Note that these columns can be defined in any order.
|
||||
The actual rendered columns are set as a property on the row definition" -->
|
||||
|
||||
<!-- Lotnum Column -->
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef >Amount</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.amount}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Title Column -->
|
||||
<ng-container matColumnDef="type">
|
||||
<th mat-header-cell *matHeaderCellDef > Type </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.auctioned_type}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Time Column -->
|
||||
<ng-container matColumnDef="time">
|
||||
<th mat-header-cell *matHeaderCellDef > Time </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.timestamp | date:'HH:mm:ss':'Europe/Paris'}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
<mat-paginator [pageSizeOptions]="[5, 10, 15]" showFirstLastButtons></mat-paginator>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<button mat-button color="warn" (click)="cancel()">Close</button>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
import { Component, OnInit, Inject, ViewChild } from '@angular/core';
|
||||
import {MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatTableDataSource } from '@angular/material/table';import * as moment from 'moment-timezone';
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
||||
|
||||
//Services
|
||||
import { apiLotService } from 'src/app/core/services/api/api.lot.service';
|
||||
|
||||
// Models
|
||||
import { Lot, Bid } from 'src/app/core/services/model/lot.interface';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'lot-detail-dialog-dialog',
|
||||
templateUrl: './lot-detail-dialog.component.html',
|
||||
styleUrls: ['./lot-detail-dialog.component.css']
|
||||
})
|
||||
export class LotDetailDialogComponent implements OnInit {
|
||||
|
||||
id: string;
|
||||
Lot: Lot;
|
||||
|
||||
displayedColumns: string[] = ['amount', 'type', 'time'];
|
||||
bids: MatTableDataSource<Bid> = new MatTableDataSource<Bid>();
|
||||
@ViewChild(MatPaginator) paginator?: MatPaginator;
|
||||
|
||||
constructor(
|
||||
private apiLotService: apiLotService,
|
||||
public dialogRef: MatDialogRef<LotDetailDialogComponent>,
|
||||
private sanitizer: DomSanitizer,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any) {
|
||||
|
||||
this.id = data.id;
|
||||
this.Lot = {
|
||||
_id: { $oid: '' },
|
||||
idPlatform: '',
|
||||
platform: '',
|
||||
timestamp: '',
|
||||
lotNumber: '',
|
||||
RawData: {},
|
||||
sale_id: { $oid: '' },
|
||||
Bids: [],
|
||||
title: '',
|
||||
description: '',
|
||||
auctioned: {
|
||||
timestamp: '',
|
||||
amount: 0,
|
||||
auctioned_type: '',
|
||||
sold: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
this.apiLotService.getLot(this.id).subscribe( Lot => {
|
||||
this.Lot = Lot;
|
||||
|
||||
this.bids = new MatTableDataSource(Lot.Bids ?? []);
|
||||
this.bids.paginator = this.paginator ?? null;
|
||||
});
|
||||
}
|
||||
|
||||
getSafeDescription(): SafeHtml {
|
||||
if (this.Lot && this.Lot.description) {
|
||||
return this.sanitizer.bypassSecurityTrustHtml(this.Lot.description.replace(/\n/g, '<br>'));
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
.mat-mdc-row .mat-mdc-cell {
|
||||
border-bottom: 1px solid transparent;
|
||||
border-top: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mat-mdc-row:hover .mat-mdc-cell {
|
||||
border-color: currentColor;
|
||||
}
|
||||
|
|
@ -17,16 +17,23 @@
|
|||
</div>
|
||||
<div fxLayout="column" fxLayoutGap="2px">
|
||||
<p><mat-icon>tag</mat-icon>{{Sale.postProcessing?.nbrLots}} Lots</p>
|
||||
<p><mat-icon>hourglass_bottom</mat-icon>{{getDurationInMinutes(Sale.postProcessing?.duration ?? 0)}} min</p>
|
||||
<p><mat-icon>hourglass_bottom</mat-icon>{{convertSecondsToHHMM(Sale.postProcessing?.bidsDuration ?? 0)}}</p>
|
||||
<p><mat-icon>hourglass_bottom</mat-icon>/Lot {{getTimePerLot(parseToNumber(Sale.postProcessing?.durationPerLots ?? '0'))}}</p>
|
||||
</div>
|
||||
<div fxLayout="column" fxLayoutGap="2px">
|
||||
<p>Total amount: {{Sale.postProcessing?.totalAmount | currency:'EUR':'symbol':'1.2-2'}}</p>
|
||||
<p>Max amount: {{Sale.postProcessing?.maxAmount | currency:'EUR':'symbol':'1.2-2'}}</p>
|
||||
<p>Min amount: {{Sale.postProcessing?.minAmount | currency:'EUR':'symbol':'1.2-2'}}</p>
|
||||
</div>
|
||||
<div fxLayout="column" fxLayoutGap="2px">
|
||||
<p>Average amount: {{Sale.postProcessing?.averageAmount | currency:'EUR':'symbol':'1.2-2'}}</p>
|
||||
<p>Median amount: {{Sale.postProcessing?.medianAmount | currency:'EUR':'symbol':'1.2-2'}}</p>
|
||||
<p>Unsold: {{Sale.postProcessing?.unsoldLots}} ({{Sale.postProcessing?.unsoldPercentage}}%)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="2px">
|
||||
<button mat-raised-button color="primary" (click)="downloadExcelStatsFile(id)">Excel</button>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
|
|
@ -48,12 +55,18 @@
|
|||
|
||||
<!-- Lotnum Column -->
|
||||
<ng-container matColumnDef="lotNum">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="idPlatform">#</th>
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="lotNumber">#</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.lotNumber}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Picture Column -->
|
||||
<ng-container matColumnDef="picture">
|
||||
<th mat-header-cell *matHeaderCellDef> Picture </th>
|
||||
<td mat-cell *matCellDef="let element"><img [src]="element.picture" alt="Picture" style="height: 60px"></td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Title Column -->
|
||||
<ng-container matColumnDef="title">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="title"> Title </th>
|
||||
|
|
@ -82,24 +95,48 @@
|
|||
<ng-container matColumnDef="price">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="auctionedAmount"> Price </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.auctionedAmount}}
|
||||
<span [ngClass]="{'bold-text': element.auctionedAmount > 0}">{{element.auctionedAmount > 0 ? element.auctionedAmount : "-"}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Price Column -->
|
||||
<!-- nbrBids Column -->
|
||||
<ng-container matColumnDef="nbrBids">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="bidsLength"> nbrBids </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.bidsLength}}
|
||||
{{element.postProcessing?.nbrBids}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- duration Column -->
|
||||
<ng-container matColumnDef="duration">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="duration"> Duration </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.postProcessing?.duration}} s
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- percentageAboveUnderLow Column -->
|
||||
<ng-container matColumnDef="percentageAboveUnderLow">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="percentageAboveUnderLow">Above/Under Low</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.postProcessing?.percentageAboveUnderLow}} %
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- percentageAboveUnderHigh Column -->
|
||||
<ng-container matColumnDef="percentageAboveUnderHigh">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="percentageAboveUnderHigh">Above/Under High</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.postProcessing?.percentageAboveUnderHigh}} %
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="openDetailLot(row._id)"></tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
<mat-paginator [pageSizeOptions]="[10, 50, 100]" showFirstLastButtons></mat-paginator>
|
||||
<mat-paginator [pageSizeOptions]="[10, 50, 100]" showFirstLastButtons></mat-paginator>
|
||||
</mat-tab>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,17 +4,18 @@ import { Router, ActivatedRoute } from '@angular/router';
|
|||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { LotDetailDialogComponent } from './lot-detail-dialog/lot-detail-dialog.component';
|
||||
|
||||
import * as moment from 'moment';
|
||||
|
||||
// Services
|
||||
import { apiService } from 'src/app/core/services/api.service';
|
||||
import { apiSaleService } from 'src/app/core/services/api/api.sale.service';
|
||||
import { apiLotService } from 'src/app/core/services/api/api.lot.service';
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
|
||||
//Models
|
||||
import { Sale } from 'src/app/core/services/model/sale.interface';
|
||||
import { Lot } from 'src/app/core/services/model/lot.interface';
|
||||
import { SaleInfo } from 'src/app/core/services/model/saleInfo.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-favorites-page',
|
||||
|
|
@ -27,8 +28,8 @@ export class SaleDetailPageComponent implements OnInit, AfterViewInit {
|
|||
|
||||
id: any = '';
|
||||
Sale: Sale;
|
||||
displayedColumns: string[] = ['lotNum', 'title', 'estimateLow', 'estimateHigh', 'price', 'nbrBids'];
|
||||
|
||||
|
||||
displayedColumns: string[] = ['lotNum', 'picture', 'title', 'estimateLow', 'estimateHigh', 'price', 'nbrBids', 'duration', 'percentageAboveUnderLow', 'percentageAboveUnderHigh'];
|
||||
lotList: MatTableDataSource<Lot> = new MatTableDataSource<Lot>();
|
||||
@ViewChild(MatPaginator) paginator?: MatPaginator;
|
||||
@ViewChild(MatSort) sort?: MatSort;
|
||||
|
|
@ -38,7 +39,8 @@ export class SaleDetailPageComponent implements OnInit, AfterViewInit {
|
|||
private notificationService: NotificationService,
|
||||
private router: Router,
|
||||
public dialog: MatDialog,
|
||||
private apiService: apiService) {
|
||||
private apiSaleService: apiSaleService,
|
||||
private apiLotService: apiLotService) {
|
||||
|
||||
this.Sale = {
|
||||
_id: { $oid: '' },
|
||||
|
|
@ -53,10 +55,15 @@ export class SaleDetailPageComponent implements OnInit, AfterViewInit {
|
|||
postProcessing: {
|
||||
nbrLots: 0,
|
||||
duration: 0,
|
||||
bidsDuration: 0,
|
||||
durationPerLots: '',
|
||||
totalAmount: 0,
|
||||
averageAmount: '',
|
||||
medianAmount: ''
|
||||
medianAmount: '',
|
||||
minAmount: '',
|
||||
maxAmount: '',
|
||||
unsoldLots: 0,
|
||||
unsoldPercentage: ''
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -76,17 +83,49 @@ export class SaleDetailPageComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
|
||||
getSale(){
|
||||
this.apiService.getSale(this.id).subscribe((sale: Sale) => {
|
||||
this.apiSaleService.getSale(this.id).subscribe((sale: Sale) => {
|
||||
this.Sale = sale;
|
||||
});
|
||||
}
|
||||
|
||||
getLotList(){
|
||||
this.apiService.getLotsBySale(this.id).subscribe((lotList: Lot[]) => {
|
||||
this.apiLotService.getLotsBySale(this.id).subscribe((lotList: Lot[]) => {
|
||||
|
||||
// adding the Bids length info
|
||||
lotList = lotList.map((lot) => {
|
||||
return {...lot, bidsLength: lot.Bids ? lot.Bids.length : 0};
|
||||
return {...lot, bidsLength: lot.postProcessing ? lot.postProcessing.nbrBids : 0};
|
||||
});
|
||||
|
||||
// adding the picture info
|
||||
let path = '';
|
||||
lotList = lotList.map((lot) => {
|
||||
switch (lot.platform) {
|
||||
case 'drouot':
|
||||
path = 'https://cdn.drouot.com/d/image/lot?size=phare&path='+(lot.RawData as { photos?: { path: string }[] })?.photos?.[0]?.path;
|
||||
return {...lot, picture: path ?? ''};
|
||||
|
||||
case 'interencheres':
|
||||
path = (lot.RawData as { medias?: { lg: string }[] })?.medias?.[0]?.lg ?? '';
|
||||
return {...lot, picture: path};
|
||||
|
||||
default:
|
||||
return {...lot, picture: ''};
|
||||
}
|
||||
});
|
||||
|
||||
// adding the duration info
|
||||
lotList = lotList.map((lot) => {
|
||||
return {...lot, duration: lot.postProcessing ? lot.postProcessing.duration : 0};
|
||||
});
|
||||
|
||||
// adding the percentageAboveUnderLow info
|
||||
lotList = lotList.map((lot) => {
|
||||
return {...lot, percentageAboveUnderLow: lot.postProcessing ? lot.postProcessing.percentageAboveUnderLow : 0};
|
||||
});
|
||||
|
||||
// adding the percentageAboveUnderHigh info
|
||||
lotList = lotList.map((lot) => {
|
||||
return {...lot, percentageAboveUnderHigh: lot.postProcessing ? lot.postProcessing.percentageAboveUnderHigh : 0};
|
||||
});
|
||||
|
||||
// adding the Auctionned ammount info
|
||||
|
|
@ -101,9 +140,9 @@ export class SaleDetailPageComponent implements OnInit, AfterViewInit {
|
|||
});
|
||||
}
|
||||
|
||||
getDurationInMinutes(duration: number): number {
|
||||
return Math.floor(duration / 60);
|
||||
}
|
||||
// getDurationInMinutes(duration: number): number {
|
||||
// return Math.floor(duration / 60);
|
||||
// }
|
||||
|
||||
getTimePerLot(duration: number): string {
|
||||
|
||||
|
|
@ -124,6 +163,30 @@ export class SaleDetailPageComponent implements OnInit, AfterViewInit {
|
|||
return parseFloat(value || '0');
|
||||
}
|
||||
|
||||
convertSecondsToHHMM(seconds: number): string {
|
||||
const h = Math.floor(seconds / 3600);
|
||||
const m = Math.floor((seconds % 3600) / 60);
|
||||
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
openDetailLot(idLot: string): void {
|
||||
this.dialog.open(LotDetailDialogComponent, {
|
||||
width: '80%',
|
||||
data: {id: idLot}
|
||||
});
|
||||
}
|
||||
|
||||
downloadExcelStatsFile(_id: String): void {
|
||||
this.apiSaleService.getSaleStatXsl(_id).subscribe((data: Blob) => {
|
||||
const downloadURL = window.URL.createObjectURL(data);
|
||||
const link = document.createElement('a');
|
||||
link.href = downloadURL;
|
||||
link.download = 'SaleStats.xlsx';
|
||||
link.click();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,31 +63,6 @@
|
|||
<td mat-cell *matCellDef="let element"> {{element.platform}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Prepare Column -->
|
||||
<!-- <ng-container matColumnDef="prepare">
|
||||
<th mat-header-cell *matHeaderCellDef> Prepare </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button *ngIf="element.status == 'ready'" mat-button (click)="prepareSale(element)"><mat-icon>format_list_numbered</mat-icon></button>
|
||||
</td>
|
||||
</ng-container> -->
|
||||
|
||||
<!-- Follow Column -->
|
||||
<!-- <ng-container matColumnDef="follow">
|
||||
<th mat-header-cell *matHeaderCellDef> Follow </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button *ngIf="element.status == 'ready'" mat-button (click)="followSale(element)"><mat-icon>play_arrow</mat-icon></button>
|
||||
<button *ngIf="element.status == 'following'" mat-button (click)="stopFollowSale(element)"><mat-icon>stop</mat-icon></button>
|
||||
<div *ngIf="element.status == 'askStop'"><mat-progress-bar mode="indeterminate"></mat-progress-bar></div>
|
||||
</ng-container> -->
|
||||
|
||||
<!-- Delete Column -->
|
||||
<!-- <ng-container matColumnDef="delete">
|
||||
<th mat-header-cell *matHeaderCellDef> Delete </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<button mat-button (click)="deleteSale(element._id)"><mat-icon>delete</mat-icon></button>
|
||||
</td>
|
||||
</ng-container> -->
|
||||
|
||||
<!-- Action Column -->
|
||||
<ng-container matColumnDef="action">
|
||||
<th mat-header-cell *matHeaderCellDef> Action </th>
|
||||
|
|
@ -97,6 +72,8 @@
|
|||
<mat-option *ngIf="element.status == 'ready'" (click)="followSale(element)"><mat-icon>play_arrow</mat-icon> Follow</mat-option>
|
||||
<mat-option *ngIf="element.status == 'following'" (click)="stopFollowSale(element)"><mat-icon>stop</mat-icon> Stop Following</mat-option>
|
||||
<mat-option *ngIf="element.status == 'following'" (click)="resetToReady(element._id)">Reset to Ready</mat-option>
|
||||
<mat-option (click)="navigateToSaleDetail(element._id)"><mat-icon>info</mat-icon> Details</mat-option>
|
||||
<mat-option (click)="postProcessing(element._id)"><mat-icon>query_stats</mat-icon> Post-processing</mat-option>
|
||||
<mat-option (click)="deleteSale(element._id)"><mat-icon>delete</mat-icon> Delete</mat-option>
|
||||
</mat-select>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router } from '@angular/router';
|
||||
import { LoadingSaleDialogComponent } from './loading-sale-dialog/loading-sale-dialog.component';
|
||||
import * as moment from 'moment';
|
||||
|
||||
// Services
|
||||
import { apiService } from 'src/app/core/services/api.service';
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
import { apiSaleService } from 'src/app/core/services/api/api.sale.service';
|
||||
|
||||
import { Sale } from 'src/app/core/services/model/sale.interface';
|
||||
|
||||
|
|
@ -15,7 +16,7 @@ import { Sale } from 'src/app/core/services/model/sale.interface';
|
|||
templateUrl: './sales-page.component.html',
|
||||
styleUrls: ['./sales-page.component.css']
|
||||
})
|
||||
export class SalesPageComponent implements OnInit {
|
||||
export class SalesPageComponent implements OnInit,OnDestroy {
|
||||
|
||||
url: string = '';
|
||||
refreshSalesId: any;
|
||||
|
|
@ -29,7 +30,8 @@ export class SalesPageComponent implements OnInit {
|
|||
private notificationService: NotificationService,
|
||||
private router: Router,
|
||||
public dialog: MatDialog,
|
||||
private apiService: apiService) {}
|
||||
private apiSaleService: apiSaleService,
|
||||
private titleService: Title) {}
|
||||
|
||||
openLoadingSale(): void {
|
||||
const dialogRef = this.dialog.open(LoadingSaleDialogComponent, {
|
||||
|
|
@ -47,7 +49,7 @@ export class SalesPageComponent implements OnInit {
|
|||
}
|
||||
|
||||
refreshSales(): void {
|
||||
this.apiService.getAllSale().subscribe((data: any) => {
|
||||
this.apiSaleService.getAllSale().subscribe((data: any) => {
|
||||
console.log(data);
|
||||
const today = moment().startOf('day');
|
||||
|
||||
|
|
@ -62,15 +64,21 @@ export class SalesPageComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.titleService.setTitle('Jucundus - Sales');
|
||||
this.refreshSales();
|
||||
this.refreshSalesId = setInterval(() => {
|
||||
this.refreshSales();
|
||||
}, 5000);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
if (this.refreshSalesId) {
|
||||
clearInterval(this.refreshSalesId);
|
||||
}
|
||||
}
|
||||
|
||||
prepareSale(Sale: Sale): void {
|
||||
|
||||
this.apiService.prepareSale(Sale).subscribe( data => {
|
||||
this.apiSaleService.prepareSale(Sale).subscribe( data => {
|
||||
console.log(data);
|
||||
this.refreshSales();
|
||||
this.notificationService.openSnackBar("Prepare Sale");
|
||||
|
|
@ -80,7 +88,7 @@ export class SalesPageComponent implements OnInit {
|
|||
|
||||
followSale(Sale: Sale): void {
|
||||
|
||||
this.apiService.followSale(Sale).subscribe( data => {
|
||||
this.apiSaleService.followSale(Sale).subscribe( data => {
|
||||
console.log(data);
|
||||
this.refreshSales();
|
||||
this.notificationService.openSnackBar("Sale followed");
|
||||
|
|
@ -90,21 +98,21 @@ export class SalesPageComponent implements OnInit {
|
|||
|
||||
stopFollowSale(Sale: Sale): void {
|
||||
Sale.status = "askStop";
|
||||
this.apiService.updateSale(Sale).subscribe( data => {
|
||||
this.apiSaleService.updateSale(Sale).subscribe( data => {
|
||||
this.refreshSales();
|
||||
this.notificationService.openSnackBar("Sale Stopping...");
|
||||
})
|
||||
}
|
||||
|
||||
deleteSale(_id: string): void {
|
||||
this.apiService.deleteSale(_id).subscribe( data => {
|
||||
this.apiSaleService.deleteSale(_id).subscribe( data => {
|
||||
this.refreshSales();
|
||||
this.notificationService.openSnackBar("Sale deleted");
|
||||
})
|
||||
}
|
||||
|
||||
resetToReady(_id: string): void {
|
||||
this.apiService.resetSaleToReady(_id)
|
||||
this.apiSaleService.resetSaleToReady(_id)
|
||||
}
|
||||
|
||||
navigateToSaleDetail(id: string) {
|
||||
|
|
@ -114,7 +122,7 @@ export class SalesPageComponent implements OnInit {
|
|||
}
|
||||
|
||||
postProcessing(id: string) {
|
||||
this.apiService.postProcessing(id).subscribe( data => {
|
||||
this.apiSaleService.postProcessing(id).subscribe( data => {
|
||||
this.notificationService.openSnackBar("Sale processing");
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,15 @@ import { SalesRoutingModule } from './sales-routing.module';
|
|||
import { SalesPageComponent } from './sales-page/sales-page.component';
|
||||
import { LoadingSaleDialogComponent } from './sales-page/loading-sale-dialog/loading-sale-dialog.component';
|
||||
import { SaleDetailPageComponent } from './sales-page/sale-detail-page/sale-detail-page.component';
|
||||
import { LotDetailDialogComponent } from './sales-page/sale-detail-page/lot-detail-dialog/lot-detail-dialog.component';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
SalesPageComponent,
|
||||
LoadingSaleDialogComponent,
|
||||
SaleDetailPageComponent
|
||||
SaleDetailPageComponent,
|
||||
LotDetailDialogComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
.mat-mdc-row .mat-mdc-cell {
|
||||
border-bottom: 1px solid transparent;
|
||||
border-top: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mat-mdc-row:hover .mat-mdc-cell {
|
||||
border-color: currentColor;
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
<div fxFlex="95%">
|
||||
<mat-toolbar>
|
||||
<h2><a routerLink="/users">Users</a>/{{id !== '' ? 'Edit' : 'New'}}</h2>
|
||||
</mat-toolbar>
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title>Sale Detail</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<form [formGroup]="userForm">
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<mat-form-field fxFlex="50">
|
||||
<input matInput placeholder="Username" formControlName="username" required>
|
||||
<mat-error *ngIf="userForm.get('username')?.hasError('required')">
|
||||
Username is <strong>required</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="50">
|
||||
<input matInput placeholder="Email" formControlName="email" required>
|
||||
<mat-error *ngIf="userForm.get('email')?.hasError('email') && !userForm.get('email')?.hasError('required')">
|
||||
Please enter a valid email address
|
||||
</mat-error>
|
||||
<mat-error *ngIf="userForm.get('email')?.hasError('required')">
|
||||
Email is <strong>required</strong>
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<mat-form-field fxFlex="50">
|
||||
<input matInput placeholder="Password" formControlName="password">
|
||||
<mat-error *ngIf="userForm.get('password')?.hasError('passwordLength')">
|
||||
Password must be at least 8 characters long
|
||||
</mat-error>
|
||||
<mat-error *ngIf="userForm.get('password')?.hasError('mismatch') || userForm.get('confirmPassword')?.value === null">
|
||||
Passwords do not match
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="50">
|
||||
<input matInput placeholder="Confirm Password" formControlName="confirmPassword">
|
||||
<mat-error *ngIf="userForm.errors?.['mismatch']">
|
||||
Passwords do not match
|
||||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<mat-checkbox name="isAdmin" formControlName="isAdmin">Admin</mat-checkbox>
|
||||
<mat-checkbox name="isAgent" formControlName="isAgent">Agent</mat-checkbox>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<button mat-raised-button color="primary" type="submit" [disabled]="!userForm.valid" (click)="save()">Save</button>
|
||||
<button mat-raised-button color="warn" type="button" *ngIf="id" (click)="deleteUser()">Delete</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
||||
import { ConfirmDialogComponent, ConfirmDialogModel } from 'src/app/shared/confirm-dialog/confirm-dialog.component';
|
||||
|
||||
// Services
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
import { apiUserService } from 'src/app/core/services/api/api.user.service';
|
||||
|
||||
function passwordMatchValidator(g: FormGroup) {
|
||||
const password = g.get('password')?.value;
|
||||
const confirmPassword = g.get('confirmPassword')?.value;
|
||||
if (password){
|
||||
if (password.length < 8) {
|
||||
g.get('password')?.setErrors({ passwordLength: true });
|
||||
return { passwordLength: true };
|
||||
}
|
||||
if (password !== confirmPassword) {
|
||||
g.get('confirmPassword')?.setErrors({ mismatch: true });
|
||||
return { mismatch: true };
|
||||
}
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-favorites-page',
|
||||
templateUrl: './user-edit-page.component.html',
|
||||
styleUrls: ['./user-edit-page.component.css']
|
||||
})
|
||||
|
||||
export class UserEditPageComponent implements OnInit {
|
||||
|
||||
id: any = '';
|
||||
|
||||
userForm: FormGroup;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private notificationService: NotificationService,
|
||||
private router: Router,
|
||||
public dialog: MatDialog,
|
||||
private apiUserService: apiUserService,
|
||||
private fb: FormBuilder
|
||||
) {
|
||||
|
||||
this.userForm = this.fb.group({
|
||||
username: ['', Validators.required],
|
||||
email: ['', [Validators.required, Validators.email]],
|
||||
password: [''],
|
||||
confirmPassword: [''],
|
||||
isAdmin: [false],
|
||||
isAgent: [false]
|
||||
}, { validator: passwordMatchValidator });
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.paramMap.subscribe(params => {
|
||||
this.id = params.get('id');
|
||||
|
||||
if(this.id) {
|
||||
this.apiUserService.getUser(this.id).subscribe((data: any) => {
|
||||
console.log(data);
|
||||
|
||||
// Update form values
|
||||
this.userForm.patchValue({
|
||||
username: data.username,
|
||||
email: data.email,
|
||||
isAdmin: data.isAdmin,
|
||||
isAgent: data.isAgent
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
save(): void {
|
||||
if (this.userForm.valid) {
|
||||
let userData = this.userForm.value
|
||||
console.log(userData);
|
||||
if(this.id) {
|
||||
userData._id = this.id;
|
||||
// Edit User
|
||||
this.apiUserService.updateUser(userData).subscribe((data: any) => {
|
||||
this.notificationService.openSnackBar("User updated successfully");
|
||||
this.router.navigate(['/users']);
|
||||
});
|
||||
} else {
|
||||
// New User
|
||||
this.apiUserService.saveUser(userData).subscribe((data: any) => {
|
||||
this.notificationService.openSnackBar("User created successfully");
|
||||
this.router.navigate(['/users']);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteUser(): void {
|
||||
if (this.id) {
|
||||
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
||||
width: '250px',
|
||||
data: {title: 'Delete User ?', message: 'Are you sure you want to delete this user?'}
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
if (result) {
|
||||
// User clicked Yes
|
||||
|
||||
this.apiUserService.deleteUser(this.id).subscribe((data: any) => {
|
||||
this.notificationService.openSnackBar("User deleted successfully");
|
||||
this.router.navigate(['/users']);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,18 +1,61 @@
|
|||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
<div fxFlex="95%">
|
||||
|
||||
<mat-toolbar>
|
||||
<mat-toolbar-row>
|
||||
<h2>Users</h2>
|
||||
</mat-toolbar-row>
|
||||
<mat-toolbar-row>
|
||||
<button mat-raised-button color="primary" (click)="openUser('')">Add User</button>
|
||||
</mat-toolbar-row>
|
||||
</mat-toolbar>
|
||||
<mat-card>
|
||||
<mat-card-content>
|
||||
<h2>Users</h2>
|
||||
|
||||
|
||||
<div fxLayout="row" fxLayoutGap="5px">
|
||||
<table mat-table [dataSource]="users" matSort class="mat-elevation-z8">
|
||||
|
||||
<div class="container" fxLayout="row" fxLayoutAlign="center none">
|
||||
<div fxFlex="50%" class="text-center no-records animate">
|
||||
<mat-icon>people_outline</mat-icon>
|
||||
<p>No users exist.</p>
|
||||
</div>
|
||||
<mat-icon> </mat-icon>
|
||||
</div>
|
||||
<!--- Note that these columns can be defined in any order.
|
||||
The actual rendered columns are set as a property on the row definition" -->
|
||||
|
||||
<!-- username Column -->
|
||||
<ng-container matColumnDef="username">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="username">Username</th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.username}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- email Column -->
|
||||
<ng-container matColumnDef="email">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="email"> Email </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
{{element.email}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Admin Column -->
|
||||
<ng-container matColumnDef="admin">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="isAdmin"> Admin </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<mat-icon *ngIf="element.isAdmin" style="color:green;">check</mat-icon>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Agent Column -->
|
||||
<ng-container matColumnDef="agent">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header="isAgent"> Agent </th>
|
||||
<td mat-cell *matCellDef="let element">
|
||||
<mat-icon *ngIf="element.isAgent" style="color:green;">check</mat-icon>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="openUser(row._id)"></tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
<mat-paginator [pageSizeOptions]="[10, 50, 100]" showFirstLastButtons></mat-paginator>
|
||||
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { NotificationService } from 'src/app/core/services/notification.service';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
|
||||
import { apiUserService } from 'src/app/core/services/api/api.user.service';
|
||||
import { User } from 'src/app/core/services/model/user.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-list',
|
||||
|
|
@ -11,14 +17,38 @@ import { NotificationService } from 'src/app/core/services/notification.service'
|
|||
})
|
||||
export class UserListComponent implements OnInit {
|
||||
|
||||
displayedColumns: string[] = ['username', 'email', 'admin', 'agent'];
|
||||
users : MatTableDataSource<User> = new MatTableDataSource<User>();
|
||||
@ViewChild(MatPaginator) paginator?: MatPaginator;
|
||||
@ViewChild(MatSort) sort?: MatSort;
|
||||
|
||||
constructor(
|
||||
private logger: NGXLogger,
|
||||
private notificationService: NotificationService,
|
||||
private titleService: Title
|
||||
private titleService: Title,
|
||||
private router: Router,
|
||||
private apiUserService: apiUserService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.titleService.setTitle('Jucundus - Users');
|
||||
this.logger.log('Users loaded');
|
||||
this.refreshUsers()
|
||||
}
|
||||
|
||||
refreshUsers(): void {
|
||||
this.apiUserService.getAllUsers().subscribe((data: any) => {
|
||||
console.log(data);
|
||||
|
||||
this.users = new MatTableDataSource(data);
|
||||
this.users.paginator = this.paginator ?? null;
|
||||
this.users.sort = this.sort ?? null;
|
||||
|
||||
this.notificationService.openSnackBar("Users loaded");
|
||||
});
|
||||
}
|
||||
|
||||
openUser(id: string): void {
|
||||
this.router.navigate(['/users/edit', id]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Routes, RouterModule } from '@angular/router';
|
|||
import { LayoutComponent } from 'src/app/shared/layout/layout.component';
|
||||
|
||||
import { UserListComponent } from './user-list/user-list.component';
|
||||
import { UserEditPageComponent } from './user-list/user-edit-page/user-edit-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
|
|
@ -10,6 +11,7 @@ const routes: Routes = [
|
|||
component: LayoutComponent,
|
||||
children: [
|
||||
{ path: '', component: UserListComponent },
|
||||
{ path: 'edit/:id', component: UserEditPageComponent },
|
||||
]
|
||||
}
|
||||
];
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ import { CommonModule } from '@angular/common';
|
|||
|
||||
import { UsersRoutingModule } from './users-routing.module';
|
||||
import { UserListComponent } from './user-list/user-list.component';
|
||||
import { UserEditPageComponent } from './user-list/user-edit-page/user-edit-page.component';
|
||||
|
||||
import { SharedModule } from 'src/app/shared/shared.module';
|
||||
|
||||
@NgModule({
|
||||
|
|
@ -11,6 +13,9 @@ import { SharedModule } from 'src/app/shared/shared.module';
|
|||
SharedModule,
|
||||
UsersRoutingModule
|
||||
],
|
||||
declarations: [UserListComponent]
|
||||
declarations: [
|
||||
UserListComponent,
|
||||
UserEditPageComponent
|
||||
]
|
||||
})
|
||||
export class UsersModule { }
|
||||
|
|
|
|||
|
|
@ -83,7 +83,12 @@
|
|||
</mat-icon>
|
||||
<p mat-line> Pictures </p>
|
||||
</a>
|
||||
|
||||
<a mat-list-item [routerLink]="['/users']" routerLinkActive="active">
|
||||
<mat-icon matListItemIcon>
|
||||
people
|
||||
</mat-icon>
|
||||
<p mat-line> Users </p>
|
||||
</a>
|
||||
|
||||
<!-- <a mat-list-item [routerLink]="['/customers']" routerLinkActive="active">
|
||||
<mat-icon matListItemIcon>
|
||||
|
|
@ -91,16 +96,10 @@
|
|||
</mat-icon>
|
||||
<p mat-line> Customers </p>
|
||||
</a>
|
||||
<a mat-list-item [routerLink]="['/users']" routerLinkActive="active">
|
||||
<mat-icon matListItemIcon>
|
||||
people
|
||||
</mat-icon>
|
||||
<p mat-line> Users </p>
|
||||
</a> -->
|
||||
|
||||
|
||||
-->
|
||||
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<h3 mat-subheader>User</h3>
|
||||
<a mat-list-item [routerLink]="['/account/profile']">
|
||||
<mat-icon matListItemIcon>person</mat-icon>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@ import { NgxLoggerLevel } from 'ngx-logger';
|
|||
export const environment = {
|
||||
production: true,
|
||||
logLevel: NgxLoggerLevel.OFF,
|
||||
serverLogLevel: NgxLoggerLevel.ERROR
|
||||
serverLogLevel: NgxLoggerLevel.ERROR,
|
||||
ServeurURL: "http://localhost:3000"
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,5 +8,6 @@ import { NgxLoggerLevel } from 'ngx-logger';
|
|||
export const environment = {
|
||||
production: false,
|
||||
logLevel: NgxLoggerLevel.TRACE,
|
||||
serverLogLevel: NgxLoggerLevel.OFF
|
||||
serverLogLevel: NgxLoggerLevel.OFF,
|
||||
ServeurURL: "http://localhost:3000"
|
||||
};
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ services:
|
|||
- ./backend:/backend
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "9229:9229"
|
||||
command: ["npm", "run", "dev"]
|
||||
depends_on:
|
||||
- db
|
||||
Loading…
Reference in New Issue