Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
eddf1e3
Merge branch 'create-config-table' into temprary-create-endpoint-fetc…
bkarimii Mar 22, 2025
604ffc4
Merge branch 'create-function-decide-status-of-user' into temprary-cr…
bkarimii Mar 22, 2025
cf566d9
Merge branch 'decideScore-should-read-weights' into temprary-create-e…
bkarimii Mar 22, 2025
a352ffc
Merge branch 'create-function-decide-status-of-user' into temprary-cr…
bkarimii Mar 22, 2025
1ea3848
endpoint sends a summary of all users status
bkarimii Mar 22, 2025
4018e8a
Merge branch 'main' into temprary-create-endpoint-fetch-summary-of-st…
bkarimii Apr 5, 2025
19cc35c
suggested changes applied
bkarimii Mar 29, 2025
8e8a5d0
changes applied
bkarimii Mar 29, 2025
e5c8c88
an endpoint that read the body and updates values in config table
bkarimii Mar 22, 2025
948780d
reactionRecieved passed to the desideScore function based on the late…
bkarimii Apr 7, 2025
980a502
endpoint updated with lates changes from main branch
bkarimii Apr 7, 2025
9ffcfe0
Merge branch 'main' into temprary-create-endpoint-fetch-summary-of-st…
bkarimii Apr 7, 2025
32038a5
formatting issue fixed
bkarimii Apr 7, 2025
8e654ee
eslint format issue fixed
bkarimii Apr 7, 2025
f199a7b
empty line commit
bkarimii Apr 10, 2025
6c7b5c9
CI tests applied on staging branch
bkarimii Apr 10, 2025
b9043af
new changes to debug production failure
bkarimii Apr 12, 2025
a0b7126
Merge pull request #121 from bkarimii/test-branch-render-debug
bkarimii Apr 12, 2025
fab7fb9
function refactored to log errors and throw error in failure cases.
bkarimii Apr 12, 2025
5a21c75
function refactored - it throws error in failure cases - log errors a…
bkarimii Apr 12, 2025
2bb8417
Merge pull request #122 from bkarimii/test-branch-render-debug
bkarimii Apr 12, 2025
4494cec
Merge pull request #118 from bkarimii/temprary-create-endpoint-fetch-…
bkarimii Apr 12, 2025
6ffede4
decideScore updated to get wieghts from DB
bkarimii Apr 19, 2025
c2a445b
decidestatus refactored to accepts an array of scores, and treshhold …
bkarimii Apr 19, 2025
4824136
getPlotBox added to return an object contains data for a plotbox diagram
bkarimii Apr 19, 2025
f8845a7
scoreNormalised added , it returns an array of normalised scores
bkarimii Apr 19, 2025
90b723c
endpoint updated to return an onject of status , and plotbox data , f…
bkarimii Apr 19, 2025
f76d2f7
test file temprarily removed to pass github checks.
bkarimii Apr 19, 2025
6b87333
config endpoint updated with new constraint for treshholds
bkarimii Apr 21, 2025
d26e51e
successful response object key changed
bkarimii Apr 21, 2025
818b6d3
JSDocs added
bkarimii Apr 21, 2025
ae503a4
rounding the normalised score
bkarimii Apr 22, 2025
4238050
swagger installed
bkarimii Apr 25, 2025
84fc9d6
swagger file added
bkarimii Apr 25, 2025
fcba247
swagger setup in app.js file
bkarimii Apr 25, 2025
f224777
endpoints updated with documentation
bkarimii Apr 25, 2025
c60b27d
swagger added to the lock file
bkarimii Apr 25, 2025
73aedc6
Merge pull request #123 from bkarimii/test-scale-comparison
bkarimii Apr 25, 2025
9119d95
Merge branch 'staging' into writ-api-documentation
bkarimii Apr 25, 2025
5e7be66
swagger doc for new endpoint
bkarimii Apr 25, 2025
3373b0e
Merge pull request #125 from bkarimii/writ-api-documentation
bkarimii Apr 28, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: CI

on:
push:
branches: [main]
branches: [main, staging]
pull_request:
branches: [main]
branches: [main, staging]

jobs:
windows:
Expand Down
329 changes: 321 additions & 8 deletions api/api.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Router } from "express";

import db from "./db.js";
import { decideStatus } from "./functions/decideStatus.js";
import { getBoxPlotData } from "./functions/getBoxPlotData.js";
import { lookupEmail } from "./functions/lookupEmail.js";
import { processImportFiles } from "./functions/processImportFiles.js";
import { scoreNormaliser } from "./functions/scoreNormaliser.js";
import { updateDbUsers } from "./functions/updateDbUsers.js";
import { updateUsersActivity } from "./functions/updateUsersActivity.js";
import messageRouter from "./messages/messageRouter.js";
Expand All @@ -14,6 +17,25 @@ const api = Router();

api.use("/message", messageRouter);

/**
* @swagger
* /subscribe:
* post:
* summary: Subscribe a user by email
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* email:
* type: string
* example: [email protected]
* responses:
* 302:
* description: Redirects on success or failure
*/
api.post("/subscribe", async (req, res) => {
const email = req.body.email;
try {
Expand Down Expand Up @@ -60,6 +82,51 @@ api.post("/subscribe", async (req, res) => {
}
});

/**
* @swagger
* /fetch-users:
* get:
* summary: Retrieve all users from the database
* responses:
* 200:
* description: A list of users
* content:
* application/json:
* schema:
* type: array
* items:
* type: object
* properties:
* id:
* type: integer
* name:
* type: string
* email:
* type: string
* 404:
* description: No users found
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: false
* message:
* type: string
* example: User not found
* 500:
* description: Internal server error
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Internal Server Error
*/
api.get("/fetch-users", async (req, res) => {
try {
const result = await db.query("SELECT * FROM all_users");
Expand All @@ -74,32 +141,256 @@ api.get("/fetch-users", async (req, res) => {
}
});

/**
* @swagger
* /upload:
* post:
* summary: Upload and process a Slack export file
* description: Accepts a Slack export ZIP file, extracts its contents, processes the data, and updates the database with user and activity information.
* requestBody:
* required: true
* content:
* multipart/form-data:
* schema:
* type: object
* properties:
* file:
* type: string
* format: binary
* description: The Slack export ZIP file to upload and process.
* responses:
* 200:
* description: The file was successfully processed, and the database was updated.
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* 400:
* description: Bad request. The uploaded file is invalid or missing.
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Invalid file upload.
* 500:
* description: Internal server error. An error occurred while processing the file or updating the database.
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Internal Server Error.
*/
api.post("/upload", processUpload, async (req, res) => {
try {
const slackZipBuffer = req.file.buffer;
const extractedDir = zipExtractor(slackZipBuffer);

const processedActivity = processImportFiles(extractedDir);

const isUsersInserted = await updateDbUsers(extractedDir, db);

if (!isUsersInserted.success) {
return res.status(500).json({});
}
await updateDbUsers(extractedDir, db);

const isActivityInserted = await updateUsersActivity(processedActivity, db);

if (!isActivityInserted.success) {
return res.status(500).json({});
if (isActivityInserted) {
logger.info(
"isActivityInserted in the upload endpoint? => inserted successfully",
);
return res.status(200).json({});
}

return res.status(200).json({});
return res.status(500).json({});
} catch (error) {
logger.error(error);
return res.status(500).json({});
}
});

/**
* @swagger
* /users/status-counts:
* get:
* summary: Get user activity status counts
* description: Retrieves user activity data within a specified date range, normalizes the scores, and calculates the status and box plot data.
* parameters:
* - in: query
* name: start_date
* required: true
* schema:
* type: string
* format: date
* description: The start date for the activity data (YYYY-MM-DD).
* - in: query
* name: end_date
* required: true
* schema:
* type: string
* format: date
* description: The end date for the activity data (YYYY-MM-DD).
* responses:
* 200:
* description: Successfully retrieved user activity status counts and box plot data.
* content:
* application/json:
* schema:
* type: object
* properties:
* status:
* type: array
* items:
* type: object
* properties:
* user_id:
* type: string
* description: The ID of the user.
* status:
* type: string
* description: The calculated status of the user.
* boxPlotData:
* type: object
* description: Data for generating a box plot of normalized scores.
* 400:
* description: Bad request. Missing or invalid query parameters.
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Missing or invalid query parameters.
* 500:
* description: Internal server error.
* content:
* application/json:
* schema:
* type: object
* properties:
* msg:
* type: string
* example: server error.
*/
api.get("/users/status-counts", async (req, res) => {
const startDate = req.query.start_date;
const endDate = req.query.end_date;
try {
const dbFetchedActivity = await db.query(
"select user_id , messages , reactions , reactions_received FROM slack_user_activity WHERE date BETWEEN $1 AND $2 ",
[startDate, endDate],
);
const userActivities = dbFetchedActivity.rows;

const rawUsers = await db.query("SELECT user_id FROM all_users");
const allusers = rawUsers.rows;

const rawConfigTable = await db.query("SELECT * FROM config_table");
const configTable = rawConfigTable.rows[0];

const normalisedScores = scoreNormaliser(
allusers,
userActivities,
configTable,
);

const boxPlotData = getBoxPlotData(normalisedScores);
const totalStatus = await decideStatus(normalisedScores, configTable);

return res
.status(200)
.json({ status: totalStatus, boxPlotData: boxPlotData });
} catch (error) {
res.status(500).json({ msg: "server error" });
}
});

/**
* @swagger
* /config:
* put:
* summary: Update configuration settings
* description: Updates the thresholds and weightings for the application configuration.
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* lowTreshholds:
* type: integer
* description: The low threshold value.
* example: 10
* mediumTreshholds:
* type: integer
* description: The medium threshold value.
* example: 20
* highTreshHolds:
* type: integer
* description: The high threshold value.
* example: 30
* messagesWeighting:
* type: number
* description: The weighting for messages.
* example: 1.5
* reactionsWeighting:
* type: number
* description: The weighting for reactions.
* example: 2.0
* reactionsReceivedWeighting:
* type: number
* description: The weighting for reactions received.
* example: 2.5
* responses:
* 200:
* description: Configuration updated successfully.
* content:
* application/json:
* schema:
* type: object
* properties:
* success:
* type: boolean
* example: true
* 400:
* description: Bad request. Invalid or missing input data.
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Invalid input data.
* 404:
* description: Configuration not found.
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Configuration not found.
* 500:
* description: Internal server error.
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: Internal Server Error.
*/
api.put("/config", async (req, res) => {
const {
lowTreshholds,
Expand All @@ -118,6 +409,28 @@ api.put("/config", async (req, res) => {
!Number.isFinite(reactionsWeighting) ||
!Number.isFinite(reactionsReceivedWeighting)
) {
logger.error("input values are inavalid");
return res.status(400).json({ message: "total weight must be 100" });
}

const totalWeights =
messagesWeighting + reactionsWeighting + reactionsReceivedWeighting;

if (totalWeights !== 100) {
res.status(400).json({});
}

if (lowTreshholds + mediumTreshholds + highTreshHolds !== 100) {
logger.error("Thresholds must add up to 100.");
return res.status(400).json({});
}

if (
!(lowTreshholds < mediumTreshholds && mediumTreshholds < highTreshHolds)
) {
logger.error(
"Thresholds must be in increasing order: low < medium < high.",
);
return res.status(400).json({});
}

Expand Down
Loading