Skip to content

Commit

Permalink
add exports, OTable pagination and columns search
Browse files Browse the repository at this point in the history
  • Loading branch information
rafael-tdp committed Nov 30, 2023
1 parent b164646 commit 28c52a4
Show file tree
Hide file tree
Showing 24 changed files with 839 additions and 37 deletions.
Binary file modified .DS_Store
Binary file not shown.
2 changes: 2 additions & 0 deletions api/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import stats from "./src/router/statsRouter.js";
import wishes from "./src/router/wishsRouter.js";
import orders from "./src/router/ordersRouter.js";
import payments from "./src/router/paymentsRouter.js";
import exports from "./src/router/exportsRouter.js";
import sequelize from "./src/config/sequelize-config.js";
import mailTransporter from "./src/config/mail-config.js";
import passwordRenewal from "./src/scripts/passwordRenewal.js";
Expand Down Expand Up @@ -53,6 +54,7 @@ app.use("/stats/", stats);
app.use("/wishes", wishes);
app.use("/orders", orders);
app.use("/payments", payments);
app.use("/exports", exports);

// CRON job
cron.schedule("0 0 * * *", async () => {
Expand Down
6 changes: 6 additions & 0 deletions api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"bcryptjs": "^2.4.3",
"body-parser": "^1.20.2",
"crypto-js": "^4.1.1",
"csv-writer": "^1.6.0",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-fileupload": "^1.4.1",
Expand Down
43 changes: 43 additions & 0 deletions api/src/lib/columnsNames.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export const columnNames = {
firstname: "Prénom",
lastname: "Nom",
email: "Email",
name: "Nom",
description: "Description",
gender: "Genre",
createdAt: "Date de création",
role: "Rôle",
disabled: "Statut",
id: "ID",
passwordUpdatedAt: "Date de modification du mot de passe",
passwordResetToken: "Token de réinitialisation du mot de passe",
birthdate: "Date de naissance",
address: "Adresse",
postalCode: "Code postal",
city: "Ville",
phone: "Téléphone",
loginAttempts: "Nombre de tentatives de connexion",
blockedAt: "Date de blocage",
isValidate: "E-mail confirmé",
authentificationToken: "Token d'authentification",
updatedAt: "Date de modification",
price: "Prix",
vat: "TVA",
quantity: "Qté",
size: "Taille",
color: "Couleur",
status: "Statut",
userId: "ID utilisateur",
deliveryAddress: "Adresse de livraison",
products: "Produits",
user: "ID utilisateur",
sku: "SKU",
discount: "Réduction",
alertQuantity: "Qté. avant alerte",
deletedAt: "Date de suppression",
modelId: "ID modèle",
productImage: "Image",
productImages: "Images",
model: "Modèle",
models: "Modèles",
};
30 changes: 30 additions & 0 deletions api/src/lib/dataToCSV.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createObjectCsvStringifier } from "csv-writer";

export const dataToCSV = async function (data) {
const header = [];
for (const colName of Object.keys(data[0])) {
header.push({
id: colName,
title: colName,
});
}

const csvStringifier = createObjectCsvStringifier({
header,
fieldDelimiter: ";",
});

for (let i = 0; i < data.length; i++) {
for (const key in data[i]) {
if (!data[i][key]) continue;
if (typeof data[i][key] !== "string") continue;
data[i][key] = data[i][key].replaceAll(/\n/g, " | ");
}
}

const csvData =
csvStringifier.getHeaderString() +
csvStringifier.stringifyRecords(data);

return Buffer.from(csvData, "utf8");
};
19 changes: 19 additions & 0 deletions api/src/models/mongodb-export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import mongoose from "mongoose";

const exportSchema = new mongoose.Schema(
{
dataScope: {
type: String,
required: true,
},
fileName: {
type: String,
required: true,
},
},
{ timestamps: true }
);

const Export = mongoose.model("Export", exportSchema);

export default Export;
23 changes: 23 additions & 0 deletions api/src/router/exportsRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import express from "express";
import exportsRoutes from "../routes/exportsRoutes.js";
import Export from "../models/mongodb-export.js";
import { exportData } from "../services/exports.service.js";
import { dataToCSV } from "../lib/dataToCSV.js";
import path from "path";
import fs from "fs";
const { requestExport, getExports, getExport, removeExport } = exportsRoutes(
exportData,
Export,
dataToCSV,
path,
fs
);

const router = express.Router();

router.get("/", getExports);
router.get("/:id", getExport);
router.post("/", requestExport);
router.delete("/:id", removeExport);

export default router;
86 changes: 86 additions & 0 deletions api/src/routes/exportsRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
export default (exportData, Export, dataToCSV, path, fs) => ({
requestExport: async (req, res) => {
try {
const dataScope = req.body.dataScope;
const data = await exportData(dataScope);
const csv = await dataToCSV(data);

const currentModuleFile = new URL(import.meta.url).pathname;
const currentModuleDirectory = path.dirname(currentModuleFile);

const fileName = `${dataScope}-${new Date()
.toLocaleDateString("fr-FR")
.replaceAll("/", "-")}-${Math.random()
.toString(36)
.slice(2)}.csv`;
const filePath = path.join(
currentModuleDirectory,
"../../uploads/exports",
fileName
);
fs.writeFileSync(filePath, csv);
await Export.create({
dataScope,
fileName,
});
console.log(`Exported ${dataScope} successfully`);
return res.json({ fileName });
} catch (err) {
console.error(err);
return res.sendStatus(500);
}
},
getExports: async (req, res) => {
try {
const exports = await Export.find();
return res.status(200).json(exports);
} catch (err) {
console.error(err);
return res
.status(500)
.json(`Error while getting exports: ${err.message}`);
}
},
getExport: async (req, res) => {
try {
const exportToFind = await Export.findById(req.params.id);
return res.status(200).json(exportToFind);
} catch (err) {
console.error(err);
return res.sendStatus(500);
}
},
removeExport: async (req, res) => {
try {
const exportToRemove = await Export.findById(req.params.id);
await Export.deleteOne({ _id: req.params.id });

const currentModuleFile = new URL(import.meta.url).pathname;
const currentModuleDirectory = path.dirname(currentModuleFile);

const filePath = path.join(
currentModuleDirectory,
"../../uploads/exports",
exportToRemove.fileName
);
if (fs.existsSync(filePath)) {
fs.unlink(filePath, (err) => {
if (err) {
console.error(
`Error deleting ${filePath}: ${err.message}`
);
} else {
console.log(`Deleted ${filePath}`);
}
});
} else {
console.log(`${filePath} does not exist.`);
}

return res.sendStatus(200);
} catch (err) {
console.error(err);
return res.sendStatus(500);
}
},
});
70 changes: 70 additions & 0 deletions api/src/services/exports.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import User from "../models/postgres-user.js";
import Product from "../models/postgres-product.js";
import Model from "../models/postgres-model.js";
import Brand from "../models/postgres-brand.js";
import Category from "../models/postgres-category.js";
import Order from "../models/postgres-order.js";
import { columnNames } from "../lib/columnsNames.js";

export const exportData = async (dataScope) => {
let data;
switch (dataScope) {
case "users":
const users = await User.findAll({
attributes: {
exclude: [
"password",
"passwordResetToken",
"encryptionKey",
"authentificationToken",
],
},
});
data = users.map((user) => user.dataValues);
break;
case "products":
const products = await Product.findAll();
data = products.map((product) => product.dataValues);
break;
case "models":
const models = await Model.findAll();
data = models.map((model) => model.dataValues);
break;
case "brands":
const brands = await Brand.findAll();
data = brands.map((brand) => brand.dataValues);
break;
case "categories":
const categories = await Category.findAll();
data = categories.map((category) => category.dataValues);
break;
case "orders":
const orders = await Order.findAll();
data = orders.map((order) => order.dataValues);
break;
default:
data = [];
console.log("dataScope not found");
break;
}
data = data.map((item) => {
const formattedItem = {};

for (const key in item) {
if (Object.prototype.hasOwnProperty.call(item, key)) {
const formattedKey = columnNames[key] || key;

if (item[key] instanceof Date) {
formattedItem[formattedKey] =
item[key].toLocaleString("fr-FR");
} else {
formattedItem[formattedKey] = item[key];
}
}
}

return formattedItem;
});

return data;
};
2 changes: 1 addition & 1 deletion client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<link rel="manifest" href="/favicon/site.webmanifest">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
<title>Vite App</title>
<title>Sneak Peak</title>
</head>
<body>
<div id="app"></div>
Expand Down
2 changes: 1 addition & 1 deletion dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
<title>Sneak Peak</title>
</head>
<body>
<div id="app"></div>
Expand Down
Loading

0 comments on commit 28c52a4

Please sign in to comment.