💡 This contributing guide is coding related. For philosophy and other literary related matters, see our contributing guide
Welcome to the sPhil repository. We're happy you're here and would like to contribute to the project! We hope this guide will get you up to speed with the workings of the web application and help you set up your own local development.
sPhil is a monolithic web app that uses Nextra.js to build its MDX contents and Next.js to bundle and run the server. React.js is used both on the frontend and backend, with Next's server actions to primarily handle client-server communication.
Besides these and many other libraries, the code is structured according to "feature based folder" paradigm to improve long-term maintainability and developer sanity. There are three main groups at play:
/app
folder that deals with routing (special Next.js folder)/lib
folder that contains code to be freely shared across the codebase./features
folder that contains specific feature-based code.
The idea is that /app
can import from /lib
and /features
, but /lib
does
not import from either, and /features
can only import from /lib
.
Example of feature based folder structure with import paths flow
┌─────────────────────────┐ ┌──────────────────────────┐
│ │ │ │
│ /lib │ │ /app │
│ ┌─────────────────┐ │ │ ┌────────────────────┐ │
│ │ /components │ ├──────────►│ │ /<routes> │ │
│ └─────────────────┘ │ │ └────────────────────┘ │
│ ┌─────────────────┐ │ │ │
│ │ /auth │ │ │ │
│ └─────────────────┘ │ └──────────────────────────┘
│ ┌─────────────────┐ │ ▲
│ │ /server │ │ │
│ └─────────────────┘ │ │
│ ┌─────────────────┐ │ ┌────────────┴─────────────┐
│ │ /hooks │ │ │ │
│ └─────────────────┘ │ │ /features │
│ ┌─────────────────┐ │ │ ┌───────────────────┐ │
│ │ /utils │ ├──────────►│ │ /courses │ │
│ └─────────────────┘ │ │ └───────────────────┘ │
│ ┌─────────────────┐ │ │ ┌───────────────────┐ │
│ │ /database │ │ │ │ /billing │ │
│ └─────────────────┘ │ │ └───────────────────┘ │
│ │ │ │
└─────────────────────────┘ └──────────────────────────┘
Individual feature folders mimic the folder structure found in /lib
, but
sometimes may have different requirements.
Example of a single feature folder (marketing) with its sub-folders
┌────────────────────────────┐
│ /marketing │
│ ┌────────────────────────┐ │
│ │ /components │ │
│ └────────────────────────┘ │
│ ┌────────────────────────┐ │
│ │ /data │ │
│ └────────────────────────┘ │
│ ┌────────────────────────┐ │
│ │ /server │ │
│ └────────────────────────┘ │
│ ┌────────────────────────┐ │
│ │ /utils │ │
│ └────────────────────────┘ │
│ │
└────────────────────────────┘
The idea is that at any given time, when a developer is working on a feature, she should only have to deal with the logic internal to that feature and whatever needed from the lib folder. When the feature is ready to be implemented into the app, it should be a matter of relatively few imports into the app folder.
Sharing code between features is disallowed! If it is discovered that a piece of code from a feature should be shared with another, then it needs to be elevated to the lib folder and imported back into the feature (e.g. as a callback). Features should ever only concern themselves with their own internals and what they need from the shared lib.
- Server components, server cache and server actions are to be preferred over traditional client component and query hooks. Not only is this a faster way to implement code, but it comes with end-to-end type safety and is more performant. Where high interactivity is required, we will consider the traditional approach.
- Nextra.js is used to generate HTML out of the MDX files from the
/content
folder as well as handle routing and a majority of the styling. Additionally, we use DaisyUI, TailwindCSS and MUI but try to keep it in style with Nextra's. Ideally, we'd want less dependencies in the UI department, but we're open to new ideas. - Prisma is used to interface with the database and to provide up-to-date type safety across the app. The major downside with Prisma is that it is query-heavy and not very performant. Currently, the DX is worth it but we may look into replacing it in the future with a more direct hands-on interface with the database.
The web app requires a number of API keys to function properly. However, not all these are required for most development. If you'd like to gain access to our shared development environment, please get in touch service@systemphil.com or join our discord with an introduction of yourself.
The remainder of this guide assumes you want to set up your own development environment.
After you have forked and cloned the repository to your machine, begin by
creating a .env
in the project root. Observe .env.example
for a list of key
names and copy over at least the minimal set.
Minimal environment will require these env vars:
NEXT_PUBLIC_SITE_ROOT
Copy this fromenv.example
NEXT_PUBLIC_GA_ID
Copy this fromenv.example
DATABASE_URL
AUTH_SECRET
AUTH_GITHUB_ID
AUTH_GITHUB_SECRET
GCP_PRIMARY_BUCKET_NAME
GCP_SECONDARY_BUCKET_NAME
GCP_BUCKET_HANDLER_KEY
STRIPE_API_KEY
Note: without Stripe access, you won't be able to create or fully update courses.
- Once you have access to the repo on github, fork and/or clone it into a folder where you keep your projects.
- Then
cd sphil
to get into the project directory. - Make your own development branch
git checkout -b dev-<your-name>
(example:git checkout -b dev-tim
). - In future, when getting the latest changes from the main development branch,
run
git merge main
whilst on your development branch to incorporate the changes into your branch.
- Once inside, run
npm i
(aliasnpm install
) to install all the packages. This will create the/node_modules
folder.
You can either sign up on cockroachdb to get a serverless instance on the free tier or set up one locally.
- To download and install cockroachdb locally, follow these guides for your system:
- Verify installation was successful, running
cockroach demo
and inputtingSELECT ST_IsValid(ST_MakePoint(1,2));
into the interactive shell. - In a new terminal, start a single-node local instance (commands for Windows
may vary). Make sure to input your user home directory.
-
cockroach start-single-node --insecure \ --listen-addr=localhost:26257 \ --http-addr=localhost:8080 \ --store=path=/home/YOUR_USER/cockroach-data,size=2GB
-
- From the standard output, make sure to extract and save the
sql:
path for theDATABASE_URL
for the.env
. It starts withpostgresql://
. - With the db instance running, you can check if Prisma is able to connect with
it and migrate its schemas. Run
npx prisma db push
.
AUTH_SECRET
can be any random string. You can useopenssl rand -base64 32
to generate one.- For the Github credentials, begin by going to github developer settings
- Create a new OAuth app
- Ensure these fields are populated as follows:
- Homepage URL:
http://localhost:3000
- Authorization callback URL:
http://localhost:3000/api/auth/callback/github
- Homepage URL:
- Once created, retrieve the Github ID and Secret and define the Github keys.
Buckets are used for storage. We use GCP and they will likely incur some cost for usage.
⚠️ The information in this section is sparse.
- Two buckets are needed, one public and one restricted. However, both of these need to be handled by the same service account.
- Start by signing up to Google Cloud.
- Create a project.
- Inside the project, navigate to storage (or search for it in the console).
- Create two buckets, each standard, single-region (preferably near you or near the database). For the bucket that will be public, turn off prevent public access.
- Go to the bucket that is going to be public, and go to
permissions
, clickGrant Access
, inputallUsers
under "New principals" and for the role assignStorage Object Viewer
. If you get a warning that you're making the bucket public, you're on the right track. - Put the names of the buckets as the local environment variables: the primary is the restricted one while the secondary is the public.
- Now navigate to
Service Accounts
and create a new service account. Name it something recognizable and give it the following roles:Service Account Token Creator
,Service Account User
andStorage Object Admin
(you can also assign these roles underIAM
page). - With the service account created and ready, click on it and go to the
keys
tab where you willadd key
->Create new key
. SelectJSON
and clickCreate
. This will download a.json
file. DO NOT SHARE THIS WITH ANYONE - Find the key file and base64 encode it. (On linux,
base64 key.json > key_base64.txt
). Copy the contents into theGCP_BUCKET_HANDLER_KEY
env var (be sure to remove empty carriage returns and enclose the contents in quotes just in case). - That should be it! 🥳
⚠️ The information in this section is sparse.
In order to create and fully update courses, a Stripe connection is needed. Sign
up an account on Stripe and set up a test environment. Get the API key and
populate the STRIPE_API_KEY
env vars.
If you want to test checkouts, you must also create a webhook connection. To
test these locally, you must have the
Stripe CLI running simultaneously with
your app. Once you have the Stripe CLI installed and authenticated, we have a
shortcut you can use from the project: npm run stripe:listen
.
⚠️ The information in this section is sparse.
We use Resend to handle emails. You will need to set up an account there and generate API keys. Possibly you may need a domain as well.