Backend part for our catastrophe aid tool. Written in Go.
This project provides the backend for a platform connecting people in a region suffering from a catastrophe, e.g. a natural disaster. The frontend part can be found here. We develop this platform within the scope of one of our university courses, the Programmierpraktikum: Soziale Netzwerke.
1) You have to have a working Go installation on your system. Preferably via your system's package manager. Also, you need to have a working and running PostgreSQL + PostGIS instance. Therefore, make sure you have PostgreSQL installed and configured correctly and additionally install the PostGIS extension which enables spatial and geographic objects and functions in PostgreSQL.
2) Initially run
$ go get github.com/caTUstrophy/backend
which downloads the backend part of the project into your GOPATH.
3) Navigate to the project in your file system via
$ cd ${GOPATH}/src/github.com/caTUstrophy/backend
and execute
$ go get ./...
to fetch all dependencies of this project.
4) Create an .env
file suited to your deployment. For this, copy the provided .env.example
to .env
and edit it to your needs. Choose strong secret keys!
5) Create a Postgres database user, e.g. catustrophy
, and set a password for that user. Set up a Postgres database, e.g. catustrophy
, too. Then add these information to your just created environment .env
file. As above, have a look at the .env.example
for a description of which values you have to set.
6) Add PostGIS to your database. For that, become the postgres
user of your system and execute following commands, assuming you previously created the catustrophy
database:
user@system $ sudo -i -u postgres
postgres@system $ psql
psql (9.5.3)
Type "help" for help.
postgres=# \c catustrophy
You are now connected to database "catustrophy" as user "postgres".
catustrophy=# CREATE EXTENSION postgis;
catustrophy=# CREATE EXTENSION postgis_topology;
catustrophy=# CREATE EXTENSION fuzzystrmatch;
catustrophy=# CREATE EXTENSION postgis_tiger_geocoder;
catustrophy=# \q
postgres@system $ exit
7) Build the project via
$ go build
8a) If you are running the project the first time or after you dropped the database to start fresh, start the backend via
$ ./backend --init
This will create the tables and fill in some default needed content.
8b) Alternatively - and in the most common case - start it with
$ ./backend
Afterwards, the backend is reachable at http://localhost:3001
.
To be able to use this backend during development we provide a default admin account. When you initially run ./backend --init
, an admin user with the following credentials will be created:
Mail: admin@example.org
Password: CaTUstrophyAdmin123$
Please make sure you don't create this default admin user in production!
We implemented an algorithm that helps to recommend matches. It's aim is to find pairs of offers and requests that share similar content. If you are interested, you can read about our basic thoughts here. The document is not a complete report of what we have actually done, you will have to look this up in matching-algorithm.go
, but it might help you to get into the idea or inspire you for your own project.
Four roles are present in this model:
The coloumn Role
denotes the minimum needed privilege to use the endpoint.
Functionality | Role | HTTP verb | Endpoint | API version | Done? |
---|---|---|---|---|---|
Login | N | POST | /auth | MVP | ✔ |
Renew auth token | L | GET | /auth | MVP | ✔ |
Logout | L | DELETE | /auth | MVP | ✔ |
Create user | U | POST | /users | MVP | ✔ |
List users | A | GET | /users | 3.0 | ✔ |
Get user userID |
A | GET | /users/:userID | 3.0 | ✔ |
Update user userID |
A | PUT | /users/:userID | 3.0 | ✔ |
List tags | L | GET | /tags | 4.0 | ✔ |
Create offer | L | POST | /offers | MVP | ✔ |
Get offer offerID |
C | GET | /offers/:offerID | 2.0 | ✔ |
Update offer offerID |
C | PUT | /offers/:offerID | 3.0 | ✔ |
Create request | L | POST | /requests | MVP | ✔ |
Get request requestID |
C | GET | /requests/:requestID | 2.0 | ✔ |
Update request requestID |
C | PUT | /requests/:requestID | 3.0 | ✔ |
Create matching | A | POST | /matchings | MVP | ✔ |
Get matching matchingID |
C | GET | /matchings/:matchingID | MVP | ✔ |
Update matching matchingID |
C | PUT | /matchings/:matchingID | 3.0 | ✔ |
Create a region | L | POST | /regions | 2.0 | ✔ |
List regions | U | GET | /regions | 2.0 | ✔ |
Get region regionID |
U | GET | /regions/:regionID | 2.0 | ✔ |
Update region regionID |
A | PUT | /regions/:regionID | 3.0 | ✔ |
List offers in region regionID |
A | GET | /regions/:regionID/offers | 2.0 | ✔ |
List requests in region regionID |
A | GET | /regions/:regionID/requests | 2.0 | ✔ |
List matchings in region regionID |
A | GET | /regions/:regionID/matchings | 2.0 | ✔ |
List recommendations for region regionID |
A | GET | /regions/:regionID/recommendations | 4.0 | ✔ |
List recommendations for offer | A | GET | /regions/:ID/offers/:ID/recommendations | 4.0 | ✔ |
List recommendations for request | A | GET | /regions/:ID/requests/:ID/recommendations | 4.0 | ✔ |
Promote user to admin for region regionID |
A | POST | /regions/:regionID/admins | 3.0 | ✔ |
List admins for region regionID |
A | GET | /regions/:regionID/admins | 3.0 | ✔ |
Promote user to system admin | S | POST | /system/admins | 3.0 | ✔ |
List admins for system | A | GET | /system/admins | 3.0 | ✔ |
Own profile | L | GET | /me | 2.0 | ✔ |
Update own profile | L | PUT | /me | 3.0 | ✔ |
List own offers | L | GET | /me/offers | 2.0 | ✔ |
List own requests | L | GET | /me/requests | 2.0 | ✔ |
List own matchings | L | GET | /me/matchings | 3.0 | ✔ |
List unread notifications | L | GET | /notifications | 3.0 | ✔ |
Update notification notificationID |
C | PUT | /notifications/:notificationID | 3.0 | ✔ |
Inside a JWT issued by this backend, the following fields are present:
{
"iss": "ralollol@bernd.orgorg",
"iat": "2016-06-09T21:39:12+02:00",
"nbf": "2016-06-09T21:38:12+02:00",
"exp": "2016-06-09T22:09:12+02:00"
}
iss (issuer): Mail of user this token is issued to.
iat (issued at): Time and date when token was issued. RFC3339 date
nbf (not before): Token is to be discarded when used before this time and date. RFC3339 date
exp (expires): Token is to be discarded when used after this time and date. RFC3339 date
Please note that further identification fields may be added in the future.
If a request was not okay, we will always send one of the following responses:
400 Bad Request
{
"<FIELD NAME>": "<ERROR MESSAGE FOR THIS FIELD>"
}
401 Unauthorized
WWW-Authenticate: Bearer realm="CaTUstrophy", error="invalid_token", error_description="<ERROR DESCRIPTION>"
404 Not Found
{
"<FIELDNAME OR ERROR>": "<MORE INFORMATION ON WHAT WAS NOT FOUND>"
}
Request:
POST /auth
{
"Mail": required, string/email
"Password": required, string
}
Response:
200 OK
{
"AccessToken": string/jwt
}
Request:
GET /auth
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
200 OK
{
"AccessToken": string/jwt
}
Request:
DELETE /auth
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
200 OK
{
"ID": UUID v4
}
Request:
POST /users
{
"Name": required, string
"PreferredName": optional, string
"Mail": required, string/email
"PhoneNumbers": required, array of strings
"Password": required, string
}
Example:
Note that PhoneNumbers
can contain no, one or multiple phone numbers in string representation, but cannot be missing.
POST /users
{
"Name": "Alexandra Maria Namia",
"PreferredName": "alex",
"Mail": "alexandra.m.namia@example.com",
"PhoneNumbers": [
"012012312373",
"07791184228843",
"+9999230203920"
],
"Password": "WhyNotSafe1337Worlds?"
}
Response:
GET /users
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
userID
GET /users/:userID
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
userID
PUT /users/:userID
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
"Name": optional, string
"PreferredName": optional, string
"Mail": optional, string/email
"PhoneNumbers": optional, array of strings
"Password": optional, string
"Groups": optinal, [
{
"ID": required, UUID v4
},
...
]
}
Response:
GET /tags
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
Request:
POST /offers
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
"Name": required, string,
"Tags": optional, string array,
"Description": optional, string,
"ValidityPeriod": required, RFC3339 date,
"Location": {
"lng": float64,
"lat": float64
},
"Radius": required, float64, [km]
}
Example:
POST /offers
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
"Name": "hugs",
"Tags": ["tag", "another tag"],
"ValidityPeriod": "2017-11-01T22:08:41+00:00",
"Location": {
"lng": 12.3,
"lat": 0.0
}
"Radius": 10
}
Response:
offerID
Request:
GET /offers/:offerID
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
offerID
Request:
PUT /offers/:offerID
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
}
Response:
Request:
POST /requests
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
"Name": required, string,
"Tags": optional, string array,
"ValidityPeriod": required, RFC3339 date,
"Location": {
"lng": required, float64,
"lat": required, float64
},
"Radius": required, float64, [km]
}
Response:
requestID
Request:
GET /requests/:requestID
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
requestID
Request:
PUT /requests/:requestID
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
}
Response:
Request:
POST /matchings
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
"Region": required, UUID v4,
"Request": required, UUID v4,
"Offer": required, UUID v4
}
Response:
matchingID
Request:
GET /matchings/:matchingID
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
matchingID
Request:
PUT /matchings/:matchingID
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
"Invalid": true
}
Response:
Request:
POST /regions
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
"Name": required, string,
"Description": required, string,
"Boundaries": {
"Points": [
{
"lat": required, float64,
"lng": required, float64
},
...
]
}
}
Response:
Request:
GET /regions
Response:
regionID
Request:
GET /regions/:regionID
Response:
regionID
PUT /regions/:regionID
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
"Name": required, string,
"Description": required, string,
"Boundaries": {
"Points": [
{
"lat": required, float64,
"lng": required, float64
},
...
]
}
}
Response:
regionID
Request:
GET /regions/:regionID/offers
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
regionID
Request:
GET region/:regionID/requests/
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
regionID
Request:
GET /regions/:regionID/matchings
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
Request:
GET /regions/:regionID/recommendations
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response: Match partner list
Request:
GET /regions/:ID/offers/:ID/recommendations
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response: Request list with matching score
Request:
GET /regions/:ID/requests/:ID/recommendations
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response: Offer list with matching score
regionID
Request:
POST /regions/:regionID/admins
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
"Mail": required, string
}
Response:
regionID
Request
GET /regions/:regionID/admins
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
List of users without their groups
Request:
POST /system/admins
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
"Mail": required, string
}
Response:
Request
GET /system/admins
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
List of users without their groups
Request:
GET /me
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
Request:
PUT /me
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
"Name": optional, string,
"PreferredName": optional, string,
"Mail": optional, string/email,
"PhoneNumbers": optional, array of strings,
"Password": optional, string
}
Response:
Request:
GET /me/offers
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
Request:
GET /me/requests
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
Request:
GET /me/matchings
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
Request:
GET /notifications
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
Response:
or in case that notifications for matchings are included:
List of matching notifications
notificationID
Request:
PUT /notifications/:notificationID
Authorization: Bearer <USER'S ACCESS TOKEN AS JWT>
{
"Read": true
}
Response:
or in case that this specific notification is meant for a matching:
{
"Groups": [
{
"AccessRight": "string",
"Description": "string",
"ID": "UUID v4",
"Region": {
"Description": "string",
"ID": "UUID v4",
"Name": "string"
}
}
],
"ID": "UUID v4",
"Mail": "string",
"MailVerified": "bool",
"Name": "string",
"PhoneNumbers": "[string, ...]",
"PreferredName": "string"
}
[
{
"Groups": [
{
"AccessRight": "string",
"Description": "string",
"ID": "UUID v4",
"Region": {
"Description": "string",
"ID": "UUID v4",
"Name": "string"
}
}
],
"ID": "UUID v4",
"Mail": "string",
"MailVerified": "bool",
"Name": "string",
"PhoneNumbers": "[string, ...]",
"PreferredName": "string"
}
]
{
"ID": "UUID v4",
"Mail": "string",
"MailVerified": "bool",
"Name": "string",
"PhoneNumbers": "[string, ...]"
}
[
{
"ID": "UUID v4",
"Mail": "string",
"MailVerified": "bool",
"Name": "string",
"PhoneNumbers": "[string, ...]"
}
]
[
{
"Name": "string"
}
]
{
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"MatchingScore": "float64",
"Name": "string",
"Radius": "float64",
"Recommended": "bool",
"Tags": [
{
"Name": "string"
}
],
"ValidityPeriod": "RFC3339 date"
}
[
{
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"MatchingScore": "float64",
"Name": "string",
"Radius": "float64",
"Recommended": "bool",
"Tags": [
{
"Name": "string"
}
],
"ValidityPeriod": "RFC3339 date"
}
]
{
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"MatchingScore": "float64",
"Name": "string",
"Radius": "float64",
"Recommended": "bool",
"Tags": [
{
"Name": "string"
}
],
"ValidityPeriod": "RFC3339 date"
}
[
{
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"MatchingScore": "float64",
"Name": "string",
"Radius": "float64",
"Recommended": "bool",
"Tags": [
{
"Name": "string"
}
],
"ValidityPeriod": "RFC3339 date"
}
]
{
"ID": "UUID v4",
"Invalid": "bool",
"Offer": {
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"Name": "string",
"Radius": "float64",
"Tags": [
null
],
"User": {
"ID": "UUID v4",
"Mail": "string",
"Name": "string",
"PhoneNumbers": "[string, ...]"
},
"ValidityPeriod": "RFC3339 date"
},
"RegionId": "UUID v4",
"Request": {
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"Name": "string",
"Radius": "float64",
"Tags": [
null
],
"User": {
"ID": "UUID v4",
"Mail": "string",
"Name": "string",
"PhoneNumbers": "[string, ...]"
},
"ValidityPeriod": "RFC3339 date"
}
}
[
{
"ID": "UUID v4",
"Invalid": "bool",
"Offer": {
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"Name": "string",
"Radius": "float64",
"Tags": [
null
],
"User": {
"ID": "UUID v4",
"Mail": "string",
"Name": "string",
"PhoneNumbers": "[string, ...]"
},
"ValidityPeriod": "RFC3339 date"
},
"RegionId": "UUID v4",
"Request": {
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"Name": "string",
"Radius": "float64",
"Tags": [
null
],
"User": {
"ID": "UUID v4",
"Mail": "string",
"Name": "string",
"PhoneNumbers": "[string, ...]"
},
"ValidityPeriod": "RFC3339 date"
}
}
]
{
"Boundaries": {
"Points": [
{
"lat": "float64",
"lng": "float64"
}
]
},
"Description": "string",
"ID": "UUID v4",
"Name": "string"
}
[
{
"Boundaries": {
"Points": [
{
"lat": "float64",
"lng": "float64"
}
]
},
"Description": "string",
"ID": "UUID v4",
"Name": "string"
}
]
{
"ID": "UUID v4",
"ItemID": "string",
"Type": "string"
}
{
"ID": "UUID v4",
"ItemID": "string",
"Matching": {
"ID": "UUID v4",
"Invalid": "bool",
"Offer": {
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"Name": "string",
"Radius": "float64",
"Tags": [
null
],
"User": {
"ID": "UUID v4",
"Mail": "string",
"Name": "string",
"PhoneNumbers": "[string, ...]"
},
"ValidityPeriod": "RFC3339 date"
},
"RegionId": "UUID v4",
"Request": {
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"Name": "string",
"Radius": "float64",
"Tags": [
null
],
"User": {
"ID": "UUID v4",
"Mail": "string",
"Name": "string",
"PhoneNumbers": "[string, ...]"
},
"ValidityPeriod": "RFC3339 date"
}
},
"Type": "string"
}
[
{
"ID": "UUID v4",
"ItemID": "string",
"Type": "string"
}
]
[
{
"ID": "UUID v4",
"ItemID": "string",
"Matching": {
"ID": "UUID v4",
"Invalid": "bool",
"Offer": {
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"Name": "string",
"Radius": "float64",
"Tags": [
null
],
"User": {
"ID": "UUID v4",
"Mail": "string",
"Name": "string",
"PhoneNumbers": "[string, ...]"
},
"ValidityPeriod": "RFC3339 date"
},
"RegionId": "UUID v4",
"Request": {
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"Name": "string",
"Radius": "float64",
"Tags": [
null
],
"User": {
"ID": "UUID v4",
"Mail": "string",
"Name": "string",
"PhoneNumbers": "[string, ...]"
},
"ValidityPeriod": "RFC3339 date"
}
},
"Type": "string"
}
]
[
{
"MatchingScore": "float64",
"Offer": {
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"Name": "string",
"Radius": "float64",
"Tags": [
{
"Name": "string"
}
],
"ValidityPeriod": "RFC3339 date"
},
"Recommended": "bool",
"Region": {
"Boundaries": {
"Points": [
null
]
},
"Description": "string",
"ID": "UUID v4",
"Name": "string"
},
"Request": {
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"Name": "string",
"Radius": "float64",
"Tags": [
{
"Name": "string"
}
],
"ValidityPeriod": "RFC3339 date"
}
}
]
[
{
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"MatchingScore": "float64",
"Name": "string",
"Radius": "float64",
"Recommended": "bool",
"Tags": [
{
"Name": "string"
}
],
"ValidityPeriod": "RFC3339 date"
}
]
[
{
"Description": "string",
"Expired": "bool",
"ID": "UUID v4",
"Location": {
"lat": "float64",
"lng": "float64"
},
"Matched": "bool",
"MatchingScore": "float64",
"Name": "string",
"Radius": "float64",
"Recommended": "bool",
"Tags": [
{
"Name": "string"
}
],
"ValidityPeriod": "RFC3339 date"
}
]
We would like to thank all third-party packages we are using in this project! The golang community is incredible.
A probably incomplete list of used packages looks like:
And of course we make heavy use of a lot of golang standard packages.
This project is licensed under GPLv3.