Personal information
Name: Prabal Singh
IRC Nick: prabal
MB username: prabalsingh24
Email: praba170107045@iitg.ac.in
Github: prabalsingh24
Time Zone. UTC+05:30
Proposal
Project overview
Bookbrainz lacks a feature which allows a person to make a collection of entities (edition, edition-group, work, author, publisher) for his/her future reference. This project is about introducing such feature - User Collections.
Music Library : Playlist :: Bookbrainz : User Collection
Features and UI prototype
Basic Features :
- A user can create a collection of a particular entity type. Author-Collection, Work-Collection and can store the entities of that type in that collection.
- A user can change the privacy of their collection thus controlling whether other people can see the collection.
- A user can makes changes to collections created by themselves or collections whose collaborator they are.
- User can add collaborators to his/her own collections - giving your friends access to edit and make changes in the collection.
- A collaborator can edit the collection - add more entities, change the description, privacy, name but is not allowed to delete the collection neither is he/she allowed to add more collaborators. That can only be done by the owner of the collection.
UI Prototype :
There will be another tab in the profile page - collections, which will show all the collections of a user in a table. Since an user can have multiple many collections, the collections be displayed using pagination.
Create New Collection
button will open the create new collection form which is described later.
Clicking on the row anywhere would redirect to that particular collection.
Inside a particular collection, you can see the entities in the tabular view with pagination. You have the option to Edit
the collection, Delete
the collection, and to Add
/Remove
the entities.
Each type of collection - author collection, work collection, edition collection - will have their releveant columns in the table
Work Collection
Edition Collection
Publisher Collection
Author Collection
- Clicking on any of the row will redirect to that entity-view page
- Clicking
add author
will open a pop up, in which one can search for the entity and add it to the collection
- Clicking âEditâ will open a page in which you can edit the information about the collection - Name, Type, Collaborator. This page will be same as
Create New Collection
page.
Adding entities to your collection
- In order to add entities in the collection
Add to Collection
button will be placed in both search results and entity page. - By making
more
buttons we make it generic and we can add other things like -merge
share
etc - Bulk edition of entities will also be possible using the checkboxes
- If the user is not logged in, - User will be redirected to login. ( Same happens with merge button )
- When the user is logged in , a list of userâs existing collections will be shown filtered by the entity type. Clicking them will add the entity to that particular collection.
- Also
New Collection
option will be there, clicking it will display a form for new colletion, and then clickingadd
will add that entity to that new collection
- Since the space is less in this
create collection
form, we can omit adding collaborators here.
Stats
in the profile page will be updated like this
There will be a page which will show all the Public
Collections. /collections
.
There will be a search collections bar in this page as well.
Database Changes
Three new tables will be added in the existing database like this.
-
user_collection
: this table will have all the information about a particular collection.editor_id
is the owner of the collection, the editor who created the collection. -
user_collection_item
: this table will store thecollection_id
andbbid
which describes the entites in the collection. -
user_collection_collaborator
: this table storescollection_id
andeditor_id
of their collaborator
CREATE TABLE bookbrainz.user_collection (
collection_id UUID PRIMARY KEY DEFAULT public.uuid_generate_v4(),
editor_id INT,
name VARCHAR(80) NOT NULL CHECK (name <> ''),
description TEXT NOT NULL CHECK (description <> ''),
type bookbrainz.entity_type NOT NULL
public BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT timezone('UTC'::TEXT, now()),
last_modified TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT timezone('UTC'::TEXT, now()),
CONSTRAINT user_collection_fk FOREIGN KEY (editor_id) REFERENCES bookbrainz.editor(id)
);
CREATE TABLE bookbrainz.user_collection_item (
collection_id UUID,
bbid UUID,
added_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT timezone('UTC'::TEXT, now()),
CONSTRAINT user_collection_item_fk FOREIGN KEY (collection_id) REFERENCES bookbrainz.user_collection(collection_id) ON DELETE CASCADE,
CONSTRAINT user_collection_item_fk1 FOREIGN KEY (bbid) REFERENCES bookbrainz.entity(bbid)
);
CREATE TABLE bookbrainz.user_collection_collaborator (
collection_id UUID ,
editor_id INT,
CONSTRAINT user_collection_collaborator_fk FOREIGN KEY (collection_id) REFERENCES bookbrainz.user_collection(collection_id) ON DELETE CASCADE,
CONSTRAINT user_collection_collaborator_fk1 FOREIGN KEY (editor_id) REFERENCES bookbrainz.editor(id)
);
ORM BookBrainz-Data Changes
Two new models will be added in BookBrainz-data
UserCollectionsItem, UserCollections , UserCollectionsCollaborator
const UserCollections = bookshelf.Model.extend({
items(){
return this.hasMany('UserCollectionItem','collection_id');
}
collaborators(){
return this.hasMany('UserCollectionCollaborator', 'collection_id');
}
owner(){
return this.belongsTo('Editor','editor_id');
}
tableName: 'bookbrainz.user_collection'
});
const UserCollectionItem = bookshelf.Model.extend({
entity() {
return this.belongsTo('Entity', 'bbid');
},
tableName: 'bookbrainz.user_collection_item'
});
const UserCollectionCollaborator = bookshelf.Model.extend({
collaborator() {
return this.belongsTo('Editor', 'editor_id');
},
collections(){
return this.belongsTo('UserCollection', 'collection_id');
}
tableName: 'bookbrainz.user_collection_collaborator'
});
Editor model will be modified little bit. These two relation will be added in it
collections() {
return this.hasMany('UserCollections', 'editor_id');
},
collaborators(){
return this.belongsToMany('UserCollectionCollaborator', 'editor_id');
}
Backend Server Changes
I will add a new router file in src/server/routes/collection
. This new router file will have following routes.
-
Router.get(âcollection/:collectionIdâ)
andRouter.get('collection/:collectionId/paginate')
: -
Router.get(âcollection/:collectionId/deleteâ)
-
Router.post(âcollection/:collectionId/delete/handlerâ)
-
Router.get(âcollection/createâ)
-
Router.post(âcollection/create/handlerâ)
-
Router.get(âcollection/:collectionId/editâ)
-
Router.post(âcollection/:collectionId/edit/handlerâ)
-
Router.post('collection/:collectionId/add')
-
Router.post('collection/:collectionId/remove')
-
Router.post('collection/:collectionId/collaborator/add)
-
Router.post('collection/:collectionId/collaborator/remove)
-
Router.get('collections')
andRouter.get('collections/collections')
-
Router.get('collections/search')
and `Router.get(âcollections/search/searchâ)
Apart from this, /editor/:id/collections
and editor/:id/collections/collections
will be added in the editor
route
Router.get(âcollection/:collectionId)
:
- This route will display a particular collection.
- First of all, user will be authenticated to check if user can see the collection ( user is either collaborator or owner or collection is public). For this a function-
isAuthenticatedForCollection(req, res, collectionId)
will be created. - The results will be in a table and in a paginated view â PagerElement -will be reused here.
Collections of different types will be having different columns in the table. - I plan to make only one table -CollectionView (different that
CollectionsTable
) - and reuse it for all the collection-types. I will achieve this by using boolean field -ShowLanguage
,ShowDateofBirth
, etc. It will be similar to the revisions table I made earlier where I achieved similar results.
In the server side, data will be fetched like this
const entitiesArray = await new UserCollections()
.where('collection_id', collectionId)
.fetchPage({
limit: size,
offset: from
withRelated: ['items', "items.entity"]
});
The entitesArray will be having bbid
and the entity
.
In case the entity is deleted, itâs name will be fetched like this.
if (!entity.dataId) {
entity.deleted = true;
const parentAlias = await orm.func.entity.getEntityParentAlias(
orm, modelName, bbid);
entity.parentAlias = parentAlias;
}
As for the frontend part, I will make a react component - CollectionsPage
which will be in src/client/components/pages/collections
The UI would be similar to described in the earlier section
router.get(â/editor/:id/collectionsâ)
and `router.get('editor/:id/collections/collections):
- This route will display all the collections of a user (both owner and collaborator)
- The first route will be send a markup whereas second route will be used for updating the data (pagination)
-
getCollectionsForEditor()
function will be made which will be used in both these routes. - Inside this function, collections can be fetched like this
const collectionIds = await new UserCollectionCollaborator()
.where('editor_id', editorId)
.fetchAll({});
collectionIds
array have collection_id
of collections of which the editor is a collaborator
const collections = await new UserCollections()
.query((qb) => {
qb.where('editor_id', editorId)
.orWhere('collection_id', 'in', collectionIds);;
})
.orderBy('created_at', 'DESC');
.fetchPage({
limit: size,
offset: from,
withRelated: ['Items', 'Collaborators']
});
- Since the number of collections of an user can be high, this table will be displayed with pagination. The
PagerElement
will be reused for this. - For the front-end part, I will make a corresponding page in
src/client/components/pagers/editor-collection
and also add corresponding file in controller to make JS file. - I will create a react component -
CollectionsTable
- to display the collections. I will be reusing this table in 'See All Collections` page also. - This
CollectionsPage
will be very much similar to RevisionsPage which I made in this PR
Router.get(âcollection/:collectionId/deleteâ)
:
- A
DeletionForm
will be displayed in this route. - I plan create
CollectionDeletionForm
which will be stored insrc/client/components/forms/collectionDeletion
- Depending on whether the user is owner or a collaborator, the details ( text of the form) will change.
- Ths form will be confirmation of the delete and hitting delete will make post call to 'collection/:collectionId/deleteâ
Router.post('collection/:collectionId/delete/handler)
:
- If user is owner of the collection, collection will be deleted,
await new UserCollections({collectionId: collectionId}).destroy();
- Otherwise if the user is collaborator, the particular row in the
user_collection_collaborator
will be deleted - user will no longer be the collaborator of the collection .
await new UserCollectionCollaborator({
collectionId: CollectionID,
editorId: editorID
}).destroy();
Router.get(âcollection/createâ)
:
- User must be logged in for this. Already defined
isAuthenticated
function fromauth.js
will be used here to check this - The
Name
,Description
,Type
andPrivacy
will be filled/edited in a form. This newCollectionForm
will be created insrc/client/components/forms/
- The âSaveâ button will make a post request to
collection/create/handler
Router.get('collection/:collectionId/edit)
- Just like in the above routes,
isAuthenticated
will be used to check if the user is logged in or not. - Because user needs to be either a collaborator or the owner of the collection, another function will be made
isAuthenticatedForCollectionEditing(req, res, collectionId)
which will be checking this - The âSaveâ button will make a post request to
collection/collectionId/edit/handler
Router.post(âcollection/create/handlerâ)
and Router.post(âcollection/:collectionId/edit/handlerâ)
:
- Both of these routes would be similar to each other, that they would use the same function
createOrEditHandler
in it. . - The params recieved from the post call will be used to make changes/create new collection
const collection = await UserCollections.forge({collectionId: collectionID}).fetch({require: true});
collection.set('name', name)
.set('description', description)
.set('public', privacy)
.save()
Router.post('collection/:collectionId/collaborator/add)
and Router.post('collection/:collectionId/collaborator/remove)
:
- User will be authenticated first to make sure if theyâre the owner of the collection.
-
EditorId
will be received asparams
and the change ( add/remove ) will be done. ( adding or removing a row inuser_collection_collaborator
table using the bookshelf model
Add to Collection
button and Router.post('collection/:collectionId/add')
:
- For this a
AddToCollection
react component will be made which will be added in entity view and search result page (As shown in the UI ). - It will display all the collections of users( with entityType=entity).
These collections can be fetched like this
const collectionIds = UserCollectionCollaborator()
.where('editor_id', editorId)
.fetchAll({});
The collectionIds
array will have all the collection_id
of collections of which the editor is a collaborator.
const collections = await new UserCollection()
.query((qb) => {
qb.where('editor_id', editorId).orWhere('collection_id', 'in', collectionIds);
qb.where('type', entityType);
})
.fetch({
withRelated: ['items', 'collaborators']
});
- Clicking Add button will then send a post request to
'collection/add'
withbbid
of the entity andcollectionId
of the collection, and it will return a message - success or failure ( in case of duplicate entities in collection, user not authorized (isAuthenticatedForEditing
) , etc) - Since user can also create new collection then and there also, clicking
New Collection
will display aCollection Form
to create a collection. - If user creates a new collection,
Collection Name
andCollection Description
will also be sent along withbbid
in the post request call.
Remove selected entities
button and Router.post('collection/:collectionId/remove')
:
-
Clicking
Remove selected entities
in the collection-view page will make a post request tocollection/:collectionId/remove
. -
The
bbid
of the entities to be removed will be sent asparams
. -
In the
Router.post('collection/:collectionId/remove)
, the user will be authenticated to check if he/she is allowed to make the change (isAuthenticatedForCollectionEditing
) and then entities will be removed from the collection.
Router.get('collections'')
and `Router.get(âcollections/collectionsâ) :
- In this route, all the public collections will be displayed. This is gonna be very similar to the revisions page I created. Obviously pagination will be required hence Pager-Element will be reused.
- The âcollectionsâ route will be sending a markup whereas âcollections/collectionsâ route will be for updating the data in the table ( for pagination).
- Both
Router.get('collections')
andRouter.get('collections/collections')
will be using a functiongetOrderedCollections()
to get the data. - Inside the
getOrderedCollections()
function, data will fetched something like this.
const collections = await new UserCollections()
.where('public', true)
.orderBy('last_modified', 'DESC')
.fetchPage({
limit: size,
offset: from,
withRelated: [
'items', 'collaborators'
]
});
- For the client side, a React Component
CollectionsPage
will be also be made. -
Pager Element
andCollectionsTable
will be reused here.
Router.get('collections/search'')
and `Router.get(âcollections/search/searchâ) :
- This route will be used to display the
search
results. - The
CollectionTable
created in the above route (collections
) will be used and Pager element will be used. - As for the search feature, I would be working along with the mentor ( Mr Monkey) to achieve this.
- I expect this to be quite similar to existing search features in the website.
TIMELINE
Here is a more detailed week-by-week timeline of the 13 weeks GSoC coding period to keep me on track
Each of the following sub tasks/week-tasks will begin by first writing tests using chai and mocha and then these routes/functions will be written to pass those tests.
The complete documentation will be required in this project and will be done at the time of coding itself, we will use JSDoc to document the code
Each of the week would be write tests->write production code â do documentation
Pre-Community Bonding Period ( April ) :
I will solve url history bug that was discussed in irc a while ago.
Because when you are in page 5 of collection-view page and you make changes in the entities ( add or remove), you would ideally want to be in page 5 after the change as well. Right now the url history is not updated when you use the Pager Element
Community Bonding Period :
I will spend some time learning about test-driven development using frameworks like chai and mocha. I will get familiar with debugging tools and learn how to use the react devtools browser extension.
Week 1 :
I will begin with setting up database (Creating new table) and writing corresponding Models in bookbrainz-data.
Week 2 :
This week will be about finalizing UI mockups, making sure we donât take any bad decision at the early stage
Week 3-4
Router.get(âcollection/createâ)
Router.post(âcollection/create/handlerâ)
Phase 1 Evaluation here
Week 5-7 :
Router.get(âcollection/:collectionId/editâ)
Router.post(âcollection/:collectionId/edit/handlerâ)
Router.post('collection/:collectionId/collaborator/add')
Router.post('collectoin/:collectionId/collaborator/remove')
Week 8 :
Router.get(âcollection/:collectionId/deleteâ)
Router.post(âcollection/:collectionId/delete/handlerâ)
Phase 2 Evaluation here
Week 9 :
-
Router.get(â/editor/:id/collectionsâ)
. -
Router.get(âcollection/:collectionId)
:
I feel Week 9 will be similar to ârouter.get(â/editor/:id/revisions) which Iâve done in this PR
Week 10 :
-
Add to Collection
feature insearch
page andentity
page Router.post('collection/:collectionID/add')
-
Remove Selected Entities
feature in the collection-view page. -
Router.post('collection/:collectionID/remove
)
Week 11:
- Collection Search Feature
- Router.get(âcollections/searchâ)
Week 12
-
Router('collections')
which displays all the public collections. - Adding Collection in user stats
Week 13:
- Buffer Period
Stretch Goal
The addition of collection
endpoints in the existing API.
About Me
I am 3rd year student in IIT Guwahati. I got to know about Metabrainz from one of my seniorâs blog - Sambhav Kothariâs Blog. I got interested in BookBrainz because of my interest in reading story books and ofcource familiarity with the technologies used in the project. I have been contributing in BookBrainz project since January. Here is list of my PRs Iâve made so far.
Tell us about the computer(s) you have available for working on your SoC project!
I have an asus laptop with i7 processor with 8gb of ram.
When did you first start programming?
I wrote Hello World
in my computer science class in high school.
What type of books do you read?
I enjoy reading detective novels - Agatha Christie novels are my favourite. Iâve also read Harry Potter, LOTR, The Hunger Games
Have you contributed to other Open Source projects? If so, which projects and can we see some of your code?
Iâve contributed to Bookbrainz only. Iâve made 35 PRs since January.
RevisionsPage, Editor-Revisions, Pager-Element, etc
How much time do you have available, and how would you plan to use it?
I am free most of the summer. I plan to give 35-40 hours a week in the project.
My classes will probably start in last 10 days. Work load will be less and I will be able to give 4-5 hours a day easily.