Quillia
2023-04-13.18-13-17.mp4
Get
Bootstrap
The app provides a docker-compose.yml
which
- Sets up a postgres server
- The models are coded to create tables and their schemas for you
- Starts our node app at port 8080
- NOTE: you unfortunately may have to set
ssl: false
, otherwise database may not run locally in the following files: - NOTE: you will still need a cloudinary account, otherwise covers won't show up
to configure you need a .env
file with the following:
# You can change these
PG_USER="hello"
PG_PASSWORD="1234"
PG_DB="user-db"
# These are hardcoded in the docker-compose.yml
PG_PORT=5432
PG_HOST="quillia-db-1"
# Cloudinary config
CLOUDINARY_API_KEY=""
CLOUDINARY_API_SECRET=""
docker compose up -d
Docker image
this will run a docker container with the app.
docker build -t quillia-dev .
docker run -p 3000:8080 quillia-dev # forward 8080 to 3000
Build
npm install
npm run build:fe # build the frontend
npm run build:be # build the backend
npm run build # runs both of the scripts
Server
npm start
Frontend
The frontend is built using , and
the frontend code is open for more improvements, but the current implementation can be found at ./public/src
Backend
The app is written in mostly safe TypeScript code, here is a breakdown of the codebase.
Routes
Each route maps to a file in ./src/routes/
, each file exports a single function that consumes a http.IncomingMessage
and a http.ServerResponse
message
Books.ts
Issue.ts
Login.ts
Signup.ts
Models
To store .epub
documents and their covers we use cloudinary, the functionality for this is implemented within ./src/models/BookModel.ts
The user, issues and books each have their own tables in a PostgreSQL database hosted at bit.io, each table corresponds to the name users
, issues
, books
. Each table is implemented separately in it's own model files.
BookModel.ts
IssueModel.ts
UserModel.ts
Miscellaneous
./src/common/utils.ts
: contains a utility functions (egsendJsonResponse
) that make our code cleaner and less repetetive./src/common/const.ts
: contains all constants, this includedPORT
,MIME
, APIERROR
blobs and even enviorment variables exported as contants (this is done to reduce the friction of moving a.env
file)./src/common/types.ts
: Contains types for our project
Potential features
Full credit goes to u/just-forest for the wonderful suggestions
- Save page position in the reader per user
- Dark mode toggle
- Option to hide books to users not logged in (alternatively, set it for each book like Memos does)
- Option to import books from a server folder
- Search
- Sort options (Alphabetical, latest, most issued)
- Sign out, delete account, delete books
- Upload book via direct epub url (eg. Guthenburg)
API
POST /api/login
Body
{
"email": "example@example.com",
"password": "password123"
}
Success
{
"messaged": "found the given user",
"status": 200,
"error": null,
"token": "JWT.TOKEN",
"data": {
"email": "example@example.com",
"id": "2ie9du9390"
}
}
Errors
{
"message": "unable to find user",
"status": 404,
"error": "user-not-found"
}
POST /api/signup
Body
{
"email": "example@example.com",
"password": "password123"
}
Success
{
"status": 201,
"message": "successfully created new user",
"error": null,
"token": "JWT.TOKEN",
"data": {
"email": "example@example.com"
"id": "29eiowiwofi"
}
}
Errors
{
"message": "the request was invalid",
"status": 400,
"error": "bad-request"
}
GET /api/books
Success
[
// ...
{
id: "8272f4b03a",
userid: "3701ced127",
author: "Captain Charles Johnson",
signature: "fd2ec14aa966f02641f1498578f8ad09",
title: "A General History of the Pirates",
cover:
"https://res.cloudinary.com/dbloby3uq/raw/upload/v1681303170/fd2ec14aa966f02641f1498578f8ad09.jpg",
},
// ...
];
POST /api/books
Headers
Authorization
: valid jwt tokenContent-type
:application/epub
Body
A single .epub
document less than 20 MB
Succces
{
"error": null,
"message": "successfully published a book of id a11a03a20c",
"data": {
"id": "a11a03a20c"
}
}
Error
{
"message": "the given credentials were invalid",
"status": 401,
"error": "unauthorized"
}
{
"message": "the mime recieved for the resource is not valid",
"status": 415,
"error": "invalid-mime-for-resource"
}
{
"message": "resource does not exist",
"status": 404,
"error": "resource-not-found"
}
DELETE /api/books
Headers
Authorization
: valid jwt tokenContent-type
:application/epub
Body
{
"bookid": "a valid bookid"
}
Succces
{
"error": null,
status: 404
"message": "successfully deleted book of id dwqoii329",
"data": {
"id": "dwqoii329"
}
}
Error
{
"message": "the given credentials were invalid",
"status": 401,
"error": "unauthorized"
}
{
"message": "the mime recieved for the resource is not valid",
"status": 415,
"error": "invalid-mime-for-resource"
}
{
"message": "was unable to delete book weqoiw0, perhaps the id was invalid?",
"status": 404,
"error": "unable-to-delete-book"
}
{
"message": "resource does not exist",
"status": 404,
"error": "resource-not-found"
}
GET /api/issues
Headers
Authorization
: a valid jwt token
Success
[
{
"id": "8d892f5772",
"lenderid": "a61dcba84c",
"borrowerid": "a61dcba84c",
"bookid": "a11a03a20c"
}
]
Error
{
"message": "resource does not exist",
"status": 404,
"error": "resource-not-found"
}
POST /api/issues
Headers
Authorization
: a valid jwt token
Body
{
lenderid: "a61dcba84c", // a user who owns the published book
bookid: "a11a03a20c"
}
Success
{
"error": null,
"message": "successfully created a new issue of id 8d892f5772",
"data": {
"id": "8d892f5772",
"borrower": "a61dcba84c",
"lender": "a61dcba84c",
"book": "The Almanack of Naval Ravikant: A Guide to Wealth and Happiness"
}
}
Error
{
"message": "the request was invalid",
"status": 400,
"error": "bad-request"
}
{
"message": "resource does not exist",
"status": 404,
"error": "resource-not-found"
}