240 lines
7.2 KiB
Markdown
240 lines
7.2 KiB
Markdown
|
# LingDocs Dictionary Monorepo
|
||
|
|
||
|
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
|
||
|
[![Netlify Status](https://api.netlify.com/api/v1/badges/65b633a2-f123-4fcd-91bc-5e6acda43256/deploy-status)](https://app.netlify.com/sites/lingdocs-dictionary/deploys)
|
||
|
![Website CI](https://github.com/lingdocs/lingdocs-main/actions/workflows/website-ci.yml/badge.svg)
|
||
|
![Functions CI](https://github.com/lingdocs/lingdocs-main/actions/workflows/functions-ci.yml/badge.svg)
|
||
|
![Account Deploy](https://github.com/lingdocs/lingdocs-main/actions/workflows/deploy-account.yml/badge.svg)
|
||
|
![Functions Deploy](https://github.com/lingdocs/lingdocs-main/actions/workflows/deploy-functions.yml/badge.svg)
|
||
|
|
||
|
![LingDocs Logo](./website/public/icons/icon-w-name.png)
|
||
|
|
||
|
## Contents
|
||
|
|
||
|
This monorepo contains:
|
||
|
- `/dictionary-client` the frontend of the dictionary, a React SPA
|
||
|
- `/account` a backend authentication server
|
||
|
- `/functions` backend Firebase functions for use with the dictionary
|
||
|
|
||
|
To update the `@lingdocs/pashto-inflector` dependency accross the project you can use the shell script included:
|
||
|
|
||
|
```sh
|
||
|
./update-inflector.sh [version]
|
||
|
```
|
||
|
|
||
|
### Dictionary Client
|
||
|
|
||
|
SPA Dictionary Frontend
|
||
|
|
||
|
Use [Yarn](https://yarnpkg.com/).
|
||
|
|
||
|
```sh
|
||
|
cd website
|
||
|
yarn install
|
||
|
```
|
||
|
|
||
|
#### Development
|
||
|
|
||
|
```sh
|
||
|
yarn start
|
||
|
```
|
||
|
|
||
|
### Account
|
||
|
|
||
|
Backend authentication server build on express / passport
|
||
|
|
||
|
#### Development
|
||
|
|
||
|
Use [npm](https://www.npmjs.com/).
|
||
|
|
||
|
```sh
|
||
|
cd account
|
||
|
npm install
|
||
|
npm run dev
|
||
|
```
|
||
|
|
||
|
### Functions
|
||
|
|
||
|
Backend Firebase functions
|
||
|
|
||
|
Use [npm](https://www.npmjs.com/).
|
||
|
|
||
|
```sh
|
||
|
cd functions
|
||
|
npm install
|
||
|
```
|
||
|
|
||
|
#### Development
|
||
|
|
||
|
```sh
|
||
|
firebase login
|
||
|
# get envars locally
|
||
|
firebase functions:config:get > .runtimeconfig.json
|
||
|
# start functions emulator
|
||
|
npm run serve
|
||
|
```
|
||
|
|
||
|
## Architecture
|
||
|
|
||
|
![LingDocs Pashto Dictioanry App Architecture](./architecture.svg)
|
||
|
|
||
|
### Source Layer
|
||
|
|
||
|
#### GitHub Git Repo
|
||
|
|
||
|
The monorepo contains both a `website` folder for the frontend PWA and a `functions` folder for the backend functions. Both parts are written in TypeScript and are tied together using the types found in the `@lingdocs/pashto-inflector` package used by both as well as the types found in `./website/src/lib/backend-types.ts`
|
||
|
|
||
|
##### `./website` frontend
|
||
|
|
||
|
The front-end website code in `./website` is made with `create-react-app` and written in typescript with `jest` testing. It is a SPA and PWA.
|
||
|
|
||
|
The deployment is done automatically by netlify upon pushing to the `master` branch.
|
||
|
|
||
|
##### `./functions` backend
|
||
|
|
||
|
The backend code found in `./functions` and is written in TypeScript.
|
||
|
|
||
|
It is compiled and deployed automatically by the repo's GitHub Actions to Firebase Cloud Functions upon pushing to the `master` branch.
|
||
|
|
||
|
#### Google Sheets Dictionary Source
|
||
|
|
||
|
The content of the dictionary is based on a Google Sheets documents containing rows with the information for each dictionary entry. This can be edited by an editor directly, or through the website frontend with editor priveledges.
|
||
|
|
||
|
A cloud function in the backend compiles the dictionary into binary form (protobuf) then uploads it into a Google Cloud Storage bucket. The deployment is triggered from the website by an editor.
|
||
|
|
||
|
### Backend Layer
|
||
|
|
||
|
#### Firebase Functions
|
||
|
|
||
|
Serverless functions are used in conjungtion with Firebase Authentication to:
|
||
|
- check if a user has elevated priveledges
|
||
|
- receive edits or suggestions for the dictionary
|
||
|
- compile and publish the dictionary
|
||
|
- create and clean up elevated users in the CouchDB database
|
||
|
|
||
|
#### Account Server
|
||
|
|
||
|
Deployed through a self-hosted actions runner. It runs on an Ubuntu 20.04 machine and it requries `node` and `redis`.
|
||
|
|
||
|
The runner is launched by this line in a crontab
|
||
|
|
||
|
```
|
||
|
@reboot ./actions-runner/run.sh
|
||
|
```
|
||
|
|
||
|
Process managed by pm2 using this `ecosystem.config.js`
|
||
|
|
||
|
```
|
||
|
module.exports = {
|
||
|
apps : [{
|
||
|
name : "account",
|
||
|
cwd : "./actions-runner/_work/lingdocs-main/lingdocs-main/account",
|
||
|
script: "npm",
|
||
|
args: "start",
|
||
|
env: {
|
||
|
NODE_ENVIRONMENT: "************",
|
||
|
LINGDOCS_EMAIL_HOST: "**************",
|
||
|
LINGDOCS_EMAIL_USER: "**************",
|
||
|
LINGDOCS_EMAIL_PASS: "*****************",
|
||
|
LINGDOCS_COUCHDB: "****************",
|
||
|
LINGDOCS_ACCOUNT_COOKIE_SECRET: "******************",
|
||
|
LINGDOCS_ACCOUNT_GOOGLE_CLIENT_SECRET: "******************",
|
||
|
LINGDOCS_ACCOUNT_TWITTER_CLIENT_SECRET: "******************",
|
||
|
LINGDOCS_ACCOUNT_GITHUB_CLIENT_SECRET: "******************",
|
||
|
LINGDOCS_ACCOUNT_RECAPTCHA_SECRET: "6LcVjAUcAAAAAPWUK-******************",
|
||
|
}
|
||
|
}]
|
||
|
}
|
||
|
```
|
||
|
|
||
|
```sh
|
||
|
pm2 start ecosystem.config.js
|
||
|
pm2 save
|
||
|
```
|
||
|
|
||
|
Put behind a NGINX reverse proxy with this config (encryption by LetsEncrypt)
|
||
|
|
||
|
```
|
||
|
server {
|
||
|
server_name account.lingdocs.com;
|
||
|
|
||
|
location / {
|
||
|
proxy_pass http://localhost:4000;
|
||
|
proxy_http_version 1.1;
|
||
|
proxy_set_header Upgrade $http_upgrade;
|
||
|
proxy_set_header Connection 'upgrade';
|
||
|
proxy_set_header Access-Control-Allow-Origin *;
|
||
|
proxy_set_header Host $host;
|
||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||
|
proxy_cache_bypass $http_upgrade;
|
||
|
}
|
||
|
|
||
|
error_page 500 /500.json;
|
||
|
location /500.json {
|
||
|
return 500 '{"ok":false,"error":"500 Internal Server Error"}';
|
||
|
}
|
||
|
|
||
|
error_page 502 /502.json;
|
||
|
location /502.json {
|
||
|
return 502 '{"ok":false,"error":"502 Bad Gateway"}';
|
||
|
}
|
||
|
|
||
|
error_page 503 /503.json;
|
||
|
location /503.json {
|
||
|
return 503 '{"ok":false,"error":"503 Service Temporarily Unavailable"}';
|
||
|
}
|
||
|
|
||
|
error_page 504 /504.json;
|
||
|
location /504.json {
|
||
|
return 504 '{"ok":false,"error":"504 Gateway Timeout"}';
|
||
|
}
|
||
|
|
||
|
listen [::]:443 ssl ipv6only=on; # managed by Certbot
|
||
|
listen 443 ssl; # managed by Certbot
|
||
|
ssl_certificate /etc/letsencrypt/live/account.lingdocs.com/fullchain.pem; # managed by Certbot
|
||
|
ssl_certificate_key /etc/letsencrypt/live/account.lingdocs.com/privkey.pem; # managed by Certbot
|
||
|
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||
|
|
||
|
}
|
||
|
server {
|
||
|
if ($host = account.lingdocs.com) {
|
||
|
return 301 https://$host$request_uri;
|
||
|
} # managed by Certbot
|
||
|
|
||
|
|
||
|
server_name account.lingdocs.com;
|
||
|
|
||
|
listen 80;
|
||
|
listen [::]:80;
|
||
|
return 404; # managed by Certbot
|
||
|
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### CouchDB
|
||
|
|
||
|
When a user upgrades their account level to `student` or `editor`:
|
||
|
|
||
|
1. A doc in the `_users` db is created with their Firebase Authentication info, account level, and a password they can use for syncing their personal wordlistdb
|
||
|
2. A user database is created which they use to sync their personal wordlist.
|
||
|
|
||
|
There is also a `review-tasks` database which is used to store all the review tasks for editors and syncs with the review tasks in the app for the editor(s).
|
||
|
|
||
|
#### Google Cloud Storage
|
||
|
|
||
|
Contains:
|
||
|
|
||
|
- `dict` - the dictionary content in protobuf format
|
||
|
- `dict-info` - information about the version of the currently available dictionary in protobuf format
|
||
|
|
||
|
The website fetches `dict-info` and `dict` as needed to check for the latest dictionary version and download it into memory/`lokijs`.
|
||
|
|
||
|
### Frontend Layer
|
||
|
|
||
|
#### PWA
|
||
|
|
||
|
The frontend is a static-site PWA/SPA built with `create-react-app` (React/TypeScript) and deployed to Netlify.
|
||
|
|
||
|
|