Back-end for Full-stack software development project course.
The back-end has been coded with TypeScript. A Dockerfile is also provided if you wish to deploy the app easily. Knex is used to handle DB migrations and seeding.
- Introduction
- Installation instructions
- Running tests & calculating code coverage
- API routes
- Permissions
- License
Many student unions across Finland use so called "clubhouses" where they can organize events and have fun with other students.
It is not always clear what events are kept there, who has the permission to use such places and how to keep a good track of who is in response of other people, and when has such a person been there.
This project is meant to solve this problem by providing:
- List of student unions
- List of users
- List of students that have access to clubhouses (night / day keys etc..)
- An event calendar to look for events (Available also as iCal feed)
- Rules of the clubhouse easily available
- Cleaning schedules of the clubhouse*
- A "newsboard" system for posting announcements
- Management interface for easy responsibility taking of other people
- Comprehensive admin interface for administrators to be constantly up to date of whats happening.
- Very flexible permissions system. You can add roles and customize their permissions as you wish.
- Spotify API support
* not yet implemented
You need to create databases DB_NAME_test
, DB_NAME_dev
and DB_NAME
, where DB_NAME
is the database you want to use with your setup. This can be done manually, or by executing yarn create-databases
when .env
file is configured.
- Clone the repo
- Install yarn if not yet installed
- Run
yarn
to install dependencies - Create
.env
file and define environment variables. See.env.example
file. yarn create-databases
to create necessary databases (optionally, you can create the databases manually.)yarn migrate
to run migrationsyarn seed
to seed the databaseyarn start
to start the server oryarn watch
to watch for code changes
- Clone the repo
- Run
docker-compose up -d --build
- Run
yarn migrate-docker
- Run
yarn seed-docker
to seed your database with example users
By default, two users are created. Login with testuser@email.com::testuser
or testuser2@email.com::testuser2
.
To run tests, run yarn test
. To run tests in a container environment, run yarn test-docker
.
Returns: JWT if the authentication succeeds
Request content-type: application/json
Request body:
Required: email
and password
{
"email": "user1@email.com",
"password": "password1"
}
Response status code: HTTP 200 (success on authentication), HTTP 4xx (validation error or user already exists), HTTP 500 (server error)
Response content-type: application/json
Response body: JWT
{
"success": true,
"message": "Authentication successful",
"payload": {
"token": "TOKEN"
}
}
Returns: A list of users registered in the service.
Response status code: HTTP 200 (success), HTTP 500 (server error)
Response content-type: application/json
Response body:
{
"success": true,
"message": "Succesfully fetched users",
"payload": [{
"userId": 1,
"email": "testuser@email.com",
"firstName": "Test",
"lastName": "User",
"permissions": 524287,
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}, {
"userId": 2,
"email": "testuser2@email.com",
"firstName": "Test2",
"lastName": "User2",
"permissions": 8,
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}]
}
Returns: Registered user if the request completes successfully
Request parameters: userId
(URL parameter, integer)
Response status code: HTTP 200 (success), HTTP 404 (not found), HTTP 500 (server error)
Response content-type: application/json
Response body: GET /api/v1/user/1
{
"success": true,
"message": "",
"payload": {
"userId": 1,
"email": "testuser@email.com",
"firstName": "Test",
"lastName": "User",
"permissions": 524287,
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}
}
Returns: Created user if the request succeeds
Request content-type: application/json
Request body:
Required: email
, password
, firstName
and lastName
{
"email": "user1@email.com",
"password": "password1",
"firstName": "firstname",
"lastName": "lastname"
}
Response status code: HTTP 201 (success on user creation), HTTP 4xx (validation error or user already exists), HTTP 500 (server error)
Response content-type: application/json
Response body: Created user
{
"success": true,
"message": "",
"payload": {
"userId": 5,
"email": "user1@email.com",
"firstName": "firstname",
"lastName": "lastname",
"permissions": 8,
"created_at": "2018-12-29 09:17:52",
"updated_at": "2018-12-29 09:17:52"
}
}
Response body:
{
"success": true,
"message": "User deleted from the server (including his/her created calendar events, messages, sessions and newsposts.)"
}
Todo
Returns: A list of student unions in the service.
Response status code: HTTP 200 (success), HTTP 500 (server error)
Response content-type: application/json
Response body:
{
"success": true,
"message": "Succesfully fetched student unions",
"payload": [{
"unionId": 1,
"name": "Union 1",
"description": "Union 1 description",
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}, {
"unionId": 2,
"name": "Union 2",
"description": "Union 2 description",
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}, {
"unionId": 3,
"name": "Union 3",
"description": "Union 3 description",
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}]
}
Returns: A single student unions in the service by its id.
Request parameters: unionId
(URL parameter, integer)
Response status code: HTTP 200 (success), HTTP 404 (not found), HTTP 500 (server error)
Response content-type: application/json
Response body: GET /api/v1/studentunion/1
{
"success": true,
"message": "",
"payload": {
"unionId": 1,
"name": "Union 1",
"description": "Union 1 description",
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}
}
Returns: Created student union if the request succeeds
Request content-type: application/json
Request body:
Required: name
and description
{
"name": "HelloWorld",
"description": "Description for student union"
}
Response status code: HTTP 201 (success on student union creation), HTTP 4xx (validation error or user already exists), HTTP 500 (server error)
Response content-type: application/json
Response body: Created student union
{
"success": true,
"message": "",
"payload": {
"name": "HelloWorld",
"description": "Description for student union",
"unionId": 4,
"created_at": "2018-12-29T09:22:28.833Z",
"updated_at": "2018-12-29T09:22:28.833Z"
}
}
Todo
Todo
Returns: All calendar events in the service, past and present.
Response status code: HTTP 200 (success), HTTP 500 (server error)
Response content-type: application/json
Response body:
{
"success": true,
"message": "Succesfully fetched calendar events",
"payload": [{
"eventId": 1,
"name": "Friday hangouts",
"description": "Friday hangouts at our clubhouse",
"restricted": 0,
"startTime": "2018-12-29 09:14:08",
"endTime": "2018-12-29 12:14:08",
"addedBy": 1,
"unionId": 1,
"locationId": 2,
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}, {
"eventId": 2,
"name": "Board meeting",
"description": "Board meeting",
"restricted": 0,
"startTime": "2018-12-29 09:14:08",
"endTime": "2018-12-29 12:14:08",
"addedBy": 1,
"unionId": 1,
"locationId": 1,
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}]
}
Returns: A single calendar event in the service by its id.
Request parameters: eventId
(URL parameter, integer)
Response status code: HTTP 200 (success), HTTP 404 (not found), HTTP 500 (server error)
Response content-type: application/json
Response body: GET /api/v1/calendar/1
{
"success": true,
"message": "Succesfully fetched single calendar event",
"payload": {
"eventId": 1,
"name": "Friday hangouts",
"description": "Friday hangouts at our clubhouse",
"restricted": 0,
"startTime": "2018-12-29 09:14:08",
"endTime": "2018-12-29 12:14:08",
"addedBy": 1,
"unionId": 1,
"locationId": 2,
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}
}
Returns: Created calendar event if the request succeeds
Request content-type: application/json
Required permissions: ADD_EVENT
Request headers: Authorization: Bearer [TOKEN]
Request body:
Required: name
, description
, restricted
, startTime
, endTime
, unionId
and locationId
{
"name": "Test event",
"description": "Test event",
"restricted": 0,
"startTime": "2018-08-12 12:00",
"endTime": "2018-08-12 14:00",
"unionId": 1,
"locationId": 2
}
Response status code: HTTP 201 (success on calendar event creation), HTTP 4xx (validation error), HTTP 500 (server error)
Response content-type: application/json
Response body: Created calendar event
{
"success": true,
"message": "Succesfully saved calendar event",
"payload": {
"eventId": 3,
"name": "Test event",
"description": "Test event",
"restricted": 0,
"startTime": "2018-08-12 12:00:00",
"endTime": "2018-08-12 14:00:00",
"addedBy": 1,
"unionId": 1,
"locationId": 2,
"created_at": "2018-12-29 09:24:46",
"updated_at": "2018-12-29 09:24:46"
}
}
Returns: An iCal file of the event in the service by its id. Triggers a file download in the browser.
Request parameters: eventId
(URL parameter, integer)
Response status code: HTTP 200 (success), HTTP 404 (not found), HTTP 500 (server error)
Response content-type: text/calendar
Response body: GET /api/v1/calendar/1/ical
BEGIN:VCALENDAR
VERSION:2.0
PRODID:clubhouse
METHOD:PUBLISH
BEGIN:VEVENT
CATEGORIES:MEETING
STATUS:TENTATIVE
DTSTAMP:20181229T092543
DTSTART:20181229T091408
UID:20181229T092543@clubhouse.com_1
DTSTART:20181229T091408
DTEND:20181229T121408
SUMMARY: Friday hangouts
DESCRIPTION: Friday hangouts at our clubhouse
LOCATION: Street Addr 1
CLASS:PRIVATE
END:VEVENT
END:VCALENDAR
Todo
Todo
Returns: All locations in the service.
Response status code: HTTP 200 (success), HTTP 500 (server error)
Response content-type: application/json
Response body:
[{
"locationId": 1,
"name": "Meeting room",
"address": "Street Addr 1",
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}, {
"locationId": 2,
"name": "Club",
"address": "Street Addr 1",
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}]
Returns: A single location in the service by its id.
Request parameters: locationId
(URL parameter, integer)
Response status code: HTTP 200 (success), HTTP 404 (not found), HTTP 500 (server error)
Response content-type: application/json
Response body: GET /api/v1/location/1
{
"success": true,
"message": "",
"payload": {
"locationId": 1,
"name": "Meeting room",
"address": "Street Addr 1",
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}
}
Returns: Created location if the request succeeds
Request content-type: application/json
Required permissions: ADD_LOCATION
Request headers: Authorization: Bearer [TOKEN]
Request body:
Required: name
and address
{
"name": "Meeting room",
"address": "Street Addr 1"
}
Response status code: HTTP 201 (success on location creation), HTTP 4xx (validation error), HTTP 500 (server error)
Response content-type: application/json
Response body: Created location
{
"success": true,
"message": "",
"payload": {
"locationId": 1,
"name": "Meeting room",
"address": "Street Addr 1",
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}
}
Todo
Todo
Returns: All permissions in the service.
Response status code: HTTP 200 (success), HTTP 500 (server error)
Response content-type: application/json
Response body:
{
"success": true,
"message": "",
"payload": {
"ALLOW_REMOVE_USER": 1,
"ALLOW_VIEW_USERS": 2,
"ALLOW_ADD_REMOVE_KEYS": 4,
"ALLOW_VIEW_KEYS": 8,
"ALLOW_ADD_EDIT_REMOVE_STUDENT_UNIONS": 16,
"ALLOW_VIEW_STUDENT_UNIONS": 32,
"ALLOW_ADD_EDIT_REMOVE_EVENTS": 64,
"ALLOW_VIEW_EVENTS": 128,
"ALLOW_ADD_EDIT_REMOVE_RULES": 256,
"ALLOW_VIEW_RULES": 512,
"ALLOW_ADD_EDIT_REMOVE_POSTS": 1024,
"ALLOW_VIEW_POSTS": 2048,
"ALLOW_ADD_EDIT_REMOVE_LOCATIONS": 4096,
"ALLOW_VIEW_LOCATIONS": 8192,
"ALLOW_ADD_EDIT_REMOVE_MESSAGES": 16384,
"ALLOW_VIEW_MESSAGES": 32768,
"ALLOW_ADD_EDIT_REMOVE_PERMISSIONS": 65536,
"ALLOW_VIEW_PERMISSIONS": 131072,
"ACCESS_SPOTIFY_API": 262144
}
}
Returns: All ongoing sessions in the service.
Request headers: Authorization: Bearer [TOKEN]
Response status code: HTTP 200 (success), HTTP 500 (server error)
Response content-type: application/json
Response body:
{
"success": true,
"message": "",
"payload": [{
"sessionId": 2,
"userId": 1,
"startMessage": "Good evening, I'm taking responsibility of a few exchange students.",
"endMessage": "",
"startTime": "2018-07-01 23:58:00",
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}]
}
Returns: All sessions (old and ongoing) by a single user, by its id.
Request headers: Authorization: Bearer [TOKEN]
Request parameters: userId
(URL parameter, integer)
Response status code: HTTP 200 (success), HTTP 404 (not found), HTTP 500 (server error)
Response content-type: application/json
Response body: GET /api/v1/session/user/1
{
"success": true,
"message": "",
"payload": [{
"sessionId": 1,
"userId": 1,
"startMessage": "Let's get this party started.",
"endMessage": "I have left the building. Moved people under my supervision to another keyholder.",
"startTime": "2017-07-01 00:00:00",
"endTime": "2017-07-01 00:10:00",
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}, {
"sessionId": 2,
"userId": 1,
"startMessage": "Good evening, I'm taking responsibility of a few exchange students.",
"endMessage": "",
"startTime": "2018-07-01 23:58:00",
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}]
}
Returns: All ongoing watches by a single user, by its id.
Request headers: Authorization: Bearer [TOKEN]
Request parameters: userId
(URL parameter, integer)
Response status code: HTTP 200 (success), HTTP 404 (not found), HTTP 500 (server error)
Response content-type: application/json
Response body: GET /api/v1/session/ongoing/user/1
{
"success": true,
"message": "",
"payload": [{
"sessionId": 2,
"userId": 1,
"startMessage": "Good evening, I'm taking responsibility of a few exchange students.",
"endMessage": "",
"startTime": "2018-07-01 23:58:00",
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08"
}]
}
Returns: Starts a new session.
Request content-type: application/json
Request headers: Authorization: Bearer [TOKEN]
Request body:
Required: startMessage
{
"startMessage": "Start message of the session."
}
Response status code: HTTP 201 (success on starting a new session), HTTP 500 (server error)
Response content-type: application/json
Response body: Status message
{
"success": true,
"message": "Session started"
}
Returns: Ends an ongoing session.
Request content-type: application/json
Request headers: Authorization: Bearer [TOKEN]
Request body:
Required: endMessage
{
"endMessage": "End message of the session."
}
Response status code: HTTP 200 (success on ending a session), HTTP 500 (server error)
Response content-type: application/json
Response body: Status message
{
"success": true,
"message": "Session ended with message 'End message of the session.'"
}
Returns: All messages in the service.
Response status code: HTTP 200 (success), HTTP 500 (server error)
Response content-type: application/json
Response body:
{
"success": true,
"message": "",
"payload": [{
"messageId": 1,
"created_at": "2018-12-29 09:30:43",
"updated_at": "2018-12-29 09:30:43",
"userId": 1,
"title": "(No title)",
"message": "Hello world!"
}]
}
Returns: A single message in the service by its id.
Request parameters: messageId
(URL parameter, integer)
Response status code: HTTP 200 (success), HTTP 500 (server error)
Response content-type: application/json
Response body: GET /api/v1/message/1
{
"success": true,
"message": "",
"payload": {
"messageId": 1,
"created_at": "2018-12-29 09:30:43",
"updated_at": "2018-12-29 09:30:43",
"userId": 1,
"title": "(No title)",
"message": "Hello world!"
}
}
Returns: Inserted message in the service
Request content-type: application/json
Request headers: Authorization: Bearer [TOKEN]
Request body:
Required: message
{
"message": "Hello world!"
}
Response status code: HTTP 201 (success on message creation), HTTP 400 (valiadtion error), HTTP 500 (server error)
Response content-type: application/json
Response body:
{
"success": true,
"message": "",
"payload": {
"created_at": "2018-12-29T09:30:42.572Z",
"updated_at": "2018-12-29T09:30:42.572Z",
"message": "Hello world!",
"title": "(No title)",
"userId": 1,
"messageId": 1
}
}
Todo
Todo
Returns: All newsposts in the service.
Response status code: HTTP 200 (success), HTTP 500 (server error)
Response content-type: application/json
Response body:
{
"success": true,
"message": "",
"payload": [{
"postId": 1,
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08",
"author": 1,
"title": "Welcome to our site",
"message": "Welcome to the new clubhouse management website."
}]
}
Returns: A single newspost in the service by its id.
Request parameters: postId
(URL parameter, integer)
Response status code: HTTP 200 (success), HTTP 500 (server error)
Response content-type: application/json
Response body: GET /api/v1/newspost/1
{
"success": true,
"message": "",
"payload": {
"postId": 1,
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08",
"author": 1,
"title": "Welcome to our site",
"message": "Welcome to the new clubhouse management website."
}
}
Returns: All newsposts by a single user in the service.
Request parameters: userId
(URL parameter, integer)
Response status code: HTTP 200 (success), HTTP 500 (server error)
Response content-type: application/json
Response body: GET /api/v1/newspost/user/1
{
"success": true,
"message": "",
"payload": [{
"postId": 1,
"created_at": "2018-12-29 09:14:08",
"updated_at": "2018-12-29 09:14:08",
"author": 1,
"title": "Welcome to our site",
"message": "Welcome to the new clubhouse management website."
}]
}
Returns: Inserted newspost in the service
Request content-type: application/json
Request headers: Authorization: Bearer [TOKEN]
Request body:
Required: title
and message
{
"title": "Hello world",
"message": "First post"
}
Response status code: HTTP 201 (success on message creation), HTTP 400 (valiadtion error), HTTP 500 (server error)
Response content-type: application/json
Response body:
{
"success": true,
"message": "",
"payload": {
"created_at": "2018-12-29T09:33:38.474Z",
"updated_at": "2018-12-29T09:33:38.474Z",
"message": "First post",
"title": "Hello world",
"author": 1,
"postId": 2
}
}
Returns: Status if the deletion succeeded or not.
Request parameters: postId
(URL parameter, integer)
Response status code: HTTP 200 (success), HTTP 400 (deletion error), HTTP 500 (server error)
Response content-type: application/json
Response body: DELETE /api/v1/newspost/1
{
"success": true,
"message": "Newspost deleted"
}
Todo
Returns: Statistics of the clubhouse management system.
Response status code: HTTP 200 (success), HTTP 500 (server error)
Response content-type: application/json
Response body:
{
"userCount": 3,
"eventCount": 2,
"newspostCount": 1,
"hoursOnWatch": 0.16666666666666666,
"messageCount": 2
}
Returns: Statistics of a single user in the clubhouse management system.
Request parameters: userId
(URL parameter, integer)
Response status code: HTTP 200 (success), HTTP 500 (server error)
Response content-type: application/json
Response body: GET /api/v1/statistics/1
{
"eventCount": 2,
"newspostCount": 1,
"hoursOnWatch": 0.16666666666666666,
"watchCount": 2,
"messageCount": 2
}
The back end has an advanced permission system, that allows for a fine tuned management of user permissions. Below is a table that has listed all permission bits that are in use.
Name | Description | Value |
---|---|---|
BAN_USER | Ban a user | 0x00000001 |
EDIT_USER_ROLE | Edit user roles | 0x00000002 |
MAKE_USER_ADMIN | Make user an admin | 0x00000004 |
ALLOW_USER_LOGIN | Allow user to login (default permission) | 0x00000008 |
ADD_KEY_TO_USER | Add a key to user | 0x00000010 |
REMOVE_KEY_FROM_USER | Remove key from user | 0x00000020 |
CHANGE_KEY_TYPE_OF_USER | Change the key type of an user | 0x00000040 |
ALLOW_VIEW_KEYS | Allow user to view keys | 0x00000080 |
ADD_USER_TO_UNION | Add user to a student union. | 0x00000100 |
REMOVE_USER_FROM_UNION | Remove user from a student union | 0x00000200 |
ADD_STUDENT_UNION | Add a student union | 0x00000400 |
REMOVE_STUDENT_UNION | Remove a student union | 0x00000800 |
EDIT_STUDENT_UNION | Edit a student union | 0x00001000 |
ALLOW_VIEW_STUDENT_UNIONS | Allow user to view student unions | 0x00002000 |
ADD_EVENT | Add an event | 0x00004000 |
EDIT_EVENT | Edit an event | 0x00008000 |
REMOVE_EVENT | Remove an event | 0x00010000 |
ALLOW_VIEW_EVENTS | Allow user to view events | 0x00020000 |
EDIT_RULES | Allow user to edit rules | 0x00040000 |
ALLOW_VIEW_RULES | Allow user to view rules | 0x00080000 |
ADD_POSTS | Allow user to add posts | 0x00100000 |
EDIT_AND_REMOVE_OWN_POSTS | Allow user to edit and remove his/her own posts | 0x00200000 |
REMOVE_POSTS | Allow user to remove posts | 0x00400000 |
ALLOW_VIEW_POSTS | Allow user to view posts | 0x00800000 |
EDIT_OTHERS_POSTS | Allow user to edit others' posts | 0x01000000 |
SEND_MAILS | Allow user to send mails | 0x02000000 |
ADD_LOCATION | Allow user to add a location | 0x04000000 |
EDIT_LOCATION | Allow user to edit a location | 0x08000000 |
Permissions can be combined with bitwise operations. For example, if I want a user to be able to login and view rules, a bitwise operation is made: 0x00000008 | 0x00080000
what is equal to 0x00080008
.
If you want to check if a user has a certain permission, do it like this:
0x00080008 & 0x00000008
= 0x00000008
If the result is equal to the permission that has been checked, the user is allowed to do the operation.
This project is licensed with MIT license.