Skip to content

Latest commit

 

History

History
387 lines (321 loc) · 30.6 KB

File metadata and controls

387 lines (321 loc) · 30.6 KB

EZWallet Full API Specifications

This document lists all the expected behaviors for the APIs that compose the EZWallet application. The examples regarding request parameters and request body content must be followed: all attributes must be named in the same ways as the examples. The examples regarding response data content define the necessary attributes that must be present in the objects returned by the various APIs, other additional attributes can be present but are not required. In cases where the APIs return an error the correct structure to follow is:

res.status(errorCode).json({ error: "Error message" });

The actual value of the error, message, and refreshedTokenMessage attributes, where required, does not matter as long as the attributes are included in the return object (any string is accepted). Route parameters (where needed) cannot be empty, as not having them would define a new route, leading to a 404 error in Postman.

The functions that require Simple, User, and Admin authentication must have the necessary checks performed before any other check, the functions that require Group authentication must first check if the requested group, then check for authentication, and then perform any other additional check.

The registerAdmin function does not require any check on whether the user calling it is an authenticated Admin: if such checks were needed, an Admin would have to be created before calling the function, but the only way to create an Admin would be with the function itself, leading to a deadlock. The requirement on Admins being allowed to call the function is a logical one.

API List

auth.js

register

  • Request Parameters: None
  • Request Body Content: An object having attributes username, email and password
    • Example: {username: "Mario", email: "mario.red@email.com", password: "securePass"}
  • Response data Content: A message confirming successful insertion
    • Example: res.status(200).json({data: {message: "User added successfully"}})
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if at least one of the parameters in the request body is an empty string
  • Returns a 400 error if the email in the request body is not in a valid email format
  • Returns a 400 error if the username in the request body identifies an already existing user
  • Returns a 400 error if the email in the request body identifies an already existing user

registerAdmin

  • Request Parameters: None
  • Request Body Content: An object having attributes username, email and password
    • Example: {username: "admin", email: "admin@email.com", password: "securePass"}
  • Response data Content: A message confirming successful insertion
    • Example: res.status(200).json({data: {message: "User added successfully"}})
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if at least one of the parameters in the request body is an empty string
  • Returns a 400 error if the email in the request body is not in a valid email format
  • Returns a 400 error if the username in the request body identifies an already existing user
  • Returns a 400 error if the email in the request body identifies an already existing user

login

  • Request Parameters: None
  • Request Body Content: An object having attributes email and password
    • Example: {email: "mario.red@email.com", password: "securePass"}
  • Response data Content: An object with the created accessToken and refreshToken
    • Example: res.status(200).json({data: {accessToken: accessToken, refreshToken: refreshToken}})
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if at least one of the parameters in the request body is an empty string
  • Returns a 400 error if the email in the request body is not in a valid email format
  • Returns a 400 error if the email in the request body does not identify a user in the database
  • Returns a 400 error if the supplied password does not match with the one in the database

logout

  • Request Parameters: None
  • Request Body Content: None
  • Response data Content: A message confirming successful logout
    • Example: res.status(200).json({data: {message: "User logged out"}})
  • Returns a 400 error if the request does not have a refresh token in the cookies
  • Returns a 400 error if the refresh token in the request's cookies does not represent a user in the database

controller.js

createCategory

  • Request Parameters: None
  • Request Body Content: An object having attributes type and color
    • Example: {type: "food", color: "red"}
  • Response data Content: An object having attributes type and color
    • Example: res.status(200).json({data: {type: "food", color: "red"}, refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if at least one of the parameters in the request body is an empty string
  • Returns a 400 error if the type of category passed in the request body represents an already existing category in the database
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin)

updateCategory

  • Request Parameters: A string equal to the type of the category that must be edited
    • Example: api/categories/food
  • Request Body Content: An object having attributes type and color equal to the new values to assign to the category
    • Example: {type: "Food", color: "yellow"}
  • Response data Content: An object with parameter message that confirms successful editing and a parameter count that is equal to the count of transactions whose category was changed with the new type
    • Example: res.status(200).json({data: {message: "Category edited successfully", count: 2}, refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • In case any of the following errors apply then the category is not updated, and transactions are not changed
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if at least one of the parameters in the request body is an empty string
  • Returns a 400 error if the type of category passed as a route parameter does not represent a category in the database
  • Returns a 400 error if the type of category passed in the request body as the new type represents an already existing category in the database and that category is not the same as the requested one
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin)

deleteCategory

  • Request Parameters: None
  • Request Body Content: An array of strings that lists the types of the categories to be deleted
    • Example: {types: ["health"]}
  • Response data Content: An object with an attribute message that confirms successful deletion and an attribute count that specifies the number of transactions that have had their category type changed
    • Example: res.status(200).json({data: {message: "Categories deleted", count: 1}, refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Given N = categories in the database and T = categories to delete:
    • If N > T then all transactions with a category to delete must have their category set to the oldest category that is not in T
    • If N = T then the oldest created category cannot be deleted and all transactions must have their category set to that category
  • In case any of the following errors apply then no category is deleted
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if called when there is only one category in the database
  • Returns a 400 error if at least one of the types in the array is an empty string
  • Returns a 400 error if the array passed in the request body is empty
  • Returns a 400 error if at least one of the types in the array does not represent a category in the database
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin)

getCategories

  • Request Parameters: None
  • Request Body Content: None
  • Response data Content: An array of objects, each one having attributes type and color
    • Example: res.status(200).json({data: [{type: "food", color: "red"}, {type: "health", color: "green"}], refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 401 error if called by a user who is not authenticated (authType = Simple)

createTransaction

  • Request Parameters: A string equal to the username of the involved user
    • Example: /api/users/Mario/transactions
  • Request Body Content: An object having attributes username, type and amount
    • Example: {username: "Mario", amount: 100, type: "food"}
  • Response data Content: An object having attributes username, type, amount and date
    • Example: res.status(200).json({data: {username: "Mario", amount: 100, type: "food", date: "2023-05-19T00:00:00"}, refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if at least one of the parameters in the request body is an empty string
  • Returns a 400 error if the type of category passed in the request body does not represent a category in the database
  • Returns a 400 error if the username passed in the request body is not equal to the one passed as a route parameter
  • Returns a 400 error if the username passed in the request body does not represent a user in the database
  • Returns a 400 error if the username passed as a route parameter does not represent a user in the database
  • Returns a 400 error if the amount passed in the request body cannot be parsed as a floating value (negative numbers are accepted)
  • Returns a 401 error if called by an authenticated user who is not the same user as the one in the route parameter (authType = User)

getAllTransactions

  • Request Parameters: None
  • Request Body Content: None
  • Response data Content: An array of objects, each one having attributes username, type, amount, date and color
    • Example: res.status(200).json({data: [{username: "Mario", amount: 100, type: "food", date: "2023-05-19T00:00:00", color: "red"}, {username: "Mario", amount: 70, type: "health", date: "2023-05-19T10:00:00", color: "green"}, {username: "Luigi", amount: 20, type: "food", date: "2023-05-19T10:00:00", color: "red"} ], refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin)

getTransactionsByUser

  • Request Parameters: A string equal to the username of the involved user
    • Example: /api/users/Mario/transactions (user route)
    • Example: /api/transactions/users/Mario (admin route)
  • Request Body Content: None
  • Response data Content: An array of objects, each one having attributes username, type, amount, date and color
    • Example: res.status(200).json({data: [{username: "Mario", amount: 100, type: "food", date: "2023-05-19T00:00:00", color: "red"}, {username: "Mario", amount: 70, type: "health", date: "2023-05-19T10:00:00", color: "green"} ] refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 400 error if the username passed as a route parameter does not represent a user in the database
  • Returns a 401 error if called by an authenticated user who is not the same user as the one in the route (authType = User) if the route is /api/users/:username/transactions
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin) if the route is /api/transactions/users/:username
  • Can be filtered by date and amount if the necessary query parameters are present and if the route is /api/users/:username/transactions

getTransactionsByUserByCategory

  • The behavior defined below applies only for the specified route
  • Request Parameters: A string equal to the username of the involved user, a string equal to the requested category
    • Example: /api/users/Mario/transactions/category/food (user route)
    • Example: /api/transactions/users/Mario/category/food (admin route)
  • Request Body Content: None
  • Response data Content: An array of objects, each one having attributes username, type, amount, date and color, filtered so that type is the same for all objects
    • Example: res.status(200).json({data: [{username: "Mario", amount: 100, type: "food", date: "2023-05-19T00:00:00", color: "red"} ] refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 400 error if the username passed as a route parameter does not represent a user in the database
  • Returns a 400 error if the category passed as a route parameter does not represent a category in the database
  • Returns a 401 error if called by an authenticated user who is not the same user as the one in the route (authType = User) if the route is /api/users/:username/transactions/category/:category
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin) if the route is /api/transactions/users/:username/category/:category

getTransactionsByGroup

  • Request Parameters: A string equal to the name of the requested group
    • Example: /api/groups/Family/transactions (user route)
    • Example: /api/transactions/groups/Family (admin route)
  • Request Body Content: None
  • Response data Content: An array of objects, each one having attributes username, type, amount, date and color
    • Example: res.status(200).json({data: [{username: "Mario", amount: 100, type: "food", date: "2023-05-19T00:00:00", color: "red"}, {username: "Mario", amount: 70, type: "health", date: "2023-05-19T10:00:00", color: "green"}, {username: "Luigi", amount: 20, type: "food", date: "2023-05-19T10:00:00", color: "red"} ] refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 400 error if the group name passed as a route parameter does not represent a group in the database
  • Returns a 401 error if called by an authenticated user who is not part of the group (authType = Group) if the route is /api/groups/:name/transactions
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin) if the route is /api/transactions/groups/:name

getTransactionsByGroupByCategory

  • Request Parameters: A string equal to the name of the requested group, a string equal to the requested category
    • Example: /api/groups/Family/transactions/category/food (user route)
    • Example: /api/transactions/groups/Family/category/food (admin route)
  • Request Body Content: None
  • Response data Content: An array of objects, each one having attributes username, type, amount, date and color, filtered so that type is the same for all objects.
    • Example: res.status(200).json({data: [{username: "Mario", amount: 100, type: "food", date: "2023-05-19T00:00:00", color: "red"}, {username: "Luigi", amount: 20, type: "food", date: "2023-05-19T10:00:00", color: "red"} ] refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 400 error if the group name passed as a route parameter does not represent a group in the database
  • Returns a 400 error if the category passed as a route parameter does not represent a category in the database
  • Returns a 401 error if called by an authenticated user who is not part of the group (authType = Group) if the route is /api/groups/:name/transactions/category/:category
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin) if the route is /api/transactions/groups/:name/category/:category

deleteTransaction

  • Request Parameters: A string equal to the username of the involved user
    • Example: /api/users/Mario/transactions
  • Request Body Content: The _id of the transaction to be deleted
    • Example: {_id: "6hjkohgfc8nvu786"}
  • Response data Content: A string indicating successful deletion of the transaction
    • Example: res.status(200).json({data: {message: "Transaction deleted"}, refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if the _id in the request body is an empty string
  • Returns a 400 error if the username passed as a route parameter does not represent a user in the database
  • Returns a 400 error if the _id in the request body does not represent a transaction in the database
  • Returns a 400 error if the _id in the request body represents a transaction made by a different user than the one in the route
  • Returns a 401 error if called by an authenticated user who is not the same user as the one in the route (authType = User)

deleteTransactions

  • Request Parameters: None
  • Request Body Content: An array of strings that lists the _ids of the transactions to be deleted
    • Example: {_ids: ["6hjkohgfc8nvu786"]}
  • Response data Content: A message confirming successful deletion
    • Example: res.status(200).json({data: {message: "Transactions deleted"}, refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • In case any of the following errors apply then no transaction is deleted
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if at least one of the ids in the array is an empty string
  • Returns a 400 error if at least one of the ids in the array does not represent a transaction in the database
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin)

users.js

getUsers

  • Request Parameters: None
  • Request Body Content: None
  • Response data Content: An array of objects, each one having attributes username, email and role
    • Example: res.status(200).json({data: [{username: "Mario", email: "mario.red@email.com", role: "Regular"}, {username: "Luigi", email: "luigi.red@email.com", role: "Regular"}, {username: "admin", email: "admin@email.com", role: "Regular"} ], refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin)

getUser

  • Request Parameters: A string equal to the username of the involved user
    • Example: /api/users/Mario
  • Request Body Content: None
  • Response data Content: An object having attributes username, email and role.
    • Example: res.status(200).json({data: {username: "Mario", email: "mario.red@email.com", role: "Regular"}, refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 400 error if the username passed as the route parameter does not represent a user in the database
  • Returns a 401 error if called by an authenticated user who is neither the same user as the one in the route parameter (authType = User) nor an admin (authType = Admin)

createGroup

  • Request Parameters: None
  • Request request body Content: An object having a string attribute for the name of the group and an array that lists all the memberEmails
    • Example: {name: "Family", memberEmails: ["mario.red@email.com", "luigi.red@email.com"]}
  • Response data Content: An object having an attribute group (this object must have a string attribute for the name of the created group and an array for the members of the group), an array that lists the alreadyInGroup members (members whose email is already present in a group) and an array that lists the membersNotFound (members whose email does not appear in the system)
    • Example: res.status(200).json({data: {group: {name: "Family", members: [{email: "mario.red@email.com"}, {email: "luigi.red@email.com"}]}, membersNotFound: [], alreadyInGroup: []} refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • If the user who calls the API does not have their email in the list of emails then their email is added to the list of members
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if the group name passed in the request body is an empty string
  • Returns a 400 error if the group name passed in the request body represents an already existing group in the database
  • Returns a 400 error if all the provided emails (the ones in the array, the email of the user calling the function does not have to be considered in this case) represent users that are already in a group or do not exist in the database
  • Returns a 400 error if the user who calls the API is already in a group
  • Returns a 400 error if at least one of the member emails is not in a valid email format
  • Returns a 400 error if at least one of the member emails is an empty string
  • Returns a 401 error if called by a user who is not authenticated (authType = Simple)

getGroups

  • Request Parameters: None
  • Request Body Content: None
  • Response data Content: An array of objects, each one having a string attribute for the name of the group and an array for the members of the group
    • Example: res.status(200).json({data: [{name: "Family", members: [{email: "mario.red@email.com"}, {email: "luigi.red@email.com"}]}] refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin)

getGroup

  • Request Parameters: A string equal to the name of the requested group
    • Example: /api/groups/Family
  • Request Body Content: None
  • Response data Content: An object having a string attribute for the name of the group and an array for the members of the group
    • Example: res.status(200).json({data: {group: {name: "Family", members: [{email: "mario.red@email.com"}, {email: "luigi.red@email.com"}]}} refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 400 error if the group name passed as a route parameter does not represent a group in the database
  • Returns a 401 error if called by an authenticated user who is neither part of the group (authType = Group) nor an admin (authType = Admin)

addToGroup

  • Request Parameters: A string equal to the name of the group
    • Example: api/groups/Family/add (user route)
    • Example: api/groups/Family/insert (admin route)
  • Request Body Content: An array of strings containing the emails of the members to add to the group
    • Example: {emails: ["pietro.blue@email.com"]}
  • Response data Content: An object having an attribute group (this object must have a string attribute for the name of the created group and an array for the members of the group, this array must include the new members as well as the old ones), an array that lists the alreadyInGroup members (members whose email is already present in a group) and an array that lists the membersNotFound (members whose email does not appear in the system)
    • Example: res.status(200).json({data: {group: {name: "Family", members: [{email: "mario.red@email.com"}, {email: "luigi.red@email.com"}, {email: "pietro.blue@email.com"}]}, membersNotFound: [], alreadyInGroup: []} refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • In case any of the following errors apply then no user is added to the group
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if the group name passed as a route parameter does not represent a group in the database
  • Returns a 400 error if all the provided emails represent users that are already in a group or do not exist in the database
  • Returns a 400 error if at least one of the member emails is not in a valid email format
  • Returns a 400 error if at least one of the member emails is an empty string
  • Returns a 401 error if called by an authenticated user who is not part of the group (authType = Group) if the route is api/groups/:name/add
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin) if the route is api/groups/:name/insert

removeFromGroup

  • Request Parameters: A string equal to the name of the group
    • Example: api/groups/Family/remove (user route)
    • Example: api/groups/Family/pull (admin route)
  • Request Body Content: An array of strings containing the emails of the members to remove from the group
    • Example: {emails: ["pietro.blue@email.com"]}
  • Response data Content: An object having an attribute group (this object must have a string attribute for the name of the created group and an array for the members of the group, this array must include only the remaining members), an array that lists the notInGroup members (members whose email is not in the group) and an array that lists the membersNotFound (members whose email does not appear in the system)
    • Example: res.status(200).json({data: {group: {name: "Family", members: [{email: "mario.red@email.com"}, {email: "luigi.red@email.com"}]}, membersNotFound: [], notInGroup: []} refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • The group must have at least one user after deleting, so given M = members of the group and N = emails to delete:
    • if N >= M at least one member of the group cannot be deleted (the member that remains can be any member, there is no rule on which one it must be)
  • In case any of the following errors apply then no user is removed from the group
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if the group name passed as a route parameter does not represent a group in the database
  • Returns a 400 error if all the provided emails represent users that do not belong to the group or do not exist in the database
  • Returns a 400 error if at least one of the emails is not in a valid email format
  • Returns a 400 error if at least one of the emails is an empty string
  • Returns a 400 error if the group contains only one member before deleting any user
  • Returns a 401 error if called by an authenticated user who is not part of the group (authType = Group) if the route is api/groups/:name/remove
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin) if the route is api/groups/:name/pull

deleteUser

  • Request Parameters: None
  • Request Body Content: A string equal to the email of the user to be deleted
    • Example: {email: "luigi.red@email.com"}
  • Response data Content: An object having an attribute that lists the number of deletedTransactions and an attribute that specifies whether the user was also deletedFromGroup or not
    • Example: res.status(200).json({data: {deletedTransactions: 1, deletedFromGroup: true}, refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • If the user is the last user of a group then the group is deleted as well
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if the email passed in the request body is an empty string
  • Returns a 400 error if the email passed in the request body is not in correct email format
  • Returns a 400 error if the email passed in the request body does not represent a user in the database
  • Returns a 400 error if the email passed in the request body represents an admin
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin)

deleteGroup

  • Request Parameters: None
  • Request Body Content: A string equal to the name of the group to be deleted
    • Example: {name: "Family"}
  • Response data Content: A message confirming successful deletion
    • Example: res.status(200).json({data: {message: "Group deleted successfully"} , refreshedTokenMessage: res.locals.refreshedTokenMessage})
  • Returns a 400 error if the request body does not contain all the necessary attributes
  • Returns a 400 error if the name passed in the request body is an empty string
  • Returns a 400 error if the name passed in the request body does not represent a group in the database
  • Returns a 401 error if called by an authenticated user who is not an admin (authType = Admin)

utils.js

handleDateFilterParams

  • Returns an object with a date attribute used for filtering mongoDB's aggregate queries
  • The value of date is an object that depends on the query parameters:
    • If the query parameters include from then it must include a $gte attribute that specifies the starting date as a Date object in the format YYYY-MM-DDTHH:mm:ss
      • Example: /api/users/Mario/transactions?from=2023-04-30 => {date: {$gte: 2023-04-30T00:00:00.000Z}}
    • If the query parameters include upTo then it must include a $lte attribute that specifies the ending date as a Date object in the format YYYY-MM-DDTHH:mm:ss
      • Example: /api/users/Mario/transactions?upTo=2023-05-10 => {date: {$lte: 2023-05-10T23:59:59.999Z}}
    • If both from and upTo are present then both $gte and $lte must be included
    • If date is present then it must include both $gte and $lte attributes, these two attributes must specify the same date as a Date object in the format YYYY-MM-DDTHH:mm:ss
      • Example: /api/users/Mario/transactions?date=2023-05-10 => {date: {$gte: 2023-05-10T00:00:00.000Z, $lte: 2023-05-10T23:59:59.999Z}}
    • If there is no query parameter then it returns an empty object
      • Example: /api/users/Mario/transactions => {}
  • Throws an error if date is present in the query parameter together with at least one of from or upTo
  • Throws an error if the value of any of the three query parameters is not a string that represents a date in the format YYYY-MM-DD

verifyAuth

  • Verifies that the tokens present in the request's cookies allow access depending on the different criteria.
  • Returns an object with a boolean flag that specifies whether access is granted or not and a cause that describes the reason behind failed authentication
    • Example: {authorized: false, cause: "Unauthorized"}
  • Refreshes the accessToken if it has expired and the refreshToken allows authentication; sets the refreshedTokenMessage to inform users that the accessToken must be changed

handleAmountFilterParams

  • Returns an object with an amount attribute used for filtering mongoDB's aggregate queries
  • The value of amount is an object that depends on the query parameters:
    • If the query parameters include min then it must include a $gte attribute that is an integer equal to min
      • Example: /api/users/Mario/transactions?min=10 => `{amount: {$gte: 10} }
    • If the query parameters include min then it must include a $lte attribute that is an integer equal to max
      • Example: /api/users/Mario/transactions?min=50 => `{amount: {$lte: 50} }
    • If both min and max are present then both $gte and $lte must be included
    • If neither is present then the function must return an empty object
      • Example: /api/users/Mario/transactions => {}
  • Throws an error if the value of any of the two query parameters is not a numerical value