Skip to content

Commit

Permalink
Merge pull request #67 from jamesspearsv/dev
Browse files Browse the repository at this point in the history
Merge admin reporting feature into main branch
  • Loading branch information
jamesspearsv authored Dec 18, 2024
2 parents 480f48b + 5c5b3d7 commit bce6f30
Show file tree
Hide file tree
Showing 37 changed files with 845 additions and 141 deletions.
19 changes: 18 additions & 1 deletion api/package-lock.json

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

3 changes: 2 additions & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
},
"devDependencies": {
"concurrently": "^9.0.1",
"nodemon": "^3.1.7"
"nodemon": "^3.1.7",
"prettier": "^3.4.1"
}
}
5 changes: 4 additions & 1 deletion api/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ app.use((error, req, res, next) => {
console.error(error);
res
.status(error.statusCode || 500)
.json({ message: error.message || "Server Error", details: error.details });
.json({
message: error.message || "Server Error",
details: error.details || "None",
});
});

app.listen(PORT, () => {
Expand Down
69 changes: 66 additions & 3 deletions api/src/controllers/adminController.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/* Middleware to handle actions requiring admin authorization */
const queries = require("../db/queries");
const queries = require("../db/queries/adminQueries");
const { selectAllFromTable } = require("../db/queries/appQueries");
const { BadRequestError } = require("../lib/errorsClasses");
const parseMonthRange = require("../lib/parseMonthRange");

// Get all rows from a given table
async function tableGet(req, res, next) {
try {
table = req.table;
const rows = await queries.selectAllFromTable(table);
const rows = await selectAllFromTable(table);

return res.json({ message: "ok", rows });
} catch (error) {
Expand Down Expand Up @@ -107,7 +109,7 @@ async function interactionsGet(req, res, next) {
queries.selectInteractions(limit, offset),
]);

res.json({ message: "ok", total_rows, rows });
return res.json({ message: "ok", total_rows, rows });
} catch (error) {
return next(error);
}
Expand All @@ -123,6 +125,66 @@ async function countTable(req, res, next) {
}
}

async function adminReportGet(req, res, next) {
try {
const { startMonth, endMonth, category } = req.query;
if (!startMonth || !endMonth || !category) {
throw new BadRequestError("Start, end, and category must be provided");
}

if (!["type", "location", "format"].includes(category)) {
throw new BadRequestError("Invalid category provided");
}

// query database for cumulative interactions counts during range
const total_interactions = await queries.countInteractionsAdmin(
startMonth,
endMonth,
);
const total_detailed = await queries.countInteractionByCategoryAdmin(
startMonth,
endMonth,
category,
);

// parse range between start and end month
const range = parseMonthRange(startMonth, endMonth);
const monthly_details = [];
// query database for each month in range
for (const month of range) {
const rows = await queries.countInteractionsByCategoryByMonth(
month,
category,
);

const monthObject = {
month,
};

// push data from each row to new formatted object
for (const row of rows) {
monthObject[row.value] = row.number_of_interactions;
}

// push month object to monthly_details array
monthly_details.push(monthObject);
}

const keys = Object.keys(monthly_details[0]);

res.json({
message: "ok",
range,
total_interactions,
total_detailed,
monthly_details,
keys,
});
} catch (error) {
return next(error);
}
}

module.exports = {
rowGetById,
tableGet,
Expand All @@ -131,4 +193,5 @@ module.exports = {
statsGet,
interactionsGet,
countTable,
adminReportGet,
};
2 changes: 1 addition & 1 deletion api/src/controllers/appController.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Middleware to handle actions not requiring admin authorization
*/

const queries = require("../db/queries");
const queries = require("../db/queries/appQueries");
const { BadRequestError } = require("../lib/errorsClasses");

// get all rows from types, locations, and formats tables
Expand Down
157 changes: 157 additions & 0 deletions api/src/db/queries/adminQueries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
const db = require("../connection");
const { DatabaseError } = require("../../lib/errorsClasses");

// select paginated subsection of interactions table
async function selectInteractions(limit, offset) {
try {
return await db("interactions")
.select(
"interactions.id",
"types.value as type",
"locations.value as location",
"formats.value as format",
"date",
)
.join("types", "interactions.type_id", "=", "types.id")
.join("locations", "interactions.location_id", "=", "locations.id")
.join("formats", "interactions.format_id", "=", "formats.id")
.limit(limit)
.offset(offset);
} catch (error) {
throw DatabaseError(error.message);
}
}

// Select row by id from a given table
async function selectRowFromTable(table, id) {
try {
return await db(table).select("*").where("id", id).first();
} catch (error) {
throw new DatabaseError(error.message);
}
}

// Update row by id from a given table
async function updateRowFromTable(table, id, values) {
try {
const rows = await db(table).where("id", id).update(values, ["*"]);
return rows[0];
} catch (error) {
throw new DatabaseError(error.message);
}
}

// Insert new row into specified table
async function insertRow(table, row) {
try {
const rows = await db(table).insert(row, ["*"]);
return rows[0];
} catch (error) {
throw new DatabaseError(error.message);
}
}

// Count all rows in interactions table
async function countAllInteractions() {
try {
const count = await db("interactions")
.count("interactions.id as total")
.first();

return count.total;
} catch (error) {
throw new DatabaseError(error.message);
}
}

// Count rows in interaction grouped by a given group
async function countAllInteractionByGroup(group) {
try {
return await db(`${group}s`)
.select(`${group}s.id`, `${group}s.value`)
.count("interactions.id as total_interactions")
.leftJoin("interactions", `${group}s.id`, "=", `interactions.${group}_id`)
.groupBy(`${group}s.id`);
} catch (error) {
throw new DatabaseError(error.message);
}
}

async function countRowsInTable(table) {
try {
const rows = await db.raw(`SELECT COUNT(id) AS total_rows FROM :table:`, {
table,
});

return rows[0].total_rows;
} catch (error) {
throw new DatabaseError(error.message);
}
}

async function countInteractionsAdmin(start, end) {
const rows = await db("interactions")
.count("* as total_interactions")
.whereRaw("strftime('%Y-%m', date) BETWEEN :start AND :end", {
start,
end,
})
.first();

return rows.total_interactions;
}

async function countInteractionByCategoryAdmin(start, end, category) {
const table = `${category}s`;

const row = await db(table)
.select(`${table}.id`, `${table}.value`)
.count("interactions.id as number_of_interactions")
.leftJoin("interactions", `${table}.id`, `interactions.${category}_id`)
.whereRaw("strftime('%Y-%m', date) BETWEEN :start AND :end", { start, end })
.groupBy(`${table}.id`);

const rows = await db(table)
.select(`${table}.id`, `${table}.value`)
.count("interactions.id as number_of_interactions")
.leftJoin(
db.raw(
"interactions ON ??=?? AND strftime('%Y-%m', interactions.date) BETWEEN ? AND ?",
[`${table}.id`, `interactions.${category}_id`, start, end],
),
)
.groupBy(`${table}.id`);

return rows;
}

async function countInteractionsByCategoryByMonth(month, category) {
try {
const table = `${category}s`;

return await db(table)
.select(`${table}.id`, `${table}.value`)
.count("interactions.id as number_of_interactions")
.leftJoin(
db.raw(
"interactions ON ??=?? AND strftime('%Y-%m', interactions.date)=?",
[`interactions.${category}_id`, `${table}.id`, month],
),
)
.groupBy(`${table}.id`);
} catch (error) {
throw new DatabaseError(error.message);
}
}

module.exports = {
selectInteractions,
selectRowFromTable,
updateRowFromTable,
insertRow,
countAllInteractionByGroup,
countRowsInTable,
countInteractionsAdmin,
countInteractionByCategoryAdmin,
countInteractionsByCategoryByMonth,
};
Loading

0 comments on commit bce6f30

Please sign in to comment.