Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make Boiler Books club generic #211

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 18 additions & 7 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,24 +1,35 @@
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:16

COPY config/ieee-money.sql /tmp/config/
# Copy configuration files
COPY config/boilerbooks.sql /tmp/config/
COPY .devcontainer/sql-presetup.sql /tmp/config/
COPY config/sql-setup.sql /tmp/config/
COPY config/nginx-dev.conf /tmp/config/

# Copy files for development
COPY .devcontainer/sample-data.sql /tmp/config/sample-data.sql
COPY .devcontainer/aliases /etc/aliases

RUN echo "postfix postfix/mailname string localhost" | debconf-set-selections\
# Preselect some options for postfix
RUN echo "postfix postfix/mailname string localhost" | debconf-set-selections \
&& echo "postfix postfix/main_mailer_type string 'Internet Site'" | debconf-set-selections

# Install required packages
RUN apt update && export DEBIAN_FRONTEND=noninteractive \
&& apt -y install git mariadb-server-10.5 nginx postfix
RUN service mariadb start && mariadb < /tmp/config/ieee-money.sql && mariadb < /tmp/config/sql-setup.sql
RUN mv /tmp/config/nginx-dev.conf /etc/nginx/sites-enabled/default \
&& service nginx reload

# Setup the database
RUN service mariadb start && mariadb < /tmp/config/boilerbooks.sql && mariadb < /tmp/config/sql-presetup.sql && mariadb < /tmp/config/sql-setup.sql

# Setup NGINX
RUN mv /tmp/config/nginx-dev.conf /etc/nginx/sites-enabled/default && nginx -t

# Setup Postfix
RUN service postfix start && newaliases

# Create a sample dataset
RUN mkdir /var/log/boilerbooks && chown node:node /var/log/boilerbooks
RUN mkdir /var/www/receipts && chown node:node /var/www/receipts

RUN service mariadb start && mariadb ieee-money < /tmp/config/sample-data.sql
RUN service mariadb start && mariadb boilerbooks < /tmp/config/sample-data.sql
ADD .devcontainer/receipts /var/www/receipts/
ADD .devcontainer/assets /var/www/assets/
104 changes: 104 additions & 0 deletions .devcontainer/assets/pieee-kite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions .devcontainer/sample-data.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
-- MariaDB dump 10.19 Distrib 10.5.15-MariaDB, for debian-linux-gnu (x86_64)
--
-- Host: localhost Database: ieee-money
-- Host: localhost Database: boilerbooks
-- ------------------------------------------------------
-- Server version 10.5.15-MariaDB-0+deb11u1

Expand All @@ -20,7 +20,7 @@

LOCK TABLES `Budget` WRITE;
/*!40000 ALTER TABLE `Budget` DISABLE KEYS */;
INSERT INTO `Budget` VALUES (1,'General Items',100.00,1,8,'Approved'),(2,'Specific Items',100.00,1,8,'Approved'),(3,'General Items',100.00,2,8,'Approved'),(4,'Specific Items',100.00,2,8,'Approved'),(5,'General Items',100.00,3,8,'Approved'),(6,'Specific Items',100.00,3,8,'Approved'),(7,'General Items',100.00,4,8,'Approved'),(8,'Specific Items ',100.00,4,8,'Approved'),(11,'General Items',100.00,6,8,'Submitted'),(12,'Specific Items',100.00,6,8,'Submitted'),(15,'General Items',100.00,7,8,'Submitted'),(16,'Specific Items ',100.00,7,8,'Submitted'),(17,'General Items',100.00,5,8,'Submitted'),(18,'Specific Items',100.00,5,8,'Submitted');
INSERT INTO `Budget` VALUES (1,'General Items',100.00,2,8,'Approved'),(2,'Specific Items',100.00,2,8,'Approved'),(3,'General Items',100.00,3,8,'Approved'),(4,'Specific Items',100.00,3,8,'Approved'),(5,'General Items',100.00,4,8,'Approved'),(6,'Specific Items',100.00,4,8,'Approved'),(7,'General Items',100.00,5,8,'Approved'),(8,'Specific Items ',100.00,5,8,'Approved'),(11,'General Items',100.00,7,8,'Submitted'),(12,'Specific Items',100.00,7,8,'Submitted'),(15,'General Items',100.00,8,8,'Submitted'),(16,'Specific Items ',100.00,8,8,'Submitted'),(17,'General Items',100.00,56,8,'Submitted'),(18,'Specific Items',100.00,6,8,'Submitted');
/*!40000 ALTER TABLE `Budget` ENABLE KEYS */;
UNLOCK TABLES;

Expand Down Expand Up @@ -50,7 +50,7 @@ UNLOCK TABLES;

LOCK TABLES `Purchases` WRITE;
/*!40000 ALTER TABLE `Purchases` DISABLE KEYS */;
INSERT INTO `Purchases` VALUES (1,'2022-11-13 06:18:00','pp',NULL,'Test Item 1','General Reason Given','Some Vendor',1,'General Items',NULL,20.00,'Denied',NULL,NULL,'','Pick-up',8),(2,'2022-11-13 06:07:27','pp',NULL,'Test Item 2','Specific Reason Given','A different vendor',1,'Specific Items',NULL,4.60,'Requested',NULL,NULL,'','Mailed',8),(3,'2022-11-13 06:30:00','pp',NULL,'Technical Items','Creating project components','Another vendor',2,'General Items',NULL,43.23,'Approved','trainboi','Cash','Approver changed funding source','Pick-up',8),(4,'2022-11-13 06:30:31','pp','2022-11-13 00:00:00','Challenging Supplies','Future Preparedness, changed by approver','One more vendor',3,'Specific Items','/receipts/Computer_Society_pp_Challenging_Supplies_4.jpg',10.00,'Purchased','mdma','BOSO','https://www.youtube.com/watch?v=dQw4w9WgXcQ','Mailed',8),(5,'2022-11-13 06:28:28','mdma',NULL,'Paper Products','Supplies for Events','Big Box Store',3,'General Items',NULL,30.00,'Denied','mdma','BOSO','','Mailed',8),(6,'2023-03-31 01:45:10','mdma','/receipts/MTT-S_trainboi_A_pile_of_wires_6.jpg','A pile of wires','To build radio equipment','The Wire Store',5,'General Items',NULL,25.76,'Processing Reimbursement','trainboi','SOGA','','Mailed',8);
INSERT INTO `Purchases` VALUES (1,'2022-11-13 06:18:00','pp',NULL,'Test Item 1','General Reason Given','Some Vendor',2,'General Items',NULL,20.00,'Denied',NULL,NULL,'','Pick-up',8),(2,'2022-11-13 06:07:27','pp',NULL,'Test Item 2','Specific Reason Given','A different vendor',2,'Specific Items',NULL,4.60,'Requested',NULL,NULL,'','Mailed',8),(3,'2022-11-13 06:30:00','pp',NULL,'Technical Items','Creating project components','Another vendor',3,'General Items',NULL,43.23,'Approved','trainboi','Cash','Approver changed funding source','Pick-up',8),(4,'2022-11-13 06:30:31','pp','2022-11-13 00:00:00','Challenging Supplies','Future Preparedness, changed by approver','One more vendor',4,'Specific Items','/receipts/Computer_Society_pp_Challenging_Supplies_4.jpg',10.00,'Purchased','mdma','BOSO','https://www.youtube.com/watch?v=dQw4w9WgXcQ','Mailed',8),(5,'2022-11-13 06:28:28','mdma',NULL,'Paper Products','Supplies for Events','Big Box Store',4,'General Items',NULL,30.00,'Denied','mdma','BOSO','','Mailed',8),(6,'2023-03-31 01:45:10','mdma','/receipts/MTT-S_trainboi_A_pile_of_wires_6.jpg','A pile of wires','To build radio equipment','The Wire Store',6,'General Items',NULL,25.76,'Processing Reimbursement','trainboi','SOGA','','Mailed',8);
/*!40000 ALTER TABLE `Purchases` ENABLE KEYS */;
UNLOCK TABLES;

Expand All @@ -70,7 +70,7 @@ UNLOCK TABLES;

LOCK TABLES `approval` WRITE;
/*!40000 ALTER TABLE `approval` DISABLE KEYS */;
INSERT INTO `approval` VALUES (2,'pain','Treasurer',1,1000000,'*',6),(3,'pain','Treasurer',2,0,'*',6),(4,'pain','Treasurer',3,0,'*',6),(5,'pain','Treasurer',4,0,'*',6),(6,'pain','Treasurer',5,0,'*',6),(7,'pain','Treasurer',6,0,'*',6),(8,'pain','Treasurer',7,0,'*',6),(9,'pain','Treasurer',8,0,'*',6),(10,'mdma','Committee Chair',3,1000000,'*',4),(11,'trainboi','Project Lead',2,100,'*',2);
INSERT INTO `approval` VALUES (2,'pain','Treasurer',2,1000000,'*',6),(3,'pain','Treasurer',3,0,'*',6),(4,'pain','Treasurer',4,0,'*',6),(5,'pain','Treasurer',5,0,'*',6),(6,'pain','Treasurer',6,0,'*',6),(7,'pain','Treasurer',7,0,'*',6),(8,'pain','Treasurer',8,0,'*',6),(9,'pain','Treasurer',9,0,'*',6),(10,'mdma','Committee Chair',4,1000000,'*',4),(11,'trainboi','Project Lead',3,100,'*',2);
/*!40000 ALTER TABLE `approval` ENABLE KEYS */;
UNLOCK TABLES;

Expand All @@ -92,7 +92,7 @@ UNLOCK TABLES;

LOCK TABLES `committees` WRITE;
/*!40000 ALTER TABLE `committees` DISABLE KEYS */;
INSERT INTO `committees` VALUES (2,'Aerial Robotics','aerial','Active','Active'),(3,'Computer Society','csociety','Active','Active'),(4,'EMBS','embs','Active','Active'),(5,'MTT-S','mtt-s','Active','Active'),(6,'Racing','racing','Active','Active'),(7,'ROV','rov','Active','Active'),(8,'SOGA','soga','Active','Inactive'),(9,'Growth & Engagement','ge','Inactive','Active'),(10,'Learning','learning','Inactive','Active'),(11,'Social','social','Inactive','Active'),(12,'Software Saturdays','swsat','Inactive','Active');
INSERT INTO `committees` VALUES (2,'General IEEE','general','Active','Active'),(3,'Aerial Robotics','aerial','Active','Active'),(4,'Computer Society','csociety','Active','Active'),(5,'EMBS','embs','Active','Active'),(6,'MTT-S','mtt-s','Active','Active'),(7,'Racing','racing','Active','Active'),(8,'ROV','rov','Active','Active'),(9,'SOGA','soga','Active','Inactive'),(10,'Growth & Engagement','ge','Inactive','Active'),(11,'Learning','learning','Inactive','Active'),(12,'Social','social','Inactive','Active'),(13,'Software Saturdays','swsat','Inactive','Active');
/*!40000 ALTER TABLE `committees` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
Expand Down
5 changes: 5 additions & 0 deletions .devcontainer/sql-presetup.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-- Create a user for the database, but only for development setups

CREATE USER 'boilerbooks'@'localhost' IDENTIFIED BY 'testpassword';
GRANT INSERT,UPDATE,DELETE,SELECT ON `boilerbooks`.* TO 'boilerbooks'@'localhost';
FLUSH PRIVILEGES;
58 changes: 49 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
# Boiler Books

The ultimate IEEE financial recordkeeping system! Boiler Books is used to track income, expenses, dues, and more across all of Purdue IEEE. Originally written with PHP in 2016, it has since been rewritten in JavaScript in 2022.
The ultimate student organization financial recordkeeping system!
Boiler Books is used to track income, expenses, dues, and more across an entire club.
Originally written with PHP in 2016, it has since been rewritten in JavaScript in 2022.

This system has three components: the database, the API, and the UI. The database is a standard MySQL installation, the API is an Express server, and the UI is a Vue SPA.

Boiler Books is hosted at [money.purdueieee.org](https://money.purdueieee.org).
## Public Demos

## Documentation
A public demo Boiler Books instance _will_ be hosted at [fake-money.purdueieee.org](https://fake-money.purdueieee.org).
This demo will reset itself every 12 hours.

All the documentation is available in the `docs/` folder, broken up by topic. For a full reference of server infrastructure, refer to the **Purdue IEEE Infrastructure Guide** stored with the Purdue IEEE president.
The following usernames / passwords will be active for the demo instance:

* Updating the Fiscal Year to match the current one: [updating_fiscalyear.md](docs/updating_fiscalyear.md)
* Setting up a development environment: [development.md](docs/development.md)
* Deploying app for production: [deployment.md](docs/deployment.md)
* API endpoint documentation: [api_endpoint.md](docs/api_endpoints.md)
* username / password / role
* master / password / Admin Account
* treas / password / Treasurer
* officer / password / Officer
* internal / password / Internal Leader
* member / password / Regular Member

<hr>

Purdue IEEE's Boiler Books instance is hosted at [money.purdueieee.org](https://money.purdueieee.org).

## Features

Expand All @@ -27,4 +36,35 @@ All users can see the dues they have paid thus far and officers can view all due

Officers must submit budgets to the treasurer who can approve the budget request for the current fiscal year. Once approved, any user can create purchase requests.

The treasurer can add and remove authorized committee members, officers, and other treasurers from the permission list.
The treasurer or admin can add and remove authorized committee members, officers, and other treasurers from the permission list.

The treasurer or admin can create new committees and fiscal years right from the interface.

## Documentation

All the documentation is available in the `docs/` folder, broken up by topic.

* Updating the Fiscal Year to match the current one: [updating_fiscalyear.md](docs/updating_fiscalyear.md)
* Setting up a development environment: [development.md](docs/development.md)
* Deploying app for production: [deployment.md](docs/deployment.md)
* API endpoint documentation: [api_endpoint.md](docs/api_endpoints.md)

## Before You Deploy

The prebuilt UI Docker image expects you to be using the Purdue IEEE SSO system.
Because the constants are baked in at build time, if you are not utilizing this system you will need to build the image from scratch after modifications.

Also, the committee with ID #2 is assumed to be a general fund. Treasurers will only be assigned purchase approval powers on this general fund.

### Docker Image Tag Policy

We tag and maintain a few series of Docker image tags:
* `latest`, `2.3`, `2` : latest release
* `master`, `master-dev` (UI only) : latest master commit

The Boiler Books release cadence is around 2 times a year.
If there is an urgent fix you need, the `master` tags on the prebuilt Docker images are always built from the latest master.

## Support

If you encounter a bug or want to suggest a new feature, create a [new issue](https://github.com/PurdueIEEE/boilerbooks/issues/new). If you have an urgent problem, reach out to our infrastructure team via email at [ieee-infrastructure@purdueieeeorg](mailto:[email protected]).
11 changes: 11 additions & 0 deletions api/.env.git
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ SEND_MAIL=yes
# Details for the SMTP server designated for emails
SMTP_HOST=localhost
SMTP_PORT=25
[email protected]

# The URL pointing to this server
HTTP_HOST=localhost
Expand All @@ -34,3 +35,13 @@ OIDC_SERVER=https://example.com
OIDC_CLIENT_ID=example-client
OIDC_CLIENT_SECRET=client-secret-here
OIDC_REDIRECT_URI=http://example.com/callback

# UI settings
UI_NAV_TEXT=Club Name
UI_NAV_IMAGE=/path/to/image.ext
UI_NAV_LINK=https://example.com
# - valid options are 'password', 'oidc'
UI_LOGIN_TYPE=password
UI_LOGIN_OIDC_NAME=Example Provider
# - url for OIDC profile management
UI_LOGIN_OIDC_PROFILE=https://sso.example.com/profile
4 changes: 2 additions & 2 deletions api/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "boilerbooks-api",
"version": "2.0.0",
"description": "Backend API for the Purdue IEEE boilerbooks accounting software",
"version": "2.2",
"description": "Backend API for the Boiler Books accounting software",
"main": "src/server.js",
"private": true,
"type": "module",
Expand Down
1 change: 1 addition & 0 deletions api/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ app.use("/access", routes.access);
app.use("/dues", routes.dues);
app.use("/search", routes.search);
app.use("/infra", routes.infra);
app.use("/ui/", routes.ui);

// conditionally mount login routes
if (process.env.USE_OIDC === "true") {
Expand Down
2 changes: 1 addition & 1 deletion api/src/controllers/oidc.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ async function post_oidc_register(req, res, next) {
// Username already exists (should not ever get hit)
if (err.code === "ER_DUP_ENTRY") {
logger.error("DUPLICATE USERNAME/EMAIL:" + req.body.uname);
res.status(400).send("Unexpected Error: Please contact Purdue IEEE!");
res.status(400).send("Unexpected Error: Please contact the system administrator!");
return next();
} else {
logger.error(err.stack);
Expand Down
15 changes: 13 additions & 2 deletions api/src/middleware/checkAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,20 @@
import Models from "../models/index.js";
import { logger } from "../utils/logging.js";

const unprivileged_endpoints = ["/login", "/oidc", "/ui"];

function is_endpoint_protected(url) {
for (let endpoint of unprivileged_endpoints) {
if (url.startsWith(endpoint)) {
return false;
}
}
return true;
}

async function checkAPI(req, res, next) {
// If we are attempting to go to the /, /login, or /oidc endpoints, don't authenticate
if (req.originalUrl.startsWith("/login") || req.originalUrl.startsWith("/oidc") || req.originalUrl == "/") {
// If we are attempting to go to an unprotected endpoint, don't authenticate
if (req.originalUrl === "/" || !is_endpoint_protected(req.originalUrl)) {
req.context = {};
next();
} else {
Expand Down
6 changes: 6 additions & 0 deletions api/src/middleware/logging.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ import { logger } from "../utils/logging.js";
async function apiLogger(req, res, next) {
// Log every route and it's result
// does not catch invalid API keys

if (req.originalUrl === "/") {
return next(); // Don't clutter logs with a key check
}

if (req.originalUrl.startsWith("/ui")) {
return next(); // Don't clutter logs with UI loading calls
}

logger.info(`[${req.context.request_user_id ? req.context.request_user_id : ""}] - Return ${res.statusCode} - "${req.method} ${req.originalUrl}"`);
next();
}
Expand Down
11 changes: 11 additions & 0 deletions api/src/models/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ async function getUserTreasurer(user) {
);
}

async function getUserTreasurerButExcludeAdmin(user) {
return db_conn.promise().execute(
`SELECT COUNT(U3.username) as validuser FROM Users U3
INNER JOIN approval A ON U3.username = A.username
WHERE A.privilege_level >= ? AND U3.username = ?
AND A.committee != 1`,
[ACCESS_LEVEL.treasurer, user]
);
}

async function getUserApprovalCommittees(user) {
return db_conn.promise().execute(
"SELECT committee FROM approval WHERE username = ? AND privilege_level > ?",
Expand Down Expand Up @@ -205,6 +215,7 @@ export default {
updatePassword,
getUserApprovals,
getUserTreasurer,
getUserTreasurerButExcludeAdmin,
getUserApprovalCommittees,
setPasswordResetDetails,
checkResetTime,
Expand Down
12 changes: 7 additions & 5 deletions api/src/models/purchase.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,11 +162,13 @@ async function getTreasurer(id) {
(SELECT CONCAT(U.first, ' ', U.last) FROM Users U WHERE U.username = p.username) purchasedby,
(SELECT CONCAT(U2.first, ' ', U2.last) FROM Users U2 WHERE U2.username = p.approvedby) approvedby
FROM Purchases p
WHERE p.status in ('Purchased','Processing Reimbursement')
AND ? in (
SELECT U3.username FROM Users U3
INNER JOIN approval A ON U3.username = A.username
WHERE (A.privilege_level >= ?))
WHERE p.status IN ('Purchased','Processing Reimbursement')
AND ? IN (
SELECT U3.username FROM Users U3
INNER JOIN approval A ON U3.username = A.username
WHERE (A.privilege_level >= ?)
AND A.committee != 1
)
ORDER BY p.purchasedate DESC`,
[id, ACCESS_LEVEL.treasurer]
);
Expand Down
Loading