list and rate your anime
This project is divided into 2 major parts: Next.js frontend and Django backend. It's deployed on a docker image, with a nginx server to direct traffic between the two services
/public
- Static media directory for Next.js/src
- The Next.js project root__tests__
- jest and cypress testscomponents
- Reusable components meant to be used anywhere on the sitepages
- Next.js pages.constants
- Constants are stored here, some exist on project init for form validation.models
- General repositories for shared api logic. For instancemodels/user.js
contains mostly axios data fetches but also handles constate sharing of user data across the app.theme
- Contains the Material UI theme. This contains overarching styles for the application and determines appearance of Material UI components globally.util
- General reusable utility functions
/animelister
- the Django project folderUser
- User related logic, views, models, etc.home
- Handles home page for django, error endpoint, settings context processorwagtailapp
- If wagtail was used via the cookiecutter setup script, this is where wagtail models, views, etc. are createdanimelister
- Project settings, asgi settings, and base urlsutil
- Container for general utility classes, functions, etc.
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.
- Node >=10.14.2
- python 3.8
- Install PostgreSQL server and client on your local computer
- For Mac: use Homebrew:
brew install postgresql
- For Ubuntu: apt-get install postgresql
- For Mac: use Homebrew:
- Install virtualenvwrapper https://virtualenvwrapper.readthedocs.io/en/latest/install.html#basic-installation
- Run the start script:
./scripts/start.sh
from the project root- This will install the pip and yarn packages and set up the local database for the project
- Refer to remote server setup instructions if setting up heroku
- NOTE: we no longer recommend using ./scripts/setup_heroku.sh
- Run
honcho start
to start the application - go to "127.0.0.1:3000" in a browser
You will be running two concurrent servers, a backend and a frontend server.
- In a terminal window, run the Django backend server using
./manage.py runserver
from the project root, which will start the server on 127.0.0.1:8000- You may choose to run
./manage.py runserver_plus
instead as this provides more debugging features
- You may choose to run
- Run the Next.js frontend server
yarn run dev
oryarn dev
which will start the server on 127.0.0.1:3000
These commands are embedded in the proc file at the root of the repo - you may also run both servers simultaneously via honcho, installed with the dev requirements.
- you can run honcho via
honcho start
.env
This handles the environment variables for Next.js and django. If you want access to an env variable at build time in Next.js it must start withNEXT_PUBLIC
Store all django environment variables inside of heroku
- heroku stores secrets, so add variable to heroku through
heroku config:set foo=bar
- instruct django to use the envrionment variable by adding a line to a settings file
foo = env("foo")
note - next only uses variables provided to it at build. All new variables will require a rebuild of the application.
- Circle stores build time secrets, so add secret to circle through web ui OR store in heroku and use
heroku config:get VARIABLE
- Make sure secret is passed to docker build process by adding a --build-arg line in circleci/config.yml
- make sure docker picks up secret at build time by adding an ARG statement to docker file
- Update next.js config file to pick up envrionment variable.
The default honcho command runs dev
but the package.json contains another command, debug
. If you run yarn run debug
or yarn debug
you will be able to evaluate debugger statements inside of a chrome tab by browsing to chrome://inspect
and clicking on the entry for the node process.
Honcho isn't great with using pdb or ipdb to debug python code. It's recommended to use the separate commands described above if python needs debugging.
NOTE: you must be at the project root to run these commands
- make sure you are still in the environment, otherwise run
workon animelister
- run
./manage.py test
which runs the Django test suite - also run:
scripts/validate.sh
to check code syntax and security
- at the root of the folder run
yarn run test
oryarn test
to run the jest tests - debug next tests with
yarn test:debug
and include adebugger
in a test or code related to a test - or run
yarn run cypress run
oryarn cypress run
to run the cypress integration tests - this requires the next dev server to be running in another tab
This application model is unique in that we are running two applications simultaniously - one managing the frontend of the application and one managing the api. We run both servers on the same box in production as well. This tight coupling gives us a couple of strong guarentees:
- all communication between server side communication and api is done over locahost, and so doesn't pay a network cost. With next.js tools like getServerSideProps, which will funnel all requests through the backend, this allows us to make multiple api requests per page load concurrently using async syntax, but only pay the cost of a single network request.
- A shared domain allows for http only cookie based authentication, giving strong protection against xss attacks.
- Envrionment variables and build artifacts can be shared between processes. This ensures things like the build id are shared between frontend and backend, or static assets can go through django's cachebusting collectstatic process.
The application is meant to be hosted with the included docker file, which sets up NGINX, Django and yarn and runs all three on a single server. This is done so that communication between the yarn process and the Django process is free - allowing very cheap server side renders, and bundling of api requests fetched via getServerSideProps.
Setup for the remote environment is handled through terraform.
- create an AWSCLI profile with an access key and secret for the account you want to use to host media. This should either be your default profile, or you can pass the profile as a var to terraform
- Manually install the terraform sentry plugin (https://www.terraform.io/docs/plugins/basics.html#installing-plugins) (https://github.com/jianyuan/terraform-provider-sentry)
- set an environment variable for SENTRY_AUTH_TOKEN and TF_VAR_SENTRY_AUTH_TOKEN after getting the token through the sentry web UI a) To get the auth token, in the sentry webapp dashboard, go to User Menu > API Keys b) Note that the .env file isn't read by terraform - so set this variable in your .bashrc or through export
- Ensure you're logged in to Heroku through the Heroku cli
- Finally Remote Heroku servers and AWS infrastructure can be created by going to the terraform/environments folder and running
terraform init
followed byterraform apply
The app is capable of working with a multi-domain setup. Use case: App A has a django backend and a react native frontend. App B also uses App A's backend but is not hosted on the same domain.
ALLOWED_HOSTS setting must look something like ALLOWED_HOSTS=127.0.0.1:3000|localhost:3000|localhost:3001|.ngrok.io
(locally) and similar on heroku. Every domain that connects to the backend needs to be in allowed hosts.
CSRF_COOKIE_DOMAIN = env("CSRF_COOKIE_DOMAIN")
the CSRF cookie domain env variable is important to allow POST requests to work coming from the secondary app. This can be set to a subdomain like .exampleapp.com
NOTE: Heroku is an exception to this rule and will not allow you to set cookies on that domain .herokuapp.com
so you must purchase a domain to use and set the backend app as something like api.exampleapp.com and the outside app as whatever.exampleapp.com
CORS_ALLOWED_ORIGINS
and CSRF_TRUSTED_ORIGINS both pull from allowed hosts and are crucial for this process.
- Emails:
- Emails will be printed to the console when being "sent" during local development
- Emails will be pushed to a third party service for sending on the remote server (n.b. this must be set up separately and configured through the relevant Django settings)
- Next execution context
- Locally next will run with
yarn dev
which will turn on hot reloading and JIT compilation of the JS code. - Remotely next will first run
yarn build
to create a set of static assets and then runyarn start
to handle routing traffic and doing SSR
- Locally next will run with
- This repo is setup to use lint-staged and husky in order to do some clean up prior to committing your code. If js, css or scss files are present in your commit they will be linted. If you notice your files are not being linted every time you commit (with information printed from Husky... 'Running tasks...') then you should try removing node_modules and running
yarn
again. If git is not initialized before node_modules installed this will fail continually.
NOTE: you must be at the project root to run any ./manage.py ...
or ./scripts/...
commands
- Pull Remote Database (to replace the local animelister database with one of the live databases):
- Install and log into the the Heroku CLI:
- For Mac: use Homebrew:
brew tap heroku/brew && brew install heroku
from https://devcenter.heroku.com/articles/heroku-cli
- For Mac: use Homebrew:
- run
./scripts/pull_remote_db.sh
- in order to pull a database besides the dev database change
$ENV_NAME-dev
in line 6 of the pull_remote_db.sh file to match the correct Heroku app
- Install and log into the the Heroku CLI:
- Django Shell (to access the local database through the Django ORM/shell):
- run
./manage.py shell_plus
- run
- Django Shell on Remote Server (caution: this can potentially change/delete production data. Use with a healthy amount of caution and capture a database backup first (see below for instructions)):
- Install and log into the the Heroku CLI:
- For Mac: use Homebrew:
brew tap heroku/brew && brew install heroku
from https://devcenter.heroku.com/articles/heroku-cli
- For Mac: use Homebrew:
- open a bash shell on the remote server: run:
heroku run bash --app [app name such as animelister-dev]
- once in the shell on the Heroku server run:
python3.8 manage.py shell_plus
- Install and log into the the Heroku CLI:
- Capture a Remote Database Backup:
- run:
heroku pg:backups:capture --app [app name such as animelister-dev]
- run:
- Retrieve Media assets from AWS S3:
- Set up and configure the AWS CLI: see https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html for instructions
- be sure to set up your AWS config with your animelister credentials: instructions here https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html
- make sure a directory exists with the path [project root]/animelister/media
- from the project root run:
aws s3 cp s3://animelister-dev ./animelister/media --recursive
replacinganimelister-dev
withanimelister-prod
or a different s3 bucket name as needed
- Set up and configure the AWS CLI: see https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html for instructions
TODO storybook
Use watchman for better performance when using the django dev server. pywatchman is installed as part of dev requirements, but you must install watchman at a system level. See https://facebook.github.io/watchman/docs/install for instructions
You can validate that it's working correctly when you do runserver - you should see "Watching for file changes with WatchmanReloader" If it's not working you will see "Watching for file changes with StatReloader"
If the project is using wagtail a new app will be included called wagtailapp
. Additionally in the settings/base.py file there will be new wagtail specific settings.
Several blocks have been included to start TitleBlock, LinkBlock, ColumnBlock, RowBlock, SectionBlock, and SocialBlock. These are used in wagtail streamfields set up and ready to use. No styling has been included for them so they will need styling, but do have templates present. Currently the main block you can add to a ContentPage is a SectionBlock. This contains a Row or a Spacer. Rows contain Columns and Columns contain title, link, text and image. These are commonly seen patterns in our projects and are not necessary if you need to remove them/have no need for them.
We have added h1 and h5 to the wagtail cms richtext editor as they do not come out of the box.
- Use Prettierjs to format your javascript code. This will happen automatically on commit but is helpful to have configured to run on save.
- Preferred naming structure for all components .jsx files is PascalCase. so for a react component named PasswordField the filename would be PasswordField.jsx
- Next.js uses the filename in
/pages
directory to determine the route name so for now those should be named lowecased with dashes if necessary
- Next.js uses the filename in
- When in doubt refer to AirBnB best practices
The nginx service that directs all incoming traffic has several paths hardcoded as going directly to the django server. To open up a new path edit the webapp.conf inside image/config
Because the django server and the nextjs server are sharing authentication through cookies, it's important they stay on the same domain. There's three parts to consider when looking at how to arrange the domains serving the app - the api domain, the nextjs domain and the domain used between the two servers. A simple example would be the production hosting of both sides of the app on foo.com - the api would be reachable on foo.com/api, the next.js server would be on foo.com, and server to server communication would be over 127.0.0.1. In this case when loading a page from scratch you'd load on foo.com, then the next.js would forward the cookies to 127.0.0.1 (but still use the cookies from foo.com), and then return an authenticated response. When communicating directly to the api to login or make a post request, you'd address foo.com/api, and so cookies would still be correctly set for both frontend and backend, because they would both live under foo.com . For local development, you'd need a similar guarentee - that's why we force local development onto 127.0.0.1 and not localhost, because if the api domain doesn't match how you're addressing the client, things will break in strange ways. If you decide in the future that you want to move the two applications to different subdomains, say www and api, you can do that as long as you configure the cookie to be shared by domain rather than subdomain
Included are some fields that work well in a Formik form and not really well anywhere else.
- /src/components/forms/fields/AutoCompleteField.jsx This field will autocomplete a set of options based on text typed in. This will work across the entire queryset if you set up a search endpoint. It is also capable of pulling in select options from the OPTIONS lookup provided by DRF. In order to actually search the backend with this field, you must add a filter (default name of search) to the endpoint being hit. Searching can be turned off as well by setting enableSearch to false
- Example usage:
<AutoCompleteField
fullWidth
label="Lab Location"
name="lab"
optionLabel={option =>
`${option.name} - ${option.labNumber}`
}
callbackURL={URLS.labs}
{...rest}
/>
or for multiple select field that you want to remove duplicates from the returned options
<AutoCompleteField
fullWidth
multiple
multipleUniqueLookup="id"
disableCloseOnSelect
label="Test Group(s)"
name={`samples.${sampleIndex}.testGroups`}
optionLabel={option => option.groupTitle}
callbackURL={URLS.testGroups}
{...rest}
/>
or field that uses the OPTIONS feature of DRF to grab choices for a field
<AutoCompleteField
fullWidth
label="Special Pricing"
name="specialPricing"
value={values.specialPricing}
optionLabel={option =>
option.displayName
}
choicesLookup="specialPricing"
callbackURL={URLS.invoices}
{...rest}
/>
- date-fns - An alternative and SIGNIFICANTLY smaller to Moment.js. MomentJS should not be needed.
- Material UI - Pretty self explanatory but before you build a component you may want to check here because they likely have it or things you can use to build it. - Material UI Icons - useful list of icons for project and how to import them.
- Constate - Write local state using React Hooks and lift it up to React Context only when needed with minimum effort.
- Formik - All forms on the site use Formik - Formik Material UI - bindings for formik with Material UI input fields - Yup - Schema for formik forms
- Axios - Promise based HTTP client
- clsx - A library to create html safe class name strings out of variables and datastructures
TO ADD
New react libraries
- next.js - server side rendering
- build + serve vs dev
- static optimization
- Get getInitialProps and getServerSideProps
- material UI
- Yup and Formik
- useSWR Arch
- Running both backend and server on the same server
- axios automatically figuring out the base url
- with auth Docker
- Base image
- nginx
- Runnit Local development
- honcho start
- localhost vs 127 Testing + Debugging
- debug watcher
- browser based debugging for node
- seperate threads when in debugging
- cypress
- jest
- cypress + django
Documentation
- How to start the server
- how to debug the node server
- how to debug the django server
- api base url
more high level stuff "django rest framework is used for the backend and djoser for the auth"
cookie management
Steps to host api and frontend on different domains