Fully document API and move dev setup instructions

This commit is contained in:
Tanner Collin 2020-01-21 23:49:01 +00:00
parent 15f736809d
commit 0a13f52053
6 changed files with 1092 additions and 144 deletions

135
README.md
View File

@ -6,140 +6,7 @@ Demo: https://spaceport.dns.t0.vc
## Development Setup
Install dependencies:
```text
# Python:
$ sudo apt update
$ sudo apt install python3 python3-pip python-virtualenv python3-virtualenv
# Yarn / nodejs:
# from https://yarnpkg.com/lang/en/docs/install/#debian-stable
$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
$ sudo apt update
$ sudo apt install yarn
```
Clone this repo:
```text
$ git clone https://github.com/Protospace/spaceport.git
$ cd spaceport
```
### API Server
Create a venv, activate it, and install:
```text
$ cd apiserver
$ virtualenv -p python3 env
$ source env/bin/activate
(env) $ pip install -r requirements.txt
```
Now setup Django and run it:
```text
(env) $ python manage.py migrate --run-syncdb
(env) $ python manage.py createsuperuser --email admin@example.com --username admin
(env) $ DEBUG=true python manage.py runserver 0.0.0.0:8002
```
Django will now be running on port 8002, connect to localhost:8002 to test it.
#### Import Old Portal Data
Place `old_portal.sqlite3` in the same directory as `manage.py`.
```text
(env) $ bash gen_old_models.sh
(env) $ time python import_old_portal.py
```
Give it about 15 minutes to run. This will import old models into the new portal database, ready to be linked to user's emails when they sign up.
#### Testing
There are unit tests in `apiserver/api/tests.py` that you can run with:
```text
(env) $ python manage.py test
```
### Webclient
```text
# In a different terminal
$ cd webclient
$ yarn install
$ yarn start
```
The webclient will now be running on port 3000. Make changes and refresh to see them.
### Reverse Proxy
It's easiest to point a domain to the server and reverse proxy requests according to subdomain. If you don't set up a reverse proxy, you'll need to change URL settings.
Domains: `example.com`, `api.example.com`, `static.example.com` should all be reverse proxied.
Configure nginx:
```text
server {
listen 80;
root /var/www/html;
index index.html index.htm;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
root /var/www/html;
index index.html index.htm;
server_name api.example.com;
client_max_body_size 20M;
location / {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Headers' 'content-type, authorization' always;
add_header 'Access-Control-Allow-Methods' 'HEAD,GET,POST,PUT,PATCH,DELETE' always;
add_header 'Access-Control-Max-Age' '86400' always;
proxy_pass http://127.0.0.1:8002/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
root /home/you/spaceport/apiserver/data/static;
index index.html;
server_name static.example.com;
location / {
add_header 'cache-control' 'max-age=2678400' always;
try_files $uri $uri/ =404;
}
}
```
https://docs.spaceport.dns.t0.vc/dev.html
## License

View File

@ -0,0 +1,825 @@
API
===
.. contents:: :depth: 3
User
----
Registration
++++++++++++
.. http:post:: /registration/
Register an account on Spaceport. Only works from Protospace's IP addresses.
:json first_name:
:json last_name:
:json username: Should be ``first.last``
:json password1:
:json password2:
:json email:
:json boolean existing_member: If ``true``, will link old portal objects based
on email match.
**Example response**:
.. sourcecode:: json
{"key":"1fb8ef73f118c5de1f9ba4939a76b3f3b0bc7444"}
Login
+++++
.. http:post:: /rest-auth/login/
Log in with username and password, responds with auth token.
Put the token in the ``Authorization`` HTTP header like ``Token <token>`` to
authenticate.
:json username:
:json password:
**Example request**:
.. sourcecode:: bash
$ curl \
-d '{"username":"tanner.tester", "password":"supersecret"}' \
-H "Content-Type: application/json" \
-X POST \
http://api.spaceport.dns.t0.vc/rest-auth/login/
**Example response**:
.. sourcecode:: json
{"key":"1fb8ef73f118c5de1f9ba4939a76b3f3b0bc7444"}
Full User
+++++++++
.. http:get:: /user/
Retrieve an object with complete user data.
:requestheader Authorization: ``Token <token>``
**Example request**:
.. sourcecode:: bash
$ curl \
-H "Authorization: Token 1fb8ef73f118c5de1f9ba4939a76b3f3b0bc7444" \
https://api.spaceport.dns.t0.vc/user/
**Example response**:
.. sourcecode:: json
{
"id": 113,
"username": "tanner.tester",
"member": {
"id": 1685,
"status": "Current",
"email": "text",
"phone": "text",
"street_address": "text",
"city": "text",
"postal_code": "text",
"old_email": "text",
"photo_large": "uuid.jpg",
"photo_medium": "uuid.jpg",
"photo_small": "uuid.jpg",
"set_details": true,
"first_name": "Tanner",
"last_name": "Collin",
"preferred_name": "Tanner",
"emergency_contact_name": "text",
"emergency_contact_phone": "text",
"birthdate": null,
"is_minor": false,
"guardian_name": "",
"is_director": false,
"is_staff": true,
"is_instructor": false,
"expire_date": "2020-01-23",
"current_start_date": "2016-08-23",
"application_date": "2016-08-23",
"vetted_date": "2016-09-27",
"paused_date": null,
"monthly_fees": 50,
"user": 113
},
"transactions": [
{
"id": 31783,
"account_type": "Paypal",
"info_source": "PayPal IPN",
"member_name": "Tanner Collin",
"date": "2019-12-22",
"member_id": 1685,
"amount": "50.00",
"reference_number": "text",
"memo": "text",
"number_of_membership_months": null,
"payment_method": null,
"category": "Memberships:Paypal Payments",
"user": 113,
"recorder": null
}
],
"cards": [
{
"id": 392,
"member_id": 1685,
"card_number": "text",
"notes": "Tanner Collin",
"last_seen_at": "2020-01-20",
"active_status": "card_active",
"user": 113
}
],
"training": [
{
"id": 971,
"session": {
"id": 11073,
"student_count": 20,
"course_name": "Metal: Metal Cutting &amp; Manual Lathe",
"instructor_name": "John W",
"datetime": "2016-09-17T16:00:00Z",
"course": 281,
"is_cancelled": false,
"old_instructor": "John W",
"cost": "10.00",
"max_students": null,
"instructor": null
},
"member_id": 1685,
"attendance_status": "confirmed",
"sign_up_date": null,
"paid_date": null
}
],
"is_staff": true
}
:json is_staff: Set in Django's admin panel. You'll need to set this for the
first user so that you can assign more admins.
:json member.is_staff: Set by directors / staff in UI.
Change Password
+++++++++++++++
.. http:post:: /password/change/
:json old_password:
:json password1:
:json password2:
**Example response**:
.. sourcecode:: json
{"detail":"New password has been saved."}
Members
-------
Member Details
++++++++++++++
.. http:get:: /members/(id)/
Retrieve an object with member details. Users can only view themselves,
admins can view anyone.
:param id:
:requestheader Authorization: ``Token <token>``
**Example request**:
.. sourcecode:: bash
$ curl \
-H "Authorization: Token 1fb8ef73f118c5de1f9ba4939a76b3f3b0bc7444" \
https://api.spaceport.dns.t0.vc/members/1685/
**Example response**:
.. sourcecode:: json
{
"id": 1685,
"status": "Current",
"email": "text",
"phone": "text",
"street_address": "text",
"city": "text",
"postal_code": "text",
"old_email": "text",
"photo_large": "uuid.jpg",
"photo_medium": "uuid.jpg",
"photo_small": "uuid.jpg",
"set_details": true,
"first_name": "Tanner",
"last_name": "Collin",
"preferred_name": "Tanner",
"emergency_contact_name": "text",
"emergency_contact_phone": "text",
"birthdate": null,
"is_minor": false,
"guardian_name": "",
"is_director": false,
"is_staff": false,
"is_instructor": false,
"expire_date": "2020-01-23",
"current_start_date": "2016-08-23",
"application_date": "2016-08-23",
"vetted_date": "2016-09-27",
"paused_date": null,
"monthly_fees": 50,
"user": 113
}
:json member.old_email: From old portal import, used to claim member when
registering.
:json \*.member_id: From old portal import, used as a hint to link the
object to users when they claim their old member.
:json photo\_\*: Should be served by nginx on the ``static`` subdomain. Refers
to photo filenames in the ``apiserver/data/static`` directory.
:json status: Derived by subtracting today's date from expire_date. More
than one month: Prepaid, less than one month: Current, less than one
month behind: Due, more than one month behind: Overdue. Members more
than three months behind are paused. Value stored to make searching
faster.
:json expire_date: Derived by summing all member transaction's
number_of_membership_months and adding to member's current_start_date.
Value stored to make searching faster.
.. http:post:: /members/
Not allowed. Object is created upon registration.
Edit Member Details
+++++++++++++++++++
.. http:patch:: /members/(id)/
Set member details.
**Users**
Can only set certain fields of their own member.
:form photo: A member photo that will be turned into different sizes and
referred to by photo_large, photo_medium, photo_small.
:json email:
:json phone:
:json street_address:
:json city:
:json postal_code:
:json boolean set_details: Set true if they've filled out the new member
form on sign up so the UI stops showing it.
:json first_name:
:json last_name:
:json preferred_name: What's shown throughout the UI.
:json emergency_contact_name: optional
:json emergency_contact_phone: optional
:json birthdate: optional, YYYY-MM-DD
:json boolean is_minor:
:json guardian_name: optional
**Admins**
Can modify any member. Above fields, plus:
:json boolean is_director: Grants admin privileges to member.
:json boolean is_staff: Same as director, just not named one.
:json boolean is_instructor: Able to create and edit courses and sessions.
:json application_date: When they applied to Protospace, YYYY-MM-DD.
:json current_start_date: When to start counting their membership dues from.
Would only differ from application_date for accounting reasons, YYYY-MM-DD.
:json vetted_date: YYYY-MM-DD
:json monthly_fees: for display only
:requestheader Authorization: ``Token <token>``
**Response**
Same as GET.
.. http:put:: /members/(id)/
Same as PATCH but requires all fields present.
Pausing / Unpausing Member
++++++++++++++++++++++++++
.. http:post:: /members/(id)/pause/
/members/(id)/unpause/
Pause or unpause a membership. Can only be done by admins.
Pausing a member sets their paused_date to today. Their cards aren't sent to
the door controller. Their expire_date and status won't be evaluated daily
any longer.
Unpausing a member sets their current_start_date to their paused_date. Their
paused_date is then set to null. They will be Due. Their active cards will
begin working again.
:param id:
:requestheader Authorization: ``Token <token>``
**Response**
:status 200:
Search
------
Searching
+++++++++
.. http:post:: /search/
Perform a search for members' names.
Exact prefix matches are returned first, then exact substring matches, then
fuzzy matches.
POST is used because our auth header causes a pre-flight request. These
can't be cached if the URL keeps changing like with query params. Using the
request body for the query prevents an OPTIONS request per keystroke.
Designed to be fast enough for incremental search.
An empty search returns the most recently vetted members.
:json q: The search query.
:json int seq: An integer that gets returned with the search results.
Useful to prevent responses that arrive out-of-order from being
displayed as search results. ``event.timeStamp()`` is a good value to use.
:requestheader Authorization: ``Token <token>``
**Example response**:
.. sourcecode:: json
{
"seq": 12345,
"results": [
{
"member": {
"id": 1685,
"preferred_name": "Tanner",
"last_name": "Collin",
"status": "Current",
"current_start_date": "2016-08-23",
"photo_small": "uuid.jpg",
"photo_large": "uuid.jpg"
}
},
{
"member": {
"id": 1993,
"preferred_name": "Tanner",
"last_name": "text",
"status": "Former Member",
"current_start_date": null,
"photo_small": null,
"photo_large": null
}
}
]
}
Search Result
+++++++++++++
.. http:get:: /search/(id)/
Returns a specific search result. Users can see a partial member object. Admins can see the full member, cards, and transactions.
:param id:
:requestheader Authorization: ``Token <token>``
**Example user response**:
.. sourcecode:: json
{
"member": {
"id": 1685,
"preferred_name": "Tanner",
"last_name": "Collin",
"status": "Current",
"current_start_date": "2016-08-23",
"photo_small": "uuid.jpg",
"photo_large": "uuid.jpg"
}
}
**Example admin response**:
Truncated.
.. sourcecode:: json
{
"member": {},
"cards": [],
"transactions": [],
}
Transactions
------------
Transaction Details
+++++++++++++++++++
.. http:get:: /transactions/(id)/
Retrieve a transaction. Users can only view their own. Admins can view
anyone's.
:param id:
:requestheader Authorization: ``Token <token>``
**Example user response**:
.. sourcecode:: json
{
"id": 9320,
"account_type": "PayPal",
"info_source": "Paypal IPN",
"member_id": 1685,
"member_name": "Tanner Collin",
"date": "2017-01-30",
"amount": "50.00",
"reference_number": "2DS184750R9",
"memo": "1685, email",
"number_of_membership_months": null,
"payment_method": null,
"category": "Memberships:Paypal Payments",
"user": 113,
"recorder": null
}
Create Transaction
++++++++++++++++++
.. http:post:: /transaction/
Add a transaction to a member. Admins only.
:json date: YYYY-MM-DD
:json decimal amount: positive is money going to Protospace, XX.XX.
:json account_type: One of: ``Interac``, ``TD Chequing``, ``Paypal``, ``Dream Pmt``,
``PayPal``, ``Square Pmt``, ``Member``, ``Clearing``, ``Cash``
:json info_source: One of: ``Web``, ``DB Edit``, ``System``, ``Receipt or Stmt``, ``Quicken
Import``, ``Paypal IPN``, ``PayPal IPN``, ``Auto``, ``Nexus DB Bulk``, ``IPN Trigger``,
``Intranet Receipt``, ``Automatic``, ``Manual``
:json number_of_membership_months: Used when calculating member status and
expire date, optional.
:json reference_number: optional
:json memo: optional
:json payment_method: optional
:json category: optional
:requestheader Authorization: ``Token <token>``
**Response**
Same as GET.
Edit Transaction
++++++++++++++++++
.. http:patch:: /transactions/(id)
Same fields as POST. Admins only.
:param id: The transaction's ID.
:requestheader Authorization: ``Token <token>``
.. http:put:: /transactions/(id)/
Same as PATCH but requires all fields present.
Courses
-------
.. http:get:: /courses/
List of all courses, ordered by which has most upcoming session.
Truncated.
.. sourcecode:: json
{
"count": 59,
"next": null,
"previous": null,
"results": [
{
"id": 261,
"name": "Woodworking Tools 1: Intro to Saws"
},
{
"id": 321,
"name": "Laser: Trotec Course"
}
]
}
.. http:get:: /courses/(id)/
:param id: The course's ID.
.. sourcecode:: json
{
"id": 417,
"sessions": [
{
"id": 12375,
"student_count": 11,
"course_name": "HAM Radio Introduction",
"instructor_name": "Pat S",
"datetime": "2019-01-24T02:00:00Z",
"course": 417,
"is_cancelled": false,
"old_instructor": "Pat S",
"cost": "0.00",
"max_students": null,
"instructor": null
}
],
"name": "HAM Radio Introduction",
"description": "text",
"is_old": true
}
:json boolean is_old: True if imported from old portal.
:json description: Text separated by \\n if is_old, otherwise HTML.
.. http:post:: /courses/
.. http:put:: /courses/(id)/
.. http:patch:: /courses/(id)/
Instructors and admins only.
:param id: The course's ID.
:json name:
:json boolean is_old:
:json description:
:requestheader Authorization: ``Token <token>``
Sessions
--------
Classes are called sessions in the API because of old portal models
and "class" keyword conflict.
A session (class) belongs to a course and has a specific date, time,
instructor, and cost.
.. http:get:: /sessions/
List of the 20 next sessions.
Truncated.
.. sourcecode:: json
{
"count": 20,
"next": null,
"previous": null,
"results": [
{
"id": 13476,
"student_count": 0,
"course_name": "CAD: Introduction to 3D CAD (Fusion)",
"instructor_name": "Mike M",
"datetime": "2020-01-18T16:30:00Z",
"course": 253,
"is_cancelled": false,
"old_instructor": "Mike M",
"cost": "0.00",
"max_students": null,
"instructor": null
}
]
}
:json student_count: Number of students registered, excluding withdrawn.
.. http:get:: /sessions/(id)/
:param id: The course's ID.
.. sourcecode:: json
{
"id": 13476,
"student_count": 0,
"course_name": "CAD: Introduction to 3D CAD (Fusion)",
"instructor_name": "Mike M",
"datetime": "2020-01-18T16:30:00Z",
"course": 253,
"students": [],
"is_cancelled": false,
"old_instructor": "Mike M",
"cost": "0.00",
"max_students": null,
"instructor": null
}
.. http:post:: /sessions/
.. http:put:: /sessions/(id)/
.. http:patch:: /sessions/(id)/
Instructors and admins only.
:param id: The session's ID.
:json datetime: UTC ISO 8601, YYYY-MM-DDTHH:MM:SSZ
:json int course: ID of the course it belongs to.
:json boolean is_cancelled: Only for display.
:json decimal cost: 0 if free.
:json int max_students: optional
:requestheader Authorization: ``Token <token>``
Training
--------
A training object is created when a member registers for a session (class).
.. http:get:: /training/(id)/
Retrieve a training object. Users can only view their own. Instructors can
view their students'. Admins can view anyone's.
:param id: The training object's ID.
:requestheader Authorization: ``Token <token>``
.. sourcecode:: json
{
"id": 971,
"attendance_status": "confirmed",
"session": 11073,
"student_name": "Tanner Collin",
"member_id": 1685,
"sign_up_date": null,
"paid_date": null,
"user": 113
}
.. http:post:: /training/
Register for a session (class).
**Users**
:json attendance_status: One of: ``waiting for payment``, ``withdrawn``
:json int session: The session (class) to register for.
**Instructors and Admins**
:json attendance_status: One of: ``waiting for payment``, ``withdrawn``,
``rescheduled``, ``no-show``, ``attended``, ``confirmed``
:json int session: The session (class) to register for.
:requestheader Authorization: ``Token <token>``
.. http:put:: /training/(id)/
.. http:patch:: /training/(id)/
Edit attendance status.
Same params as POST.
:requestheader Authorization: ``Token <token>``
Cards
-----
Cards are sent to Protospace's door controllers to grant access to the
building. Only active cards of unpaused members are sent.
.. http:get:: /cards/(id)/
Retrieve a card. Users can only view their own, admins can view anyone's.
:param id: The card object's ID.
:requestheader Authorization: ``Token <token>``
.. sourcecode:: json
{
"id": 392,
"card_number": "text",
"member_id": 1685,
"active_status": "card_active",
"notes": "Tanner Collin",
"last_seen_at": "2020-01-20",
"user": 113
}
.. http:post:: /card/
.. http:put:: /card/(id)/
.. http:patch:: /card/(id)/
.. http:delete:: /card/(id)/
Admins only. Don't change the status when pausing a member, paused member's
cards are filtered out automatically.
:param id: The card object's ID.
:json card_number: Usually a 10 character hex string.
:json int member_id: Which member the card belongs to.
:json active_status: One of: ``card_blocked``, ``card_inactive``,
``card_member_blocked``, ``card_active``
:json notes: optional
:requestheader Authorization: ``Token <token>``
Door
----
Public route that the door controllers should poll for a list of cards
allowed to scan into the building.
.. http:get:: /door/
List all active cards of unpaused members.
The json dict format is to match the current front door controller's script
and will likely be changed in the future.
No authentication required.
**Example response**
Truncated.
.. sourcecode:: json
{
"0000001234": {
"name": "Tanner C",
"id": 1685,
"enabled": true
},
"000000ABCD": {
"name": "Tanner C",
"id": 1685,
"enabled": true
}
}
:json key: The dict keys are the card numbers.
:json int id: Member's ID.
:json name: Member's name.
:json boolean enabled: Always true.
.. http:post:: /door/(card_number)/seen/
Update card's last_seen_at to today.
This doesn't do any fancy logging yet.
:param card_number: Usually a 10 character hex string.
No authentication required.

View File

@ -0,0 +1,78 @@
API Overview
============
The current API URL is: https://api.spaceport.dns.t0.vc/
The Spaceport API uses REST. JSON is returned by all API responses including
errors and HTTP response status codes are to designate success and failure.
Request bodies can be JSON or form data.
API Routes
----------
All API routes require a trailing slash. This is a Django default and you'll get
a 301 redirect if you forget it.
Authentication
--------------
All API routes except for ``/door/`` require authentication with a token. The
token is returned on registration and login. The token needs to be placed in the
``Authorization`` request header like this: ``Token <token>``.
**Example**
Login response:
.. sourcecode:: json
{"key":"1fb8ef73f118c5de1f9ba4939a76b3f3b0bc7444"}
Add the following header to requests:
.. sourcecode:: text
Authorization: Token 1fb8ef73f118c5de1f9ba4939a76b3f3b0bc7444
Quick Reference
---------------
.. http:post:: /registration/
.. http:post:: /rest-auth/login/
.. http:get:: /user/
.. http:post:: /password/change/
.. http:get:: /members/(id)/
.. http:post:: /members/
.. http:patch:: /members/(id)/
.. http:put:: /members/(id)/
.. http:post:: /members/(id)/pause/
.. http:post:: /members/(id)/unpause/
.. http:post:: /search/
.. http:get:: /search/(id)/
.. http:get:: /transactions/(id)/
.. http:post:: /transaction/
.. http:patch:: /transactions/(id)
.. http:put:: /transactions/(id)/
.. http:get:: /courses/
.. http:get:: /courses/(id)/
.. http:post:: /courses/
.. http:put:: /courses/(id)/
.. http:patch:: /courses/(id)/
.. http:get:: /sessions/
.. http:get:: /sessions/(id)/
.. http:post:: /sessions/
.. http:put:: /sessions/(id)/
.. http:patch:: /sessions/(id)/
.. http:get:: /training/(id)/
.. http:post:: /training/
.. http:put:: /training/(id)/
.. http:patch:: /training/(id)/
.. http:get:: /cards/(id)/
.. http:post:: /card/
.. http:put:: /card/(id)/
.. http:patch:: /card/(id)/
.. http:delete:: /card/(id)/
.. http:get:: /door/
.. http:post:: /door/(card_number)/seen/

View File

@ -29,6 +29,7 @@ author = 'Tanner Collin'
# ones.
extensions = [
'sphinx_rtd_theme',
'sphinxcontrib.httpdomain',
]
# Add any paths that contain templates here, relative to this directory.
@ -51,3 +52,4 @@ html_theme = 'sphinx_rtd_theme'
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html4_writer = True

View File

@ -0,0 +1,181 @@
Development Setup
=================
.. contents:: :depth: 3
This guide assumes you are on a Debian-based distro.
Install dependencies:
.. sourcecode:: bash
# Python:
$ sudo apt update
$ sudo apt install python3 python3-pip python-virtualenv python3-virtualenv
# Yarn / nodejs:
# from https://yarnpkg.com/lang/en/docs/install/#debian-stable
$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
$ sudo apt update
$ sudo apt install yarn
Clone the repo:
.. sourcecode:: bash
$ git clone https://github.com/Protospace/spaceport.git
$ cd spaceport
API Server
----------
Create a venv, activate it, and install:
.. sourcecode:: bash
$ cd apiserver
$ virtualenv -p python3 env
$ source env/bin/activate
(env) $ pip install -r requirements.txt
Now setup Django and run it:
.. sourcecode:: bash
(env) $ python manage.py migrate --run-syncdb
(env) $ python manage.py createsuperuser --email admin@example.com --username admin
(env) $ DEBUG=true python manage.py runserver 0.0.0.0:8002
Django will now be running on port 8002, connect to localhost:8002 to test it.
Import Old Portal Data
++++++++++++++++++++++
Place `old_portal.sqlite3` in the same directory as `manage.py`.
.. sourcecode:: bash
(env) $ bash gen_old_models.sh
(env) $ time python import_old_portal.py
Give it about 15 minutes to run. This will import old models into the new portal database, ready to be linked to user's emails when they sign up.
Testing
+++++++
There are unit tests in `apiserver/api/tests.py` that you can run with:
.. sourcecode:: bash
(env) $ python manage.py test
Documentation
+++++++++++++
Compile this documentation:
.. sourcecode:: bash
(env) $ cd docs
(env) $ make html
HTML files will be put in the `apiserver/docs/build/html` directory.
Webclient
---------
.. sourcecode:: bash
# In a different terminal
$ cd webclient
$ yarn install
$ yarn start
The webclient will now be running on port 3000. Make changes and refresh to see them.
Reverse Proxy
-------------
Point a domain to the server and reverse proxy requests according to subdomain.
Domains: `portal.example.com`, `api.portal.example.com`, `static.portal.example.com`, `docs.portal.example.com` should all be reverse proxied.
Configure nginx:
.. sourcecode:: text
server {
listen 80;
root /var/www/html;
index index.html index.htm;
server_name portal.example.com;
location / {
proxy_pass http://127.0.0.1:3000/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
root /var/www/html;
index index.html index.htm;
server_name api.portal.example.com;
client_max_body_size 20M;
location / {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Headers' 'content-type, authorization' always;
add_header 'Access-Control-Allow-Methods' 'HEAD,GET,POST,PUT,PATCH,DELETE' always;
add_header 'Access-Control-Max-Age' '86400' always;
proxy_pass http://127.0.0.1:8002/;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
root /home/you/spaceport/apiserver/data/static;
index index.html;
server_name static.portal.example.com;
location / {
add_header 'cache-control' 'max-age=2678400' always;
try_files $uri $uri/ =404;
}
}
server {
listen 80;
root /home/you/spaceport/apiserver/docs/build/html;
index index.html;
server_name docs.portal.example.com;
location / {
try_files $uri $uri/ =404;
}
}
HTTPS
+++++
Install certbot and run it:
.. sourcecode:: bash
$ sudo apt install certbot python-certbot-nginx
$ sudo certbot --nginx
Answer the prompts, enable redirect.

View File

@ -3,18 +3,13 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Spaceport's documentation!
=====================================
Spaceport Documentation
=======================
.. toctree::
:maxdepth: 2
:caption: Contents:
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
dev
apioverview
api