Like Leetcode.. but you can draw out the solution before you code and submit .
- Video (Audio On): Creating a new question from the admin page (also shows role based authorization) by randomly selecting it from leetcode, then submitting the solution code to get the results :
draw-code.1.mp4
- Video (Audio On): How you can use 'Canvas Vim' to quickly draw out your solution before coding it from Codespace (Before Canvas vim Vs With Canvas vim):
draw-code.2.mp4
For more videos (errors & fix videos) on draw-code app, visit my X profile
I frequently draw out the solution before coding it - when doing dsa or developing. There was no existing tool for doing both inside one application. So I built Draw-Code.
- Typescript
- Javascript
- SQL
- Express (Node)
- React
- Postgresql
- Docker containers
- Jest
- Supertest
- Testcontainers
- Tailwindcss
-
Drawboard (Special Feature: Canvas vim)
- Choose a Tool : You can use Rectangle, Circle, Line, Pencil (freehand) - Choose one tool and start drawing
- Choose a color : You can choose from the Color palette - Red/Green/Blue/Yellow
- Draw : Draw it out by clicking and dragging your Mouse. You can draw from within the canvas, go out of it and come back again without releasing the mouse to resume the drawing in the same flow.
- Undo, Redo : Undo or Redo the changes
- X : Clear the canvas
- Canvas vim : Keep your Left hand on the keyboard to switch between the shapes on the go (fasttt) using Keyboard shortcuts. Right hand to draw using Mouse.
-
Drawboard resizer, Codespace resizer : Resize the drawboard horizontally if you need more space for drawing. Resize the Result box in the Codespace vertically to view the Test case results without scrolling. Resize the Codespace horizontally to code with a wider view.
-
Coding space
- Code editor : Write your code - Submit - get the Result/Error at the Resultbox.
- Code judge engines for each language : Containerized Coding judges with restricted resources for executing user submitted code. (Malicious code can't do harm to the host)
- Result Box : Case results or Error messages are shown. You can find out the succeeded or failed test cases from here. Timeout Error if you used an infinite loop, Out of Memory error if alloted memory exceeded. Other Errors based on the code - are all shown here.
-
Signin with Google : OAuth authentication of user.
-
Custom Jwt with Rotating Refresh token system : Short lived Jwts for authenticating the user in subsequent requests after login. Long lived Refresh tokens for recreating the jwts when expired. Rotation of Refresh tokens for adding more security if hacker got hands on a valid Refresh token (rare but can't take the chance).
-
Administrator : Add questions from the admin page (only for authorized people -> jwt body object's role=admin property)
You can see (everything) the process from the start - demonstrations, bug encounters & fixes at my X profile
Note : Everything is handcrafted (for learning purposes) - no AI code cut & paste involved, no Roughjs or other libraries for canvas involved. Likewise no Codemirror library involved for the code editor -> For making a lean software.
- Drawboard :
- Learnt about 2d Context, Relation between Device pixel ratio (DPR), Physical pixels, css pixels and canvas pixels - for improving pixel clarity, and syncing coordinates of canvas with css by scaling.
- Created pixel images for the shape buttons (Normal mode, Highlight mode, Selected mode). Used event handlers (MouseDown, MouseUp, MouseMove, MouseLeave) and useRef hooks to change the button modes on usage - pixel perfectly. No useState hook is used in the Canvas component.
- Learnt about requestAnimationFrame web api to draw Rectangles, Circles, Lines (Eg; When you press and drag to create rectangle, you need to delete each rectangle you had drawn previously so that the new rectangle at a new mouse position can be seen - before you release the mouse to draw it)
- Freehand drawing implemented by connecting lines.
- Undo-Redo implemented. At first, I stored the canvas images after each drawing inside an Array, so that i could undo-redo linearly (not an undo-redo tree). But localstorage only allows 5MB max, and one pixel stores 4 bytes ie; 1920*1080 canvas means approx. 8mb. Can't be done. So I stored Vectors (Drawing coordinates) of each drawing inside Undo-Redo array. That's it.
- When Undo-ing, I had to redraw everything from start of the array to the end. Which caused lag. So I implemented Offscreen canvas - to offload the redraws and then only render after that inside the drawing canvas - Improved performance in Undos.
- Canvas vim implemented using keydown event handlers - built with developer comfort in mind (Developers will be using Draw-code)
- Learnings :
- Canvas manipulation
- Geometry calculations in code
- Web apis
- Code Engine :
- Learnt about containerization. Used docker to containerize Coding judges. Created Docker images using Dockerfile, ran the containers using the runtime config ie; docker-compose.yaml. Used Docker compose cli to run the judges.
- Created custom function for deep comparing User's result object (object Array or object Object, or anything else) with the Solution object. Here : Custom functions
- Learnings :
- Containerization
- Debugging & fixing errors
- Node Buffers, Regex (String manipulation)
- Async Js (Promises), JS event loop system (Callstack, Webapis, Microtask queue, Macrotask queue, Event loop)
- Node child processes (Pipes, Forking, File Descriptor inheritance)
- Server error handling (To not let it crash when error happens)
- Custom Jwt with Rotating Refresh token System :
- Learnt about JWT, SHA256 hashing, HMAC, and used a containerized Postgresql to store the Refresh token.
- All the cases of hacking are tested manually using Curl cli tool.
- Implemented an Absolute expiry for the Refresh token chain so that even if a hacker gets a valid RT, but the user is idle, then the Refresh token chain will expire after the abs.exp time. So that hacker can't rotate the refresh token infinitely.
- If a refresh token is stolen, after the user or the hacker rotates the refresh token, then the subsequent refresh token rotation request would cause every Refresh tokens of that user's device (hacker copied one of it) to be revoked. Thus logging the user and hacker out.
- Refresh tokens are stored in httpOnly cookies to disable js accessing the cookie, and with SameSite=Lax to prevent CSRF, and jwt is in client memory.
- Learnings :
- OAuth
- Jwt signing (HMAC) using SHA256 hashing algorithm
- Refresh tokens (with Rotation)
- Manual testing using Curl
- Administrator :
- Created a CRUD application for Adding, Deleting, Updating, Reading Questions.
- In the frontend, used useState hooks, useSearchParams hook (react router) for Pagination, useParams hook for retrieving the route parameters, useEffects for adding after effects, useNavigation for navigating programmatically.
- In the backend, created beautiful Restful API end points for CRUD operations.
- Learnings :
- Pagination (using query parameters)
- React hooks
- Error handling in React (using custom Error handler Class component)
- Commonjs and ES modules usage
- Homepage & Other Components :
- Created Svgs for the Homepage
- Created Custom hook for datafetching from secure routes (If invalid/expired jwt, then would refetch at a refreshtoken end point to get a new jwt and a refresh token - if the current refresh token is valid. If the current RT is not valid, then logsout the user using useState hooks). Created utility button components for repeated usage. Here are Custom hooks and Utility components
- Learnings :
- Custom hooks
- Utility components
- Testing :
- Integration testing (Backend and Database): Supertest api testing library used inside Jest testing framework to give input into the Backend which communicates with the DB inside testcontainer. Output is asserted and if the assertions are true, then tests are passed.
- Learnings :
- Automated integration testing
- Testing with containers
- Configurations :
- Created a Monorepo setup using npm workspaces (To share node modules between frontend app and backend app - which reduces space, and improves speed of first time project setups).
- Configured Jest, Typescript (tsconfig -> allowJs allows Js modules to be imported inside TS modules)
- Learnings :
- Giving compile time typesafety for JS functions using JSDocs (When importing functions from JS modules into TS)
- Self-Hosting :
- Rented a VPS, used a Reverse proxy (Caddy) to direct requests to the drawcode app.
- Deployed Postgresql container and Code judge containers onto the VPS.
- Used pm2 for keeping the drawcode server as a long running process.
- Semi-automated CI/CD by manually pulling updated code from GitHub to the VPS.
- Learnings :
- SSH, DNS, Name servers.
- Reverse proxy config (Caddy)
- Working of TLS certificates and the TLS handshake used to establish HTTPS.
That's it.. That was the process.
- I had used localstorage for storing the undo-redo array which forced me to store vectors (Drawn shapes' coordinate data) instead of imageData in it - to restrict the memory usage (As Local storage maximum size is 5MB). But it reduces undo performance slightly when there are large single freehand drawings (even though i used offscreenCanvas). I would improve this by storing image data in the undo-redo array- but in indexed db instead of local storage.
- I will be implementing Vim motions in the Coding space too.
- Clone the repo into a directory
npm installin the project root directory to install the required dependencies for both frontend and backend workspaces (Monorepo setup).npm run devfrom the project root directory to run the backend and frontend (Vite) servers.- Install docker. Then, cd into the
postgresqldirectory at project root directory, and dodocker compose upto spin up the Postgresql container and adminer (Admin panel for Db) - Open a browser, and go to http://localhost:5173/ - to run the app locally.
Just let me know if there are any bugs.. And Contributions are welcome.