Banter-Reel.mov
Banter is an experimental podcasting platform I built to explore brutalist design principles, including a RESTful API designed with best-practice standards and OAuth authentication implemented from scratch without any dependencies.
git clone git@github.com:aadv1k/banter
cd banter/
npm install
npm run build # npm run devThe app is loosely based around the MVC Architecutre, and the /dashboard is a SPA rendered via preact. Everything from the oAuth (spofity, microsoft), to MongoDB queries is implemented from scratch.
"dependencies": {
"cloudinary": "^1.35.0",
"dotenv": "^16.0.3",
"ejs": "^3.1.8",
"formidable": "^2.1.1",
"mongodb": "^5.0.1",
"node-fetch-commonjs": "^3.2.4",
"rss": "^1.2.2",
"uuid": "^9.0.0"
},- NodeJS (node-http)
- MongoDB (Profile, Podcasts, Episodes)
- Cloudinary (Audio, Image storage)
- SASS
- Preact
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
JavaScript 34 436 7 2423
SCSS 8 220 2 1135
CSS 1 29 0 1117
EJS 5 38 0 252
Markdown 1 49 0 181
-------------------------------------------------------------------------------
SUM: 49 772 9 5108
-------------------------------------------------------------------------------
Here is the oAuth flow for the APP
- Check if user is already logged in via a sessionID
- If user is logged in, redirect to
/dashboard - If user is not logged in redirect them to [SITE FOR AUTH TOKEN] for oAuth
- The oAuth site redirects the user back to
/[AUTH]/[SITE]/callback - After the OAuth redirect, if the user exists (found by their email) a new sessionID is set and the user is redirected to
/dashboard - If the user doesn't exist a request is sent to [SITE WITH USER INFO BY ACCESS TOKEN] and data is used to create a new user (along with a random password) and they are redirected to
/dashboard
./routes/AuthMicrosoft.js./routes/AuthSpotify.js
Each file within the ./routes/ directory returns a function which consumes a node-http Request and Response object.
./models/BucketStore.js: Exports a class which contains functions to push local files to a bucket like Cloudinary./models/UserModel.js: Has functions for user creation, deletion, and updating../models/MemoryStore.js: utilify used to store and modify sessionIDs in a global store object
Here is what a user object looks like looks like
{
_id: ObjectID(),
name: String,
email: String,
password: MD5(String),
profileImage: String,
podcasts: Array[]
}
For the Front-End is used.
The
./scss/ folder is structured like so
_variables.scss: Variables for rest of the stylescomponents/_btn.scss: The styles for the button_editor.scss: Style for the editor elements in/dashboard/share_form.scss: Style for the forms in the modals and the login page_index.scss: fowarding_modal.scss: Style for the modals_toast.scss: Style for the Toast component
index.scss
A large part of the frontend is handled by which is a minified version of the actual React and is pulled as a CDN.
When the user accesses /dashboard, the server responds with static html, the static html links to a index file, after which point all the dynamic UI of the app is handled as a SPA here is the overview of ./public/js/ Folder
App.js: Exports a single<App />component that ties in with the htmldashboard.ejsModal.js: Exports a<Modal />componentModalEpisode.js: Exports a<ModalEpisode />component which is used to edit/create a new episodeModalPodcast.js: Exports a<ModalPodcast />component which is used to edit/create a new podcastPageCreate.js: Component that is routed by/dashboard/create/is also the default routePageManage.js: Component that is routed by/dashboard/manage/PageShare.js: Component that is routed by/dashboard/share/Toast.js: Component that exports aToastfunction which uses a.toastfor stylesindex.js: Component linked todashboard.ejs
- Method:
POST - Fields
emailpassword
- Code:
302 - Redirect:
/dashboard - Set-Cookie:
sessionid=
badInput-400: the provided input was invaliduserNotFound-404: the given user is not registeredinvalidPassword-401: the password given for the user is invalidinvalidMethod-405: method invalid for requested resource
- Method:
POST - Fields
emailpasswordname
- Code:
302 - Redirect:
/dashboard - Set-Cookie:
sessionid=
bad-input-400: the provided input was invaliduser-exists-400: the user is already registeredinvalid-method-405: method invalid for requested resource
- Method:
GET - Cookie:
sessionid - Fields
podcastIDepisodeIDOPTIONAL
unauthorized-401: you don't have the credentials to access this resourceinvalid-podcast-id-404: the podcast ID provided does not existinvalid-episode-id-404: the episode ID provided does not exist
- Method:
POST - Cookie:
sessionid - Fields
audio: an audio filetitlenumberpodcastIDdescription
bad-input-400: the provided input was invalidinvalid-audio-file-format-400: the provided file format for the cover was invalidexceeds-audio-size-limit-400: the provided image exeeds the image size limit of a 10 Megabytesunable-to-find-user-400: was unable to find the provided sessionID or userunauthorized-401: you don't have the credentials to access this resourceinternal-err-500: something went wrong on the serverinvalid-episode-id-404: the episode ID provided does not exist
- Method:
PUT - Cookie:
sessionid - Fields
podcastIDepisodeID- Any valid parameter for
/createEpisode
unauthorized-401: you don't have the credentials to access this resourceinvalid-podcast-id-404: the podcast ID provided does not existinvalid-episode-id-404: the episode ID provided does not exist
- Method:
DELETE - Cookie:
sessionid - Fields
podcastIDepisodeID
unauthorized-401: you don't have the credentials to access this resourceinvalid-podcast-id-404: the podcast ID provided does not existinvalid-episode-id-404: the episode ID provided does not exist
- Method:
GET - Cookie:
sessionid - Fields
podcastID
unauthorized-401: you don't have the credentials to access this resourceinvalid-podcast-id-404: the podcast ID provided does not exist
- Method:
POST - Cookie:
sessionid - Fields
cover: a image file for podcast covertitleexplicitdescriptioncategorylanguage
bad-input-400: the provided input was invalidinvalid-image-file-format-400: the provided file format for the cover was invalidexceeds-imageh-size-limit-400: the provided image exeeds the image size limit of a 10 Megabytesunable-to-find-user-400: was unable to find the provided sessionID or userunauthorized-401: you don't have the credentials to access this resourceinternal-err-500: something went wrong on the serverinvalid-episode-id-404: the episode ID provided does not exist
- Method:
PUT - Cookie:
sessionid - Fields
podcastID- Any valid parameter for
/createPodcast
unauthorized-401: you don't have the credentials to access this resourceinvalid-podcast-id-404: the podcast ID provided does not exist
- Method:
DELETE - Cookie:
sessionid - Fields
podcastID
unauthorized-401: you don't have the credentials to access this resourceinvalid-podcast-id-404: the podcast ID provided does not exist