There are three options for a quick setup:
- Run the following command from the projects root directory:
docker-compose up
orsudo docker-compose up
The database information is permanently stored at ./dockerdata via a docker volume. Which will be created by docker-compose if not readily available. Make sure to give docker-compose the necessary permissions (via sudo if necessary).
-
Clone repo.
-
Install dependencies as usual.
npm install
-
Move to the scripts directory
-
Build the database image using the Dockerfile at the scripts folder, i.e.:
docker build -t indipostgres .
-
Run the database container:
docker run --name {container_name} -p 5432:5432 -e POSTGRES_PASSWORD=mysecpass -d indipostgres
-
Start the server
npm start
Port and password must fit the values found in the config files found @ ./src/db/development.js and .src/db/test.js
{
"user": "postgres",
"password": "mysecpass",
"database": "indigitest",
"port": "5432",
"host": "localhost"
}
- Clone repo
- Install dependencies:
npm install as usual
- If your postgres database is not listening on the default port 5432, change the port at ./src/db/development.js and .src/db/test.js.
- Making sure that postgres is running, run the database intialization script:
./scripts/initAcreateDb.sh
Otherwise, make sure you create both databases as in the .sh file above. - Finally, run the server:
npm start
The project follows a layered architecture, where the applicattion core (domain) has the minimum possible outbound dependencies. As expected of a RESTful API, it is Event-Driven and part of a Client-Server distributed system.
Emphasis was put on controlling dependency-direction so that the domain layer is independent, and dependencies flow up from the infrastructure details (web server and database) to the domain model.
Please note how the service "user-actions" does not directly depend on the database. This was achieved by injecting the db into the service by means of a factory function @ /src/domain/user-service/index.js.
The dependency injection pattern has been used throughout the application. It allows to work separately on individual components on a test-driven basis (write tests first, then the code to fulfill the test assertions).
In a more object oriented fashion, this could also have been accomplished using typescript's interfaces.
The API has been defined according to the openAPI specification. You may inspect it at swaggerhub or take a look at the openAPI.yaml within the assets folder.
It comprises the following endpoints:
The universe of discourse for this test produces the following ORM Conceptual Schema.
Here, the most complex elementary fact is given by: "User(.id) befriends User(.id)", where the verb befriend was chosen to avoid a symmetric relationship such as "User(.id) is friend of User(.id)".
The circular symbol attached to the binary roles is an assymetry constraint, meaning if we insert the fact "User1 befriends User2", it will no longer be possible to insert "User2 befriends User1" until the former relationship is removed. It also implies irreflexivity preventing the insertion of facts such as "User1 befriends User1" ( i.e.: in our model, a user may not be his own friend).
Given the many to many relationship between users who befriend each other two tables are required.
Note how the assymetry condition on the friendship relation is being enforced via:
- irreflexive CONSTRAINT @ friendship table declaration
- enforce_Asymmetry TRIGGER
--create schema/database within psql
CREATE DATABASE indigitest;
--connect to the database schema
\c indigitest
--create tables and trigger
CREATE TABLE Users (
userId VARCHAR(25) NOT NULL PRIMARY KEY,
email VARCHAR(30) NOT NULL,
password VARCHAR(64) NOT NULL,
username VARCHAR(30) NOT NULL,
browserLang VARCHAR(10),
latitude FLOAT8,
longitude FLOAT8);
CREATE TABLE friendships(
befriender VARCHAR(25) NOT NULL references Users on delete cascade,
userId VARCHAR(25) NOT NULL references Users on delete cascade,
CONSTRAINT irreflexive check (befriender <> userId),
PRIMARY KEY (befriender, userId));
CREATE FUNCTION find_sym_trig() RETURNS trigger AS $find_sym_trig$
BEGIN
IF ( EXISTS(SELECT * FROM friendships WHERE befriender = NEW.userid AND userid = NEW.befriender) ) THEN
RAISE EXCEPTION 'friendship already stored as symmetric';
END IF;
RETURN NEW;
END;
$find_sym_trig$ LANGUAGE plpgsql;
CREATE TRIGGER enforce_asymmetry BEFORE INSERT ON friendships
FOR EACH ROW EXECUTE PROCEDURE find_sym_trig();
Note: The same is done for the database used for integration testing (indintdb), as can be seen in the intialization script @ ./src/scripts/initAcreateDb.sh .
This project was coded according to the TDD methodology in that test specifications almost always preceded the source-code implementation. The process started with the creation of a test specification for the user model and ended with the integration tests for the API (using supertest).
Test type | Tested Component(s)/System(s) | Test Spec |
---|---|---|
Unitary | User Model | user.spec.js |
Unitary | User Service | user-service.spec.js |
Unitary | User Controller | use-endpoint.spec.js |
Unitary | User Controller | friend-endpoint.spec.js |
Integration | Database Accessor/Interface, Database | user-db.spec.js |
Integration | whole API | integration.spec.js |
As a byproduct of this methodology high code coverage is achieved without having to put emphasis on it.