This repository contains the Ruby on Rails assessment (A2-1) for Coder Academy's GenTech program.
Group members:
- Jack Gronow
- Tony Huynh
- Natalie Sargent
The initial purpose of Bookly arose when the development team observed the lack of localised and dedicated platforms on which students could exchange their textbooks. Many competitors who dominate the marketplace sphere often fail to tailor their products to student needs. The resulting product is often a convoluted and unintuitive website for students. While failing to understand their key demographic and charging disproportionately high fees, a void was created in the marketplace. It was because of this demand for a student-centric textbook marketplace that Bookly was created.
During the development process, the team came to fully grasp the generic and modular nature of the application design. By a unanimous vote, it was decided that the application be tailored to the general public selling books of all kinds.
Starting from its humble beginnings, targeting students, the development team of Bookly eventually recognised that everyone could benefit from a book-specific marketplace that operates on a no/low-fee model. Business models behind the dominating marketplaces have changed over time. Many move to subscription-based services while others increase payment fees in order to fund other financial ventures. Whenever this business model shift occurs on an industry-wide level, there is a new drive in demand for simpler alternatives. Bookly fulfills this demand with a no-nonsense book exchange marketplace that does what it does best, provide a platform for those wishing to buy and sell books.
The driving influence and philosophy behind Bookly is to focus on one purpose and to excel at that purpose. A marketplace on which people can exchange books is simple in design but incredibly difficult to implement with many minor considerations that affect the user experience and overall quality of the product.
Contrasting with generic marketplaces, Bookly focuses on books and allows users to list books for sale or place an order with minimal impediments. A user need not spend more time than necessary in order to list a book as being for sale. Similarly, a user will find very few steps in between logging in to Bookly and placing an order for a book that they have just found. This straightforward implementation resolves any qualms that a user might have in navigating a more convoluted marketplace competitor to achieve the same objective as on Bookly.
- HTML
- Sass / CSS
- Bulma
- JavaScript
- Ruby and eRuby (Embedded Ruby)
- Ruby on Rails
- PostgreSQL
- Heroku
Installing Ruby:
- Ensure Ruby is installed by entering the following command in Terminal:
ruby --version
- If a valid Ruby version is not returned, refer to the following link to install Ruby for your operating system: https://www.ruby-lang.org/en/downloads/
Installing PostgreSQL:
- Ensure PostgreSQL is installed by entering the following command in Terminal:
psql --version
- If a valid version of PostgreSQL is not returned, refer to the link below to install PostgreSQL for your operating system: https://www.postgresql.org/download/
Downloading the Application:
- Navigate to the GitHub repository as linked above; and
- Either clone or download ZIP of the application.
Installing Gems:
- Ensure you have Bundler installed by opening Terminal and entering the following command:
bundler --version
- If Bundler is not installed and an error is returned, enter the following command to install:
gem install bundler
- If Bundler is not installed and an error is returned, enter the following command to install:
- Navigate to the root directory of the application and install the required gems with:
bundle install
Database Setup:
- Bookly's repository contains the default database configuration in
database.example.yml
. - Ensure your Terminal's path is in
/Bookly/config
and enter the following command to copy the contents of this default configuration into a separate file to be used by Rails:cp database.example.yml database.yml
- If necessary, change the settings within
database.yml
to match the credentials of your PostgreSQL setup.
Running the Application on your localhost:
- Ensure you are in the application's root directory and enter the following command to run the application within your browser:
rails server
For this application the team used Heroku for the HTTP routing. This cloud service was chosen as it handles all of the routing through the use of web, worker and one-off dynos. Heroku handles incoming requests by sending them to a load balancer that offers SSL (Secure Socket Layer) termination. From there the request is sent to a router which then determines where the web dynos for the application will be located. Once a web dyno has been selected the router establishes a TCP connection with the dyno then forwards the HTTP request to said dyno. Once the web dyno has received the HTTP request, the request is put into a queue and the web dyno returns a success message to the user. A worker dyno picks up the request and runs any process in the users procfile that is not defined as 'web'. The worker dyno returns a result and then saves to a database.
Bookly makes use of several third party software and services for varying purposes.
Cloudinary provides a file upload and storage service whereby users can upload images and videos to a secure cloud storage system. By employing Cloudinary, the development team avoids having to build and maintain any infrastructure.
Databases excel at storing small pieces of data in significant numbers but are not optimised to hold large chunks of data, such as media files. It is possible to store media files in a local folder when working locally. However, the files in said local folder cannot be uploaded when services such as Heroku are employed for deployment, since they are not designed to store files long term.
In configuring Bookly to integrate with Cloudinary's service, two gems were added to the application which include cloudinary
and activestorage-cloudinary-service
. Subsequently, Cloudinary was configured within storage.yml
where API keys were set. Cloudinary was then set as the upload service of the application in config/environments/production.rb
.
Stripe is a complete payments platform that was integrated into Bookly in order to accept payments online via credit card. By using this third party platform, the development team can offer users unmatched convenience with security features that are arguably essential to a marketplace application.
It is an industry standard that credit card details ought not to be stored on our servers as Bookly is not compliant with the Payment Card Industry Data Security Standard, much like many other marketplaces who also use third party payment platforms.
Thus, Stripe was added to Bookly in the form of a Ruby gem and a checkout session to be placed in a controller. Following the successful completion of payment, Stripe will redirect the users away from that third party service to a page on Bookly. This provides for a seamless transition between Bookly and Stripe that improves usability.
Aside from Cloudinary and Stripe as mentioned above, other gems were used for the development of Bookly.
RSpec is a Domain Specific Language testing tool used to test Ruby code. It formed a large part of the testing process which is discussed in greater detail under the Testing Process heading.
The GoogleBooks gem forms an essential part of Bookly's back end code where it acts as an intermediary between the Rails application and Google Books' servers. The gem queries the Google API to search for publications in the Google Books repository, having found a match it will assist the application in acquiring information on a specific book and hold a copy of that data in Bookly's own database within the books
table.
Devise is a flexible authentication solution for Rails based on Warden. It was adopted as the authentication system for Bookly since it is relatively easy to integrate. The gem hashes and stores passwords in the database to validate the authenticity of a user while signing in. It is generally advisable not to store passwords in plaintext. With Devise, the development team can easily store encrypted credentials instead which further secures user data should a breach of security occur.
For this project the development team decided to use PostgreSQL to manage the database. The initial reason for this was that all team members had some familiarity with PostgreSQL and its application to Rails. Upon further examination of PostgreSQL, the team also discovered that it was one of the most advanced database management systems available. The pros of PostgreSQL being that it is flexible, highly extensible, and offers strong security. PostgreSQL allows the user to set specific parameters, thus allowing the ability to place custom limits on fields that require user input. Another great feature that PostgreSQL offers is that it is highly extensible, allowing continuous changes to the database. For example, if the need arose to add a new data type this can be done through PostgreSQL simply by asking it to create an extension. PostgreSQL also offers great security features: it provides parameter security; as well as app security. Another arguably great thing about PostgreSQL is that it is an open source project meaning that if an issue arose there is a vibrant and independent community to aid the team. On the other hand PostgreSQL has some draw backs when compared to newer NoSQL databases. It has a slower performance and is very strict towards querying data. However, for the purposes of this application the pros far outweigh the cons.
For the Rails application the team used PostgreSQL as the production database instance (environment). However, to set up the database the team used the built-in Rails helpers to generate migration scripts that were then run using rails db:migrate
. These migration scripts were generated to create, edit and remove tables from the database. This setup method was selected because the team already had a basic understanding of migration scripts and their application to PostgreSQL.
- Formulate required data as models and their entities;
- Establish relations between those models in order to maximise efficiency and reduce repetition in data;
- Define methods within the model to determine how that data is passed to the controller;
- Create controller actions where required to interact with database and views; and
- Create views to display that data to the end user.
As seen in the above visualisation, the high-level components of Bookly can be broken into three broad categories of models, views and controllers as per the MVC architecture. The architecture has been framed as a part of the workflow in order to better demonstrate the different components being used at various stages.
The User
model is the first component that a user will interact with, where they are prompted to create an account before they can access the application's features. Here, the User
model determines the form in which inputted data is saved into the database. Once validated by the model, that data is saved into the Users
table within the database.
When a user attempts to add a book to Bookly, they will search for a book which triggers the Book
model. Within the model is a method by which the GoogleBooks
gem queries the Google Books servers to return the first result. Specifically, a book's title, description, authors, ISBN and a link to the cover image are pulled from said servers. Consequently, the Books
controller saves that data into the database.
When the user clicks on the book that was just added, the Books
controller will accept the ID of the book as a parameter and pull a book from the database which matches that parameter. That information is passed from the database, through the controller, to the view where it is displayed to the user.
Should the user click to sell the book as displayed in the view, the Adverts
controller will obtain the ID of the book as a foreign key and create a new object within the adverts
table of the database. The data in that adverts
table is conversely passed to the Adverts
controller where it is then displayed to the user in the view.
If the user clicks on the advert, the ID of the advert is passed through to the Adverts
controller where the view will display that selected advert.
Once the user attempts to order the book within that advert, the ID of the advert will be passed to the Orders
controller which redirects that information to Stripe in order to process payment. Once payment is completed, the Orders
controller is again tasked with routing the user to the completion page. Here, the relevant view displays a completion prompt to the user.
It would be fair to claim that most two-sided marketplaces in current existence work in a very similar manner to Bookly, but on a larger scale in terms of features and the sheer amount of data being processed. There are several large, online-based bookstores, such as Book Depository, but they do not compete in the same space as they are not two-sided.
eBay is arguably the most prominent of examples when it comes to similar two-sided marketplaces. Indeed, one of the most obvious comparisons is that it is not catered to people exchanging books but open to all kinds of products. This in turn requires eBay to store many more models with their specific entities. These models may include categories or, more precisely, item types and their details that can range from condition to technical specifications (e.g. of a mobile phone). eBay's expansive model of data results in little reusability of tables and relations as it caters for more obscure and minute differences in products.
In terms of payment, eBay employs PayPal as its payment gateway much like Bookly uses Stripe as the third-party to process payments. Although PayPal was a wholly-owned subsidiary of eBay for nearly 13 years, it is presently a separate publicly traded company. Consequently, eBay is divested of its responsibility in storing payment data as the majority of purchases route through the independent PayPal.
- US1 - As a User I can post an advert for my chosen book.
- US2 - As a User I can search for a specific book.
- US3 - As a User I can view a list of books available for sale.
- US4 - As a User I can order a book.
- US5 - As a User I can add photos to my adverts.
- US6 - As a Seller I can delete my adverts.
To plan this the team used Trello and daily stand-ups to ensure everyone was up to date and knew what their task for the day was.
- Settle on an idea for the marketplace application
- Begin skeletons of the App, README, ERD, Trello
- Implement a CSS framework (i.e. Bulma)
- Finalise the ERD
- Plan the Models
- Populate database with test data
- Finalise wireframes
- Create relationships between Models
- Define a clear purpose for each Controller
- Implement Models
- Begin manual testing
- Plan Controllers
- Implement Controllers
- Implement User Authentication with Devise
- Finish MVP
- Finish RSpec testing
- Begin documentation
- Finalise the MVP with negative testing
- Work through the documentation
- Complete any remaining tasks on the Trello Board
The development team constantly communicated with each other every day on Slack.
The team has endeavoured to implement an Agile method of development for this project, which is considered to be a more flexible and iterative approach. Being a small team, there was regular communication in person, as well as via Slack, Trello and GitHub. Establishing user stories helped to define the minimum viable product. Development and testing were completed concurrently, which meant the team could make necessary adjustments to ensure the project remained on track to meet the MVP requirements.
As was mentioned previously, initially the team intended to create an app dedicated to the exchange of text books specifically, and the user stories were written in that regard. However, as development progressed it was agreed that the app could have wider appeal if it was not limited to text books alone. The implementation of Google Books allowed the user to add any book title, and so the team agreed to expand the scope of the app to be a book exchange, for all books.
Throughout development, feedback was welcomed among team members. Minor changes were implemented where it was considered beneficial and viable. Where ideas fell significantly outside the scope of the MVP, they were recorded in Trello to be considered in future extensions of the project.
Source control proved to be one of the more challenging aspects during the development of Bookly. The group members all lacked experience in working with teams of more than two, thus the early stages of planning largely consisted of establishing a consistent workflow that all members had to abide by.
The consensus largely consisted of opinions favouring the Feature Branch Workflow. As the workflow used by many companies for in-house development, the Feature Branch Workflow favours the idea that all feature development take place in a feature branch instead of the master
branch. This workflow ensures that the main codebase remains undisturbed while multiple developers are working on a particular feature.
Basic source control process as followed by the team:
- Having ensured that the master branch is up to date with
git pull origin master
, create and checkout to a new branch withgit checkout -b <branch>
; - After completing changes on the branch, add and commit those changes on the local repository, then push them to the branch with
git push origin <branch>
; - The author of those changes is to create a pull request on GitHub, requesting a review by the other two developers;
- Once reviewed by the other developers, one of those parties will then merge those changes with
master
.
The development team of Bookly underwent a thorough testing process that constantly ensured the quality and integrity of the application remain intact. Testing consisted of both manual testing and unit testing.
The development team executed manual test cases to discover bugs in the Rails application. Manual testing is a necessity as it allows developers to understand the feasibility of automated testing. In the case of Bookly, the developers continually tested each component of the application as they were being created, whether it be the controllers, models or views. A record of the manual testing process can be found below.
Unit testing consists of testing individual components of the application but differs from manual testing in that it is often automated. RSpec, the behaviour-driven development framework, was used for this project to ensure that all components matched a consistent design as defined in the test files. The output of said automated tests can be found below.
Tue 30 Jul 23:20:15 AEST 2019
===================================
Advert
creates new adverts with correct fields
Book
creates new questions with correct fields
User
creates a new user with correct fields
BooksController
routing
routes to #index
routes to #new
routes to #show
routes to #edit
routes to #create
routes to #update via PUT
routes to #update via PATCH
routes to #destroy
Finished in 0.07638 seconds (files took 0.84083 seconds to load)
11 examples, 0 failures
===================================
Wed 31 Jul 11:12:23 AEST 2019
===================================
BooksController
GET #index
returns a success response
GET #show
returns a success response
GET #new
returns a success response
POST #create
with valid params
creates a new Book
Advert
creates new adverts with correct fields
Book
creates new books with correct fields
User
creates new adverts with correct fields
Books
GET /books
works!
BooksController
routing
routes to #index
routes to #new
routes to #show
routes to #edit
routes to #create
routes to #update via PUT
routes to #update via PATCH
routes to #destroy
books/index
renders a list of books
books/new
renders new book form
books/show
renders attributes in <p>
Finished in 1.91 seconds (files took 0.87056 seconds to load)
19 examples, 0 failures
===================================
Mon 5 Aug 15:59:30 AEST 2019
===================================
AdvertsController
GET #index
returns a success response
GET #show
returns a success response
GET #new
returns a success response
POST #create
with valid params
creates a new Advert
redirects to the created advert
with invalid params
returns a success response
DELETE #destroy
destroys the requested advert
redirects to the adverts list
BooksController
GET #index
returns a success response
GET #show
returns a success response
GET #new
returns a success response
POST #create
with valid params
creates a new Book
redirects to the created book
with invalid params
returns a success response
Advert
creates new adverts with correct fields
Book
creates new books with correct fields
User
creates new adverts with correct fields
Adverts
GET /adverts
works!
Books
GET /books
works!
AdvertsController
routing
routes to #index
routes to #new
routes to #show
routes to #edit
routes to #create
routes to #update via PUT
routes to #update via PATCH
routes to #destroy
BooksController
routing
routes to #index
routes to #new
routes to #show
routes to #edit
routes to #create
routes to #update via PUT
routes to #update via PATCH
routes to #destroy
books/index
renders a list of books
books/new
renders new book form
books/show
renders attributes in <p>
Finished in 2.8 seconds (files took 0.84801 seconds to load)
38 examples, 0 failures
===================================
Mon 5 Aug 22:06:24 AEST 2019
===================================
AdvertsController
GET #index
returns a success response
GET #show
returns a success response
GET #new
returns a success response
POST #create
with valid params
creates a new Advert
redirects to the created advert
with invalid params
returns a success response
DELETE #destroy
destroys the requested advert
redirects to the adverts list
BooksController
GET #index
returns a success response
GET #show
returns a success response
GET #new
returns a success response
POST #create
with valid params
creates a new Book
redirects to the created book
with invalid params
returns a success response
Advert
creates new adverts with correct fields
Book
creates new books with correct fields
User
creates new adverts with correct fields
Adverts
GET /adverts
works!
Books
GET /books
works!
AdvertsController
routing
routes to #index
routes to #new
routes to #show
routes to #edit
routes to #create
routes to #update via PUT
routes to #update via PATCH
routes to #destroy
BooksController
routing
routes to #index
routes to #new
routes to #show
routes to #edit
routes to #create
routes to #update via PUT
routes to #update via PATCH
routes to #destroy
adverts/index
renders a list of adverts
adverts/new
renders new advert form
adverts/show
renders attributes in <p>
books/index
renders a list of books
books/new
renders new book form
books/show
renders attributes in <p>
Finished in 3.6 seconds (files took 0.84266 seconds to load)
41 examples, 0 failures
===================================
Tue 6 Aug 09:37:14 AEST 2019
===================================
AdvertsController
GET #index
returns a success response
GET #show
returns a success response
GET #new
returns a success response
POST #create
with valid params
creates a new Advert
redirects to the created advert
with invalid params
returns a success response
DELETE #destroy
destroys the requested advert
redirects to the adverts list
BooksController
GET #index
returns a success response
GET #show
returns a success response
GET #new
returns a success response
POST #create
with valid params
creates a new Book
redirects to the created book
with invalid params
returns a success response
Advert
creates new adverts with correct fields
Book
creates new books with correct fields
Order
creates new orders with correct fields
User
creates new adverts with correct fields
Adverts
GET /adverts
works!
Books
GET /books
works!
Orders
GET /orders
works!
AdvertsController
routing
routes to #index
routes to #new
routes to #show
routes to #edit
routes to #create
routes to #update via PUT
routes to #update via PATCH
routes to #destroy
BooksController
routing
routes to #index
routes to #new
routes to #show
routes to #edit
routes to #create
routes to #update via PUT
routes to #update via PATCH
routes to #destroy
OrdersController
routing
routes to #index
routes to #new
routes to #show
routes to #edit
routes to #create
routes to #update via PUT
routes to #update via PATCH
routes to #destroy
adverts/index
renders a list of adverts
adverts/new
renders new advert form
adverts/show
renders attributes in <p>
books/index
renders a list of books
books/new
renders new book form
books/show
renders attributes in <p>
Finished in 3.66 seconds (files took 0.84835 seconds to load)
51 examples, 0 failures
=======
The information security requirements that are related to information systems are 'Authentication and Authorisation', 'Data Backups', 'Encryption', and 'Third Party Vendor Security'. The following list is made up of the required information security for an information system. However, there are multiple other requirements that further the strength of an application's security, to an information system.
This requirement is focused on identifying a user's unique system, controlling a user's privileges and ensuring that all sensitive data is secure and encrypted. The reason that each user has to be identified is to prevent a user from signing up with a bot to spam the database with information causing various complications. After the user has been identified they are then able to sign up, which requires a password. Passwords fall under the category of sensitive data. This is another aspect of the authentication side of this requirement - encrypting sensitive data for the user that is then used to protect their account. Once a user has signed up, they are assigned the absolute minimum amount of privileges needed to use the application. Applying such heavy restrictions to the users privileges means that there are less pages to add authentication to, saving the team time. In the application, Devise was used to handle the 'Authentication and Authorisation'.
Encrypting data is one of the best ways to prevent users from gaining access to sensitive data. This method of security is used to disguise sensitive data like API keys to third party services, passwords and specific portions of code to the user. For this project a small amount of manual encryption was used for the Stripe and Cloudinary API keys, in the credentials
file provided by Rails. The rails credentials
file can only be decrypted with the unique master.key
file that is shared only within the team, granting them access to the API keys in the credentials
file. Multiple cloud services have also been used, each with their own inbuilt encryption protocols. It is recommended that if this application is published, much more information would need to be encrypted. For example, IDs and crucial source code (unless the application is released as open source).
Another requirement of information security is having backups of all the data that is stored on the system. This security measure ensures that the users crucial data (i.e. their account) will not be lost if an error occurs that requires the database to be reset. These backups offer a level of safety and security to the data that cannot be gained any other way. For this application the team chose to backup all source code on GitHub.
Outsourcing to a third party vendor is a great way to save time and resources. However, for each additional cloud service used by an application, that application can only be as secure as that cloud service. Hence, it is important when outsourcing to only provide the vendor with the minimum amount of privilege and data. Doing this ensures that the application is not left open to unnecessary levels of risk. Another aspect of third party vendor security is ensuring that the connection between an application's local server and the third party's server is secure. An easy way to do this is through the use of API keys for encryption purposes. However, these precautions are meaningless if the vendor itself is insecure. The third party services that are used in this application are Stripe, Cloudinary and Heroku. All of these services offer strong reliable security. In the future though, if there was an increase in user traffic, it may be necessary to consider upgrading the subscriptions with Heroku, Cloudinary and Stripe, as more server space may be required.
The methods used to protect data are Devise, Heroku, Cloudinary, Stripe and the credentials
file provided by Rails to encrypt sensitive data (e.g. API keys).
The initial reason for choosing Devise was because it was quite easy to implement and came with its own views for signing up and logging in as well as a few built-in methods that made tailoring the UI to each individual user much simpler. After doing some further research the team discovered that Devise was in fact a viable option for user authentication as it uses strong encryptions and strict validations to keep its information safe. Devise uses an authentication model that is responsible for sending the user's passwords to a hash that is then encrypted, and for validating the authenticity of a user while signing in. To encrypt the passwords, Devise generates a random string that is then fed to the development database for a Rails application. Devise also validates the user's inputs based on default requirements like default password length and validation for emails. Overall, Devise is an incredibly secure, easy to use cloud service for adding authentication to a Rails application.
Heroku employs many different methods of security to protect their servers along with the user's information and their network. These methods include 'Firewalls', 'Spoofing and Sniffing Protections', 'System and Customer Authentication' and 'Application Isolation'. The Heroku firewalls are designed to restrict access to systems from external networks and internal systems. "By default, all access is denied and only explicitly allowed ports and protocols are allowed based on business need." This excessive limitation helps Heroku with another method of security. Application isolation in Heroku is done through the use of dynos that are randomly selected by an algorithm run by Heroku. Isolating each user application in their own environment prevents a myriad of stability and security issues. These issues being Distributed Denial of Service (DDoS) attacks and Spoofing and Sniffing. This is because each environment is protected with a firewall that will not deliver traffic to an interface that it is not directly addressed to. Heroku protects its connection to the PostgreSQL database through an incredibly strong encryption called SSL.
A new feature (as of Rails 5.2) is an encrypted credentials file. This file is encrypted with a unique key that is stored in the master.key
file. This file was used in the application to protect the API keys for Stripe and Cloudinary. The benefits of using the built-in Rails encryption is that encrypted credentials are saved in a repository and are all in one file. This saved time as it was easier to manage having one file, than decoding multiple. An example of a master.key
file can be found here.
Cloudinary and Stripe use very similar security measures to keep their service safe. In terms of application security, they both use a secret API key to connect to an application. This key is stored in the credentials file and is encrypted by both services. In regards to network security, they differ slightly. Cloudinary uses third party software called Dome9 to monitor network traffic to and from their servers. Stripe uses an SSL encryption for all of its HTTP requests. Both of these methods are viable and provide more than a reasonable amount of security.
To backup our application we used GitHub. GitHub is a trusted online storage platform that is used by many real world companies and therefore has more than enough security for our purposes.
Australian businesses have been primarily governed by the Privacy Act 1988 ("the Act") since an amendment was passed in 2000. The Act contains the 13 Australian Privacy Principles (APPs) which are the cornerstone of the privacy protection framework.
As summarised by the Office of the Australian Information Commissioner, the APPs govern the standards, rights and obligations around:
- the collection, use and disclosure of personal information;
- an organisation or agency’s governance and accountability;
- integrity and correction of personal information; and
- the rights of individuals to access their personal information.
Section 16 of the Act establishes than an Australian Privacy Principles (APP) entity must comply with the APP, said entity means an agency or organisation as defined in s 1. The threshold for constituting an 'organisation' lies in the requirement that the individual, body corporate, partnership, unincorporated association or trust have an annual turnover of more than AUD$3,000,000 for a financial year unless an exception within s 6D applies.
A party may constitute an 'organisation' and be held responsible under the APP should it be held accountable to the exceptions within the s 6D as mentioned above. The exceptions include businesses that provide a health service and hold health information, disclose personal information for profit, or are contracted service providers for a Commonwealth contract under s 6D(4).
As Bookly does not have an annual turnover above AUD$3,000,000 nor does it fall within the exceptions of s 6D, it ought not to constitute an 'organisation' under the act or be held responsible under the APP and Privacy Act 1988.
The General Data Protection Regulation (GDPR) is a regulation that was approved by the European Union (EU) Parliament in 2016 and became enforceable as of 25 May 2018. The regulation primarily aims to give control to individuals over their personal data and to simplify the regulatory environment for international business.
The GDPR not only applies to organisations located within the EU, but also to organisations outside of the EU. While Bookly operates as a marketplace primarily targeting the Australian market, it is an online marketplace that is unrestricted by geographical boundaries and, in a strict sense, falls within the requirements as set out in the GDPR.
As Bookly can theoretically store personal data about EU citizens, it must comply with data subject rights which concern multiple aspects.
Processing personal data is generally prohibited with the exception that it is expressly allowed by law, or the data subject has consented to such processing. The requirements for the effectiveness of a valid legal consent are contained within Article 7 and further clarified by recital 32 of the GDPR. In essence, consent must be freely given, specific, informed and unambiguous. Regarding "freely given", consent must have been given on a voluntary basis without inappropriate pressure or influence that could have affected that choice.
Article 33 of the GDPR establishes that, in the case of a personal data breach, the controller must notify the personal data breach to the supervisory authority competent in accordance with Article 55 not later than 72 hours after having become aware of the breach. There will be an exception to the notification requirement where the breach is unlikely to result in a risk to the rights and freedoms of natural persons.
By virtue of Article 15 of the GDPR, a data subject shall have the right to obtain from the controller confirmation as to whether or not personal data concerning the data subject are being processed. If applicable, the controller must also allow access to the personal data.
Article 20 of the GDPR concerns the right to data portability. As stated within the article, the data subject shall have the right to receive his or her personal data to which he or she has provided to a controller. Specifically, this personal data must be provided in a structured, commonly used and machine-readable format where the data subject may transmit those data to another controller without hindrance.
Article 25 of the GDPR requires that controllers implement appropriate technical and organisational measures designed to implement data-protection principles, such as data minimisation, in an effective manner and to integrate necessary safeguards into the processing.
Most relevantly, the controller must implement appropriate technical and organisational measures to ensure that only personal data which are necessary for each specific purpose of the processing are processed.
Bookly, by design, collects a minimal amount of information to process orders. In order to use Bookly, a user must sign up for an account which will request first name, last name, username, email and password. Of these, last name and username are optional fields. Email and password are used for security and authentication purposes while first name is used for personalised prompts and messages. The development team stress that they are committed to open and transparent management of user data, information such as passwords are encrypted and not visible even to the developers themselves.
A relational database recognises relationships among sets of data, stored in tables. These tables form the models for the database. The table columns represent the model attributes or fields, while a row represents a record.
In designing the database, it is important to consider what information is required, how it will be sorted and stored among tables, and how the data can be accessed to accommodate processing and reporting needs. The goal is to optimise database efficiency by reducing redundancy and duplication of data, ensuring data accuracy and integrity, and allowing for extendibility. The process of database normalisation seeks to achieve this by splitting data into smaller tables, where data is not repeated, and linking them through relationships.
The Entity Relationship Diagram (ERD) below displays how the database for this project has been designed. Data is stored in four separate tables: Users
, Books
, Adverts
and Orders
. Data is not duplicated in any of these tables. The Adverts
table retrieves the seller's information from the Users
table through the foreign key, seller_id
, and book information from the Books
table through the foreign key, book_id
. Likewise, the Orders
table draws information about an advert from the Adverts
table, through the foreign key, advert_id
, and information about the buyer from the Users
table through the foreign key, buyer_id
.
A user can sign up and must be logged in to buy or sell books. Once logged in a user can:
- View a list of books
- Add a new book to the list
- Select a book to sell
- Create an advert
- Delete an advert that they have created
- View adverts placed by other users
- Complete an order to buy a book from another user
The user primary key is the user_id
. A user has an email
and password
attribute, which are both stored as a string. The default for these attributes is an empty string and they are set as required at the database level. These fields have been implemented with the Devise gem. To create a more personal user experience, a firstname
, lastname
and username
field have also been added. These fields are also stored as a string and are optional at the database level. Of these, the decision was made to make only the first name a required field. This has been enforced in the form view so the user cannot submit a request to sign up without entering their first name.
A book represents a book listing with Google Books. The user enters the book title to add the Google Book listing to the Bookly Book List. Books on this list can be selected by users to create an advert to list a book for sale. A user can also purchase a book from an advert created by another user.
A book has the attributes: ISBN
, title
, author
, description
and image_link
. The primary key is the book_id
. This data is sourced from Google Books and these fields all have the data type, string, to be consistent with the Google Books record. The ISBN
and title
are required fields as a book cannot be added without this information. The description
, image_link
and author
are optional.
An advert shows a book listed for sale by a user. The advert displays the book title, author, and description from Google Books. It also displays the condition and price set by the seller, with the seller’s contact email. The cover image is displayed from Google Books, unless the seller uploads their own image. The adverts appear in the Bookly Book Store.
An advert has the attributes of seller_id
, price
, condition
and book_id
. The primary key is the advert_id
. The seller_id
refers to the user_id
but has been renamed for clarity as a seller will post an advert. The seller_id
and book_id
are stored as integers. The price
is a decimal and the condition
is a string. All fields are required to create an advert and this in enforced in the application. A user must be signed in, they must select a book, they must enter a price, and the condition is set as a drop-down selection in the view. This prevents the user creating an advert record without the required information.
An order represents when a user opts to purchase a book from an advert listed by another user. The user selects the advert from the Bookly Book Store. Once a user places an order they are directed to Stripe to make payment. If payment is successful, the order is then complete.
The order primary key is order_id
, with the attributes advert_id
and buyer_id
, stored as integers. The buyer_id
again refers to the user_id
but has been renamed for clarity as a buyer will order a book. Both advert_id
and buyer_id
are required attributes. This is enforced in the application as a user must be signed in and then select an advert to place an order.
Defining relations within the database was a rather involved process as the development team sought to optimise use of data, avoiding any repetition where possible. As shown in the ERD, the following relationships were used.
- One mandatory primary key to many optional foreign keys (
seller_id
) in the Advert model- This defines the relationship between
users
andadverts
where the user has many adverts and adverts belong to user. - This can also facilitate the implementation of a future extension whereby users can view all the adverts they have posted.
- This defines the relationship between
- One mandatory primary key to many optional foreign keys (
buyer_id
) in the Order model- This defines the relationship between
users
andorders
where the user has many orders and orders belong to user. - By establishing this relationship, it becomes a trivial matter to access a user's orders should that be required.
- This defines the relationship between
- One mandatory primary key to many optional foreign keys (
book_id
) in the Advert model.- This defines the relationship between
books
andadverts
where a book has many adverts and adverts belong to a book. - By establishing this relationship, the application avoids duplication of data on the same book and a user can theoretically view all adverts of a book. As a result, the amount of database calls needed to perform an action are also minimised.
- This defines the relationship between
- One mandatory primary key to one mandatory foreign key (
advert_id
) in the Order model.- This relationship exists because an order must belong to an advert. Through this relationship, the Order model can use all information available to the Advert model and present that to the user.
- The only relationships in the Order model have already been covered above where its entities consist only of foreign keys referring to the
advert_id
andbuyer_id
.
Our project database consists of several models, these being Book
, Advert
, Order
, and User
.
The User
model was the first one that the team chose to implement. This was due to the fact that in previous projects, when it was implemented later, team members found that putting functions behind Devise was more work than just starting with it. This model's associations were has_many :advert
and the standard connections that Devise generated.
has_many :adverts
, the purpose of this connection was to allow a single user to create multiple adverts allowing them to advertise more than one book at a time.
The second model implemented was called Book
. This was because the team chose to link the application to Google Books and needed a model to locally store the data used for this application. This model was connected to the Advert
model through a has_many :adverts
association.
has_many :adverts
, this was due to the fact that multiple users could create an advert for the same book.
Next, the Advert
model was implemented. This model unlike Book
has associations to more than one table, these connections being belongs_to :book
and belongs_to :user
.
belongs_to :book
, this association was created because the team only wanted a user to be able to advertise one book per advert.belongs_to :user
, the purpose of this connection was to ensure that only one user (:seller
) would be attached to an advert. Only having one user attached to each advert allowed their contact information and name to be displayed easily. This also simplified the connection to the Stripe cloud service.
For the Order
model the team settled on two belongs_to
associations, one being to :adverts
the other being to :user
. The purpose of this model was to put the information for Stripe in a single model making its implementation much simpler.
belongs_to :adverts
, the connection to adverts enables the application to display the details of which advert the user has chosen to buy, making it quite simple to send that information to Stripe.belongs_to :user
, theuser
association was again designed to simplify the connection with Stripe.