This project is structured as a monorepo with five core properties:
- Backend
- Dashboard
- Shared (aka Interface)
- Marketing
- Mobile
The Backend is architected as two main components with the underlying data layer being powered by a Postgres database. These are the Hasura/GraphQL engine, and the Node.js REST API.
While everything can be fully built on Hasura, we have opted for the best
tool for the job and fallback to REST API endpoints for functionality that is
more well-suited for a REST implementation. A great example of that is
authentication, which is implemented with the /api/authenticate.js, and
/api/log-out.js endpoints.
Please fully read Hasura documentation to understand the architecture of the primary API. We currently use Hasura's automatically-generated GraphQL API, permissions, and events (webhooks). Hasura also manages data migrations and other metadata (like relationships, permissions, event configuration).
The Dashboard is architected as an isomorphic web application powered by Next.js. The dashboard is for Super Admins and Network Admins only. Network members cannot access the dashboard.
We utilize certain technologies across the entire platform (Javascript, React, GraphQL), and have decided to maintain a directory of React components, Javascript utility functions, and GraphQL queries/mutations that can be re-used across all other properties.
Importing files from Interface into other properties is achieved with symlinks and/or Babel aliases (in the case of the Mobile property). While the more correct implementation would be an npm dependency with a version number, we have decided to instead keep it simple and lean into the benefits of a monorepo.
The Marketing site is architected as a statically generated web site powered by Next.js. The Marketing site must be running (including locally) in order to log out of the dashboard.
The Mobile property is architected as a React Native application, fully Managed by the Expo framework. We opt for React Hooks as much as possible, and use Context for managing any kind of shared or global state. Data fetching is implemented within the screen, and is only lifted up and into Context if necessary. Screens are created and used liberally to separate concerns as high up as possible.
Declaration is a private social network as-a-service platform. As such, please keep in mind that almost all data is scoped to a Network. The main exception is User, which can belong to multiple Networks.
Anyone can authenticate into Declaration, but will quickly reach a wall if they're not a member of a Network or a Super Admin User. To be gain access to a Network, we have three entry points:
-
Become a Super Admin. This can only be accomplished by having admin access to the database, and after creating a User record. Insert a User UUID into the
super_admintable to gain Super Admin access. -
Get invited to a Network by a Network Admin or Super Admin. This is primarily achieved through the Dashboard, after navigating to the Network's Members page. A form exists on that page which inserts a row into the
network_membership_invitationtable, which fires a webhook to/api/webhooks/network-membership-invitation, which sends an email to the invited user. If the user accepts the invitation, a User record is created (if one didn't exist already), and are granted access to the Network via an entry in thenetwork_usertable. -
Request membership to a Network from a Network Admin or Super Admin. This is primarily achieved from the mobile applications, after registration. A screen exists that displays a list of Networks with forms to submit a Membership Request. Upon submission of the form, a row is created in the
network_membership_requesttable, which fires a webhook to/api/webhooks/network-membership-request. A Network Admin or Super Admin can then accept the request on the Network's Members Dashboard page. After acceptance, the User can refresh the mobile application to gain access to the network.
Additionally, the metal-future-printed-judge@example.com email can be used to
bypass the entire authentication process. This was primarily created to allow
for a smoother Apple App Store review process.
Content is architected to have set of base fields, and then allow for a single
partial to extend Content to include functionality that differs significantly
from each other. On the frontend/design side we call these extensions
"Templates", on the backend side we call them "Partials" and are enabled via
the various content_partial_* tables.
As such, Content can have a heading, sub heading, body, media (via media
table), meta (via content_meta table), and a single content_partial
one-to-one relationship. Content Partial can then have a relationship to one of
several Content Partial sub-tables like content_partial_poll.
This structure allows us to define Content as our primitive, while still allowing for Templates in an extensible manner. Maintaining Content as our primitive is fundamental to Declaration's flexibility and value proposition.
npm install vercel --global
npm install hasura-cli --global
npm install expo-cli --globalEnsure you have iOS and Android simulators installed, or iOS and Android devices on hand.
Users must be added to Github and Vercel accounts
`cd backend`
`docker-compose up -d` # start services
`npm run dev` or `hasura console --admin-secret <HASURA_ADMIN_SECRET>` # start hasura console. https://hasura.io/docs/1.0/graphql/core/hasura-cli/hasura_console.html
`vercel dev` # start REST API server
For initial onboarding, prior to running `vercel dev`, create vercel account and run `vercel` to go thru vercel onboarding.
- Project is declaration-backend
To initialize your local database, run the following only once:
`hasura migrate apply --admin-secret <HASURA_ADMIN_SECRET>`
`hasura metadata apply --admin-secret <HASURA_ADMIN_SECRET>`
Create a .env file with expected environment variables:
```bash
HASURA_BASE_URL="http://localhost:8080/v1/graphql"
HASURA_ADMIN_SECRET_KEY=
JWT_SECRET_KEY=
NODE_ENV='development'
SENDGRID_API_KEY=
DECLARATION_AWS_ACCESS_KEY=
DECLARATION_AWS_SECRET_KEY=
DECLARATION_AWS_BUCKET_NAME=Note: Some environment variables are prefixed with DECLARATION_ due to
Vercel Platform Limitations.
cd dashboard
npm install
npm run devFor initial onboarding:
- run the dashboard and enter your email. Dashboard will say no permissions.
- close the dashboard
- In Hasura, insert the user uuid from user table into the super admin table.
- start dashboard again and login. New link will be sent to email or available in verification_code table.
- Verify super admin is working by creating a new Network.
- Marketing site is required to be running to log out of the Dashboard.
cd marketing
npm install
npm run devexpo startTo run URQL tools for monitoring database queries:
npx urql-devtools
To run simulators:
open -a "Simulator" # for iOS
emulator @<NAME_OF_AVD> # for AndroidNote: Ensure you have installed and configured iOS and Android simulators/emulators correctly by following the guides on Expo.
To run React Native Debugger:
open -g 'rndebugger://set-debugger-loc?port=19001'The backend consists of three technologies:
- Postgres database
- Hasura GraphQL server
- REST API functions
We deploy and manage the Postgres database with AWS RDS.
After spinning up the LTS version of Postgres, follow the Hasura guide for Postgres requirements. It essentially entails configuring a database user for Hasura to use, with the required permissions, and ensuring the appropriate Postgres extensions are installed.
Once the Postgres database is deployed and ready for Hasura consumption, deploy the Hasura GraphQL server.
We deploy Hasura as a Docker image to AWS Elastic Beanstalk.
Spin up an Elastic Beanstalk Docker application (Single Container), create an environment, configure the environment's capacity to be Load balanced with minimum and maxmimum instance counts. This ensures that a load balancer is provisioned for the environment, which is necessary for HTTPS.
Ensure the expected environment variables are set:
HASURA_GRAPHQL_DATABASE_URL=
HASURA_GRAPHQL_ENABLE_CONSOLE=
HASURA_GRAPHQL_ADMIN_SECRET=
HASURA_GRAPHQL_JWT_SECRET=
HASURA_GRAPHQL_UNAUTHORIZED_ROLE=
NETWORK_MEMBERSHIP_INVITATION_WEBHOOK_URL=
VERIFICATION_CODE_WEBHOOK_URL=
USER_WEBHOOK_URL=
NETWORK_USER_WEBHOOK_URL=
NETWORK_USER_RELATIONSHIP_WEBHOOK_URL=
COMMENT_WEBHOOK_URL=
CONTENT_STAR_WEBHOOK_URL=
CONTENT_WEBHOOK_URL=
CONTENT_META_WEBHOOK_URL=
MEDIA_WEBHOOK_URL=See ./backend/docker-compose.yml for examples of the above environment
variables. Updating environment variables might require a redeployment to take
effect.
Upload dockerrun.aws.json as the application artifact to the environment. The
artifact essentially tells Elastic Beanstalk to fetch the public Hasura docker
image and run it with port 8080 exposed. We shouldn't need to change and
redeploy this often.
At this point, the Elastic Beanstalk environment should be working as expected on the Elastic Beanstalk provisioned URL. To configure a declaration.net specific subdomain, do the following.
Create a CNAME record that maps the declaration.net subdomain to the Elastic Beanstalk provisioned URL.
Create an SSL certificate for the subdomain you intend to use with AWS Certificate Manager. Once the certificate is verified and provisioned, go back to the Elastic Beanstalk environment, configure the load balancer, and add a new listener for HTTPS. The configuration should be listener port 443, listener protocol HTTPS, instance port 80, instance protocol HTTP, SSL certificate should be the certificate that was just provisioned.
The Hasura server should now be working on HTTP and HTTPS, both on the Elastic Beanstalk provisioned URL, as well as the declaration.net subdomain.
For incremental deployments, use the hasura.txt file in the credentials folder to find the migrate and metadata commands:
hasura migrate apply ...
hasura metadata apply ...
The REST API is coded as serverless functions that are intended to run on the Vercel platform.
Ensure your Vercel account has a seat on the Declaration Vercel team.
Ensure the expected environment variables are set:
HASURA_BASE_URL=
HASURA_ADMIN_SECRET_KEY=
JWT_SECRET_KEY=
NODE_ENV="production"
SENDGRID_API_KEY=
DECLARATION_AWS_ACCESS_KEY=
DECLARATION_AWS_SECRET_KEY=
DECLARATION_AWS_BUCKET_NAME=See ./backend/.env for examples of the above environment variables. Updating
environment variables requires a redeployment to take effect. To set
environment variables, either use the vercel env command or navigate to the
Vercel settings page for the declaration-backend project.
To deploy, simply run vercel in the ./backend/ directory. An immutable
deployment will be created. The immutable deployment can then be aliased to the
intended URL. The immutable deployment can then be aliased to the intended URL
(e.g. vercel alias https://declaration-backend-gainsboroughtech.declaration.vercel.app stage.api.declaration.net).
To deploy to production, simply run vercel --prod.
The Dashboard is coded as an isomorphic Nextjs application that is intended to be run on the Vercel platform.
Ensure your Vercel account has a seat on the Declaration Vercel team.
Ensure the expected environment variables are set:
HASURA_BASE_URL=
REST_BASE_URL=
MARKET_BASE_URL=
MOBILE_BASE_URL=
BASE_URL=See ./dashboard/next.config.js for examples of the above environment variables. Updating
environment variables requires a redeployment to take effect. To set
environment variables, either use the vercel env command or navigate to the
Vercel settings page for the declaration-dashboard project.
To deploy, simply run vercel in the ./dashboard/ directory. An immutable
deployment will be created. The immutable deployment can then be aliased to the
intended URL (e.g. vercel alias https://declaration-dashboard-gainsboroughtech.declaration.vercel.app stage.dashboard.declaration.net).
To deploy to production, simply run vercel --prod.
The Dashboard is coded as a statically generated Nextjs application that is intended to be run on the Vercel platform.
Ensure your Vercel account has a seat on the Declaration Vercel team.
Ensure the expected environment variables are set:
HASURA_BASE_URL=
REST_BASE_URL=
DASHBOARD_BASE_URL=
BASE_URL=See ./marketing/next.config.js for examples of the above environment variables. Updating
environment variables requires a redeployment to take effect. To set
environment variables, either use the vercel env command or navigate to the
Vercel settings page for the declaration-dashboard project.
To deploy, simply run vercel in the ./marketing/ directory. An immutable
deployment will be created. The immutable deployment can then be aliased to the
intended URL. The immutable deployment can then be aliased to the intended URL
(e.g. vercel alias https://declaration-marketing-nickdandakis.declaration.vercel.app https://stage.declaration.net).
To deploy to production, simply run vercel --prod.
Vercel deployments are split into three environments. Production, Preview, and Development.
The Development environment is what's loaded when you use vercel dev.
The Preview environment is what's loaded when you deploy with vercel.
The Production environment is what's loaded when you deploy with vercel --prod.
Take care in setting the appropriate environment variables for each project,
and each environment. We largely ignore the Development environment, as we use
local process.env overload files during development. But, ensure Preview and
Production environment variables are set correctly.
The Mobile application is coded as a React Native application, built with the Expo managed workflow.
We maintain two mobile applications, one for production named 'Declaration',
and one for stage named 'Declaration (Stage)'. Each application has separate
environment variables, ./mobile/.production.env and ./mobile/.stage.env
respectively.
Ensure you are a part of the Declaration Apple team, and Declaration Google Play team.
Update the package.json version. The version number can then be found at the bottom of the Settings screen in the app
Or run npm run deploy
For Full Build
Update the app.config.js version, buildNumber, and versionCode for the Apple/Google deployment.
# to publish production iOS and Android
BUILD_ENVIRONMENT_MODE="production" expo publish
# to build production iOS
BUILD_ENVIRONMENT_MODE="production" expo build:ios -t archive
# to upload production iOS
BUILD_ENVIRONMENT_MODE="production" expo upload:ios
# to build production Android
BUILD_ENVIRONMENT_MODE="production" expo build:android -t app-bundle
# to upload production Android
BUILD_ENVIRONMENT_MODE="production" expo upload:android
# to publish stage iOS and Android
BUILD_ENVIRONMENT_MODE="stage" expo publish
# to build stage iOS
BUILD_ENVIRONMENT_MODE="stage" expo build:ios -t archive
# to upload stage iOS
BUILD_ENVIRONMENT_MODE="stage" expo upload:ios
# to publish stage Android
BUILD_ENVIRONMENT_MODE="stage" expo publish:android
# to build stage Android
BUILD_ENVIRONMENT_MODE="stage" expo build:android -t app-bundle
# to upload stage Android
BUILD_ENVIRONMENT_MODE="stage" expo upload:androidBuilding means creating the mobile platform's equivalent standalone artifact (Archive for iOS, App Bundle for Android).
Publishing means deploying the updated React Native bundle to Expo's servers.
Uploading means submitting the previously built standalone application to Apple/Google servers.
Expo manages over-the-air updates of our application. The standard procedure is to always publish new builds, but sometimes upload new builds if native changes have occurred. Check the Expo documentation for more information.