GSOC 2023: Proposal for Administration System- Bookbrainz

Personal Information

  • Name: Riya Kumari

  • IRC nick: riyaku11

  • Email: riyaku217@gmail.com

  • Github: https://github.com/riyaku11

  • Location: Noida, India

  • Time zone: UTC +5:30 Kolkata

  • University: University of Delhi

  • Major: Information Technology and Mathematical Innovations

Project Description

  • Name: Implement an Administration System for Bookbrainz site

  • Proposed Mentor: @Monkey

  • Goals:

  1. Modifying the database schema to add tables for to define roles and attach users to roles

  2. Implementing an admin panel webpage to allow admins to perform privileged actions.

  3. Developing middlewares for securing specific routes according to a user’s roles

  4. Developing a page to allow privileged users to edit and add relationships and identifiers

  5. Implementing a public log of administration actions

Modifying the database schema

My proposed system suggests suggests three roles: Editor & Admin. So first of all I will be creating a TYPE column that includes these three types of users:


Create TYPE bookbrainz.Roles_type(
    ‘EDITOR’, ‘ADMIN’
);

Admin: Highest level
Editor: lowest level

To make the system future proof and to handle more complicated use roles and combination of roles, we can use flags with bit masking to determine the level of privileges as :

exports const Editor_flag = 1;
exports const Relationship_editor_flag = 2;
exports const Editing_disabled_flag = 4;
exports const Reindexing_disabled_flag = 8;
exports const Blocked_user_flag= 16;

and so on for all privileges.

Next step would be to modify the bookbrainz.editor table to include two fields: TYPE and PERMISSIONS . The PERMISSIONS field will have the AND of all the binary flags for the particular user.

Only the ADMINs will have the access to give permissions to specific users.

The users would be able to access only those routes which they are allowed to.

Implementing an Admin panel webpage

The next step would be to create an admin dashboard which is only accessible by users with the role as “Admin”. The page would contain all the necessary features like the total number of users, search users by Id, give privileged roles to different users (like upgrading editors to super-editors or admins), and block or delete abusive users.
Visual mockups for the admin dashboard

Admin

page to update user types and privileges

admin_user

Middlewares for securing specific routes

Next step is to create middlewares to check the role of a user

For example

exports.IsAdmin = async (req,res,next)=>{
   const role = req.user.type;
   if(role !=="admin"){
    return res.status(403).json({message: “You are not authorized to access this resource”})
}
    next();
};

The admin routes can be accessed by

router.route("/admin/user/:id").get(isAuthenticatedUser,isAdmin,getUserById);
router.route("/admin/user/:id").put(isAuthenticatedUser,isAdmin,updateRole);
router.route("/admin/user/:id").delete(isAuthenticatedUser,isAdmin,deleteUser);
router.route("/superEditor/edit").put(isAuthenticatedUser,isSuperEditor,EditRelationships)

Where the isAuthenticatedUser function checks whether a user is signed in or not.

npm install --save bookbrainz-data-js

Once installed, you can use the bookbrainz-data-js module to create a connection to the BookBrainz database and perform CRUD operations on the various database tables.

Functions:

const { User } = require('bookbrainz-data-js');
 
const getUserById = async (req, res, next) => {
  const userId = req.params.id;
  try {
    const user = await User.get(userId);
    if (!user) {
      return res.status(404).json({
        error: 'User not found'
      });
    }
    req.user = user;
    next();
  } catch (error) {
    return res.status(500).json({
      error: 'Error retrieving user from database'
    });
  }
};

The require(‘bookbrainz-data-js’) statement loads the bookbrainz-data-js module into the script. This module provides an interface for interacting with the BookBrainz database.

The first line of the function extracts the user ID from the req.params object. This assumes that the user ID is included in the URL parameters of the incoming request.

getUserById is an Express.js middleware function that takes three arguments: req, res, and next. This function is designed to be used as middleware in an Express application. When called, this function will attempt to retrieve a user from the BookBrainz database based on their ID.

const { User } = require('bookbrainz-data-js');

const updateUserRole = async (req, res, next) => {
  const userId = req.params.id;
  const newRoleId = req.body.roleId;

  try {
    const user = await User.where({ id: userId }).fetch();
    if (!user) {
      return res.status(404).json({
        error: 'User not found'
      });
    }

    await user.roles().detach(); // remove existing roles
    await user.roles().attach(newRoleId); // assign new role

    res.status(200).json({
      message: 'User role updated successfully'
    });
  } catch (error) {
    console.error(error);
    res.status(500).json({
      error: 'Error updating user role in database'
    });
  }
};

updateUserRole is an Express.js middleware function that updates the role of a user by the uses of the bookshelf ORM to fetch the user by their ID, detach any existing roles they have, and attach the new role specified in the request body. If there’s an error during this process, the function returns a JSON response with an error message and a 500 status code. If the update is successful, it returns a JSON response with a success message and a 200 status code.

const { User } = require('bookbrainz-data-js');
 
const deleteUser = async (req, res, next) => {
  const userId = req.params.id;
  try {
    const user = await User.get(userId);
    if (!user) {
      return res.status(404).json({
        error: 'User not found'
      });
    }
    await user.delete();
    res.status(200).json({
      message: 'User deleted successfully'
    });
  } catch (error) {
    return res.status(500).json({
      error: 'Error deleting user from database'
    });
  }
};

deleteUser is an Express.js middleware function that takes three arguments: req, res, and next. This function is designed to be used as middleware in an Express application. When called, this function will attempt to delete a user from the BookBrainz database based on their ID.

Developing a page to allow privileged users to edit and add relationships and identifiers

Create the user interface for the edit and add relationships and identifiers page. This could involve designing a form that allows users to input the necessary data for the relationships and identifiers, as well as providing fields for searching for and selecting existing entities to connect to.
This web page would likely have a user interface that allows users to search for and select the entity they want to edit or add information to. They could then modify or add relationships and identifiers through a form or other interface provided by the page. Additionally, access control and security measures would need to be implemented to ensure that only authorised users can access and modify the data.

Implementing a public log of administration actions

Create a table for admin logs

Create table bookbrainz.admin_logs(
id serial primary key,
User_id integer references editor(Metabrainz_user_id) 
Action varchar(100)
)

SQL Query:

  • INSERTs a new row into the bookbrainz.admin_logs table

  • Sets the user_id column to the userId value extracted from the request

  • Sets the action column to the action string constructed from the HTTP method and original URL of the request

Create a function for admin logs

const { AdminLog } = require('../models');

const logAction = (req, res, next) => {
  const userId = req.user.id;
  const action = req.method + ' ' + req.originalUrl;
  AdminLog.create({
    user_id: userId,
    action: action
  })
  .catch(error => {
    console.error('Error logging action:', error);
  });
  next();
};
;

module.exports = logAction;

Function:

  • It requires the AdminLog model, which represents the admin_log table in the database. This model is defined using Bookshelf.js, an ORM that makes it easier to work with databases in JavaScript.
  • It extracts the user ID from the req.user object, assuming that the user has been authenticated and their information is stored in this object.
  • It constructs an action string by concatenating the HTTP method (req.method) and original URL (req.originalUrl) of the request.
  • It creates a new instance of the AdminLog model with the user_id and action values set to the user ID and action string, respectively.
  • Then we create a new log entry using AdminLog.create(), passing in an object with the user_id and action values.

Contribution

I recently contributed to resolving an issue on the BookBrainz website, where the test environment wasn’t easily distinguishable from other environments. I implemented a banner feature that makes it clear to users when they are on the test site, and PR has been merged into the codebase.

Timeline

Week 1:

  • Analyse the requirements for the admin system and finalise the database schema.
  • Implement the new database schema, adding tables for roles and attaching users to roles.

Week 2:

  • Implement middleware for securing specific routes according to a user’s roles.

  • Add necessary API endpoints to update user privileges and roles.

Week 3:

  • Implement the admin panel webpage to allow admins to search for users, give users privileges, and take other actions.

  • Add a security layer to the admin panel to restrict access to authorised users.

Week 4:

  • Implement the page to allow privileged users to edit and add relationships and identifiers

  • Developing middlewares for securing routes.

Week 5:

  • Develop a feature that permits privileged users to trigger a reindex of the search server, which updates the search index and improves search performance.

Week 6:

  • Test the system, and fix any bugs and issues found during testing.

Week 7:

  • Improve the documentation of the admin system and the API endpoints.

  • Submit the code and documentation to the BookBrainz project repository for review.

Other Information

  • Tell us about the computer(s) you have available for working on your
    SoC project!

    I have an ASUS vivobook with an i3 processor and 8 gb of RAM.

  • When did you first start programming?

    I started programming when I was in class 11th.

  • What type of music do you listen to?

    I mostly listen to country music

  • If applying for a BookBrainz project: what type of books do you
    read?

    I love to read fiction novels. Everything by Rober
    Frost-Bookbrainz
    .

  • What aspects of the project you’re applying for (e.g., MusicBrainz,
    AcousticBrainz, etc.) interest you the most?

    I’ve been making some contributions to Bookbrainz. I like the
    community and the discussions focused on improving the project as
    a whole.

  • Have you ever used MusicBrainz to tag your files?

    No. I would like to give it a try.

  • Have you contributed to other Open Source projects?

    Yes, I have been contributing to Open Source projects since last
    year. I have also participated in hacktoberfest 2022.

  • How much time do you have available, and how would you plan to use
    it?

    I will be able to give 30 hours a week. I plan to utilise this
    time to achieve my goal to complete the project within the
    specified timeline, while also ensuring that the implementation is
    of high quality, well documented, and conforms to the coding
    standards and best practices of the BookBrainz project.

2 Likes

Thanks for submitting a proposal draft @Riya_Kumari :slight_smile:
I see some room for improvement; overall, more details on each step would help us both see more clearly the scope of the project and how we can organize our time.


About roles I’ll copy over my comments from another post:
The proposed system only suggests three roles (which could have been a type column instead, probably) but we are bound to end up with more complicated use-cases and combinations of roles.
Can I be an admin but not have some data modification rights?
Can I have some admin rights, but not the right to delete users?
Can I be a privileged user that can edit relationship types, but not have the right to reindex?
What if we want to prevent a user from entering edits?

In short, the system as proposed is not very extensible/future-proof.

I know MusicBrainz uses another type of system to define permissions with bit masking I believe. Have a look at the flags they can set: musicbrainz-server/constants.js at master · metabrainz/musicbrainz-server · GitHub
You can ask someone from the MusicBrainz team in the #metabrainz IRC channel who could have more information about how this is used and set.
Consequently, the database tables and the middleware would be different.
For one, we would only need a numeric column on the user table to define multiple privileges, since the numeric flags can be combined.


The Implementing an Admin panel webpage section only describes very briefly what the actual meat of the project is. Instead of telling me what would happen, I’d like for you to show me how you would make it happen instead.

Visual mockups could also be helpful here to show where you are heading.

Same comments for the Developing a page to allow privileged users to edit and add relationships and identifiers section


For the methods that modify the database (updateUserRole, deleteUser,…), it would have been useful to get familiarized with the ORM we use to interface with the database: https://github.com/metabrainz/bookbrainz-data-js

We most likely wouldn’t be executing SQL instructions like this in bookbrainz-site, rather these operations would live in the ORM.


Sets the action column to the action string constructed from the HTTP method and original URL of the request

This would make the admin log basically unreadable for anyone other than a programmer (and even then, not nicely readable) which basically negates the idea of a public log.


Regarding your timeline, I would expect you to have already figured out your items from weeks 1-2 before the beginning of the coding period.

Looking at the database changes you propose, I can’t imagine it would take the next two weeks of work. Maybe I’m being optimistic but I would imagine you could have it sorted in a couple of days.

All the meat of the project is squeezed into weeks 7 & 8 as a block without any details. You’re going to want to write down all the steps one by one, if only to give yourself an idea of what tasks are at hand and how long they could take.

Thank you @mr_monkey :slightly_smiling_face: for reviewing my proposal.
I have edited my proposal accordingly, kindly check .

Thanks for updating your proposal.

Regarding permissions, I think the SUPER_EDITOR type is not necessary and that functionality should instead be represented by the permission flags.
Unless I misunderstand…what could a SUPER_EDITOR do that would not be represented by permission flags?

The same could possibly be said about the ADMIN type, but justifiably that could be a useful distinction to have. For example as you describe, access to the admin panel would be restricted to that editor type, rather than be represented by permissions flags.

A detail, but the bookbrainz.editor table already has a column for type.

In your middleware however you mention req.user.role instead of req.user.type.

I don’t understand why we need a superEditor/edit endpoint if we already have the /admin/user/:id PUT endpoint to update the roles (“permissions”? “type”? The naming varies across the proposal, you should try to stick to one).

Some of your middleware functions in their current state would crash, as you don’t defined dbName. Looks like copy-pasted/generated code, not a great look.
As I mentioned in my previous response we would be doing these operations with our ORM rather than querying the database directly. That was your cue to go have a look at the ORM and modify the middleware accordingly :slight_smile:

The Developing a page to allow privileged users to edit and add relationships and identifiers section I think misunderstood the asignment.
The idea is not to restrict adding relationships or identifiers to super users (that would make this open database pretty unusable for normal editors), but rather to allow the to add relationship types, identifier types, etc. For example the relationship “Author X wrote work Y” is a relationship type. Wikipedia ID is an identifier type.
The wording of the ideas page was not ideal so I’ll take some part of the blame for the misunderstanding.

About the Implementing a public log of administration actions section, please re-read my previous response, you did not address my feedback other than adding “presented in more user readable form.” which does not add much. I do not think the concatenated string is the right way to go, and the suggestion that we would need to then parse that string to make it human-friendly supports that.

Thank you! @mr_monkey for suggesting the changes :slightly_smiling_face:
I have edited my proposal according to your suggestions. Please have a look. :smiley: