├── .gitignore ├── routes ├── health.js ├── pattern.routes.js └── project.routes.js ├── models ├── Project.js └── Pattern.js ├── package.json ├── services └── ravelryService.js ├── public ├── js │ └── main.js └── css │ └── style.css ├── README.md ├── index.js └── views └── index.pug /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | -------------------------------------------------------------------------------- /routes/health.js: -------------------------------------------------------------------------------- 1 | import {Router} from 'express' 2 | 3 | export const healthRouter = new Router(); 4 | 5 | healthRouter.get('/', (req, res) => { 6 | res.status(200).json({ 7 | 'status': 'ok' 8 | }) 9 | }); -------------------------------------------------------------------------------- /models/Project.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const projectSchema = new mongoose.Schema({ 4 | saveToRavelry: Boolean, 5 | projectDetails: { 6 | craft_id: { 7 | type: Number, 8 | default: 2 9 | }, 10 | made_for: String, 11 | name: { 12 | type: String, 13 | required: true, 14 | unique: true 15 | }, 16 | notes: String 17 | } 18 | }); 19 | 20 | export default mongoose.model('Project', projectSchema) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "capstone-backend", 3 | "version": "1.0.0", 4 | "description": "API for Per Scholas capstone", 5 | "license": "ISC", 6 | "author": "Rebecca", 7 | "type": "module", 8 | "main": "index.js", 9 | "scripts": { 10 | "dev": "nodemon index.js", 11 | "start": "node index.js" 12 | }, 13 | "dependencies": { 14 | "axios": "^1.8.4", 15 | "cors": "^2.8.5", 16 | "dotenv": "^16.4.7", 17 | "express": "^4.21.2", 18 | "helmet": "^8.0.0", 19 | "mongoose": "^8.12.1", 20 | "pug": "^3.0.3" 21 | }, 22 | "devDependencies": { 23 | "morgan": "^1.10.0", 24 | "nodemon": "^3.1.9" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /models/Pattern.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const patternSchema = new mongoose.Schema({ 4 | foot_circ: { 5 | type: Number, 6 | required: true 7 | }, 8 | foot_length: { 9 | type: Number, 10 | required: true 11 | }, 12 | thigh_circ: { 13 | type: Number, 14 | required: true 15 | }, 16 | sock_length: { 17 | type: Number, 18 | required: true 19 | }, 20 | stitch_gauge: { 21 | type: Number, 22 | required: true 23 | }, 24 | row_gauge: { 25 | type: Number, 26 | required: true 27 | }, 28 | pattern_repeat: { 29 | type: Number, 30 | required: true 31 | }, 32 | title: { 33 | type: String, 34 | required: true 35 | }, 36 | }) 37 | 38 | export default mongoose.model('Pattern', patternSchema) -------------------------------------------------------------------------------- /services/ravelryService.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const RAV_API_BASE_URI = "https://api.ravelry.com"; 4 | const RAV_USERNAME = process.env.RAV_USERNAME; 5 | const RAV_PASSWORD = process.env.RAV_KEY 6 | 7 | export const createRavelryProject = async(projectData) => { 8 | try { 9 | const response = await axios.post( 10 | `${RAV_API_BASE_URI}/projects/DragonChilde/create.json`, 11 | projectData, 12 | { 13 | auth: { 14 | username: RAV_USERNAME, 15 | password: RAV_PASSWORD 16 | }, 17 | headers: { 18 | "Content-Type": "application/json" 19 | } 20 | } 21 | ) 22 | 23 | return response.data 24 | 25 | } catch (e) { 26 | console.error("error uploading project to Ravelry", e) 27 | 28 | throw new Error('Could not add project to Ravelry') 29 | } 30 | } -------------------------------------------------------------------------------- /public/js/main.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("DOMContentLoaded", () => { 2 | // Add copy functionality to code blocks 3 | document.querySelectorAll("pre code").forEach((block) => { 4 | block.addEventListener("click", () => { 5 | const text = block.textContent; 6 | navigator.clipboard.writeText(text).then(() => { 7 | // Show a brief "Copied!" message 8 | const message = document.createElement("div"); 9 | message.textContent = "Copied!"; 10 | message.style.cssText = ` 11 | position: fixed; 12 | top: 20px; 13 | right: 20px; 14 | background: #28a745; 15 | color: white; 16 | padding: 10px 20px; 17 | border-radius: 5px; 18 | transition: opacity 0.3s; 19 | `; 20 | document.body.appendChild(message); 21 | setTimeout(() => { 22 | message.style.opacity = "0"; 23 | setTimeout(() => message.remove(), 300); 24 | }, 2000); 25 | }); 26 | }); 27 | }); 28 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Backend API For Stocking Pattern Generator 2 | 3 | ## Description 4 | This is the backend for my Per Scholas capstone project, a web app that generates a custom-sized, toe-up, thigh-high stocking pattern. It provides API endpoints for managing patterns and projects and is integrated with my personal Ravelry account to add projects. 5 | 6 | API documentation can be found [here](https://capstone-backend-i1us.onrender.com/) 7 | 8 | ## Features 9 | - **Pattern Management:** Full CRUD functionality for creating, retrieving, updating, and deleting custom-generated knitting patterns. 10 | - **Project Tracking:** Full CRUD functionality for managing knitting projects, including project name, notes, and recipient. 11 | - **Ravelry Integration:** Add projects directly to my personal Ravelry account. 12 | 13 | ## Technologies Used 14 | - Express (backend framework) 15 | - MongoDB & Mongoose (database & ODM) 16 | - Pug (templating engine) 17 | 18 | ## Future Work 19 | - **OAuth for Ravelry:** Implement authentication so users can sign in and save projects to their own Ravelry accounts. 20 | - **User Authentication:** Implement user accounts with signup and login functionality. 21 | - **Frontend Integration:** Connect project management endpoints to the frontend to allow users to create, update, and delete projects from the UI. 22 | 23 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 3 | "Helvetica Neue", Arial, sans-serif; 4 | line-height: 1.6; 5 | max-width: 1000px; 6 | margin: 0 auto; 7 | padding: 20px; 8 | } 9 | 10 | .api-title { 11 | color: #2c3e50; 12 | border-bottom: 2px solid #3498db; 13 | padding-bottom: 10px; 14 | } 15 | 16 | .endpoint { 17 | background: #f8f9fa; 18 | padding: 15px; 19 | margin: 10px 0; 20 | border-radius: 5px; 21 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); 22 | } 23 | 24 | .method { 25 | display: inline-block; 26 | padding: 4px 8px; 27 | border-radius: 4px; 28 | color: white; 29 | font-weight: bold; 30 | margin-right: 10px; 31 | } 32 | 33 | .get { 34 | background: #28a745; 35 | } 36 | .post { 37 | background: #007bff; 38 | } 39 | .patch { 40 | background: #ffc107; 41 | color: #000; 42 | } 43 | .delete { 44 | background: #dc3545; 45 | } 46 | 47 | code { 48 | background: #e9ecef; 49 | padding: 2px 5px; 50 | border-radius: 3px; 51 | font-family: "Courier New", Courier, monospace; 52 | } 53 | 54 | pre { 55 | background: #f8f9fa; 56 | padding: 15px; 57 | border-radius: 5px; 58 | overflow-x: auto; 59 | } 60 | 61 | pre:hover { 62 | cursor: pointer; 63 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import morgan from 'morgan'; 3 | import helmet from 'helmet'; 4 | import dotenv from 'dotenv'; 5 | import cors from 'cors'; 6 | import mongoose from 'mongoose'; 7 | 8 | import { healthRouter } from './routes/health.js'; 9 | import projectRouter from './routes/project.routes.js'; 10 | import patternRouter from './routes/pattern.routes.js'; 11 | 12 | dotenv.config() //configures .env to be usable in our project 13 | // console.log(process.env.MONGODB_URI) 14 | 15 | //connect to MongoDB 16 | await mongoose.connect(process.env.MONGODB_URI) 17 | .then(() => console.log("Connected to MongoDB")) 18 | .catch(e => console.error(e)) 19 | 20 | 21 | const PORT = process.env.PORT || 4000; 22 | 23 | const app = express(); 24 | 25 | 26 | // set view engine 27 | app.set('views', './views') 28 | app.set('view engine', 'pug') 29 | 30 | 31 | // middleware 32 | app.use(express.static('./public')) //serves static files and makes them available to the templates 33 | app.use(express.json()) //built in express middleware that parses json in req into req.body 34 | app.use(express.urlencoded({extended: true})) //built in express middleware that will parse info from url 35 | app.use(morgan('dev')); //logging middleware 36 | app.use(helmet()); // security middleware that makes server more secure, sets a bunch of security headers by default 37 | app.use(cors()); //enables full-stack app in development can be turned off in production or can be added to individual routes as middleware 38 | 39 | 40 | 41 | // routes 42 | app.get('/', (req, res) => { 43 | res.render('index'); 44 | }) 45 | 46 | app.use('/api/health', healthRouter) 47 | app.use('/api/patterns', patternRouter) 48 | app.use('/api/projects', projectRouter) 49 | 50 | //global error handling middleware 51 | app.use((err, _req, res, next) => { 52 | console.error(err); 53 | res.status(500).send("Seems like we messed up somewhere..."); 54 | }); 55 | 56 | app.listen(PORT, () => { 57 | console.log(`Sever is running on port: ${PORT}`) 58 | }) -------------------------------------------------------------------------------- /routes/pattern.routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import Pattern from "../models/Pattern.js"; 3 | 4 | const patternRouter = new Router(); 5 | 6 | patternRouter 7 | .route('/') 8 | /** 9 | * GET /api/patterns returns all patterns 10 | */ 11 | .get(async(req, res) => { 12 | try { 13 | const patterns = await Pattern.find(); 14 | 15 | if(patterns.length === 0) res.status(404).json({message: "Patterns not found"}) 16 | else res.json(patterns) 17 | } catch (e) { 18 | console.error(e) 19 | 20 | res.status(400).json({ 21 | error: e.name, 22 | message: e.message 23 | }) 24 | } 25 | }) 26 | /** 27 | * POST /api/patterns creates a new pattern 28 | */ 29 | .post(async(req, res) => { 30 | try { 31 | const newPattern = new Pattern(req.body) 32 | await newPattern.save() 33 | 34 | res.status(201).json(newPattern) 35 | } catch (e) { 36 | console.error(e) 37 | 38 | res.status(400).json({ 39 | error: e.name, 40 | message: e.message 41 | }) 42 | } 43 | }) 44 | 45 | patternRouter 46 | .route('/:patternId') 47 | /** 48 | * GET /api/patterns/:patternId returns a single pattern by id 49 | */ 50 | .get(async(req, res) => { 51 | try { 52 | const pattern = await Pattern.findById(req.params.patternId) 53 | 54 | if(!pattern) res.status(404).json({message: 'Pattern not found'}) 55 | else res.json(pattern) 56 | } catch (e) { 57 | console.error(e) 58 | 59 | res.status(400).json({ 60 | error: e.name, 61 | message: e.message 62 | }) 63 | } 64 | 65 | }) 66 | /** 67 | * PATCH /api/patterns/:patternId updates a pattern by id 68 | */ 69 | .patch(async(req, res) => { 70 | try { 71 | const pattern = await Pattern.findByIdAndUpdate(req.params.patternId, req.body, {new: true}) 72 | 73 | if(!pattern) res.status(404).json({message: 'Pattern not found'}) 74 | else res.json(pattern) 75 | 76 | } catch (e) { 77 | console.error(e) 78 | 79 | res.status(400).json({ 80 | error: e.name, 81 | message: e.message 82 | }) 83 | } 84 | }) 85 | /** 86 | * DELETE /api/patterns/:patternId deletes a pattern by id 87 | */ 88 | .delete(async(req, res) => { 89 | try { 90 | const pattern = await Pattern.findByIdAndDelete(req.params.patternId) 91 | 92 | if(!pattern) res.status(404).json({message: 'Pattern not found'}) 93 | else res.send("Pattern successfully deleted") 94 | } catch (e) { 95 | console.error(e) 96 | 97 | res.status(400).json({ 98 | error: e.name, 99 | message: e.message 100 | }) 101 | } 102 | }) 103 | 104 | export default patternRouter; -------------------------------------------------------------------------------- /routes/project.routes.js: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | 3 | import Project from "../models/Project.js"; 4 | 5 | import { createRavelryProject } from "../services/ravelryService.js"; 6 | 7 | const projectRouter = new Router(); 8 | 9 | projectRouter 10 | .route('/') 11 | /** 12 | * GET /api/projects returns all projects 13 | */ 14 | .get(async(req, res) => { 15 | try { 16 | const projects = await Project.find(); 17 | 18 | if(projects.length === 0) res.status(404).json({message: "projects not found"}) 19 | else res.json(projects) 20 | } catch (e) { 21 | console.error(e) 22 | 23 | res.status(400).json({ 24 | error: e.name, 25 | message: e.message 26 | }) 27 | } 28 | }) 29 | /** 30 | * POST /api/projects creates a new project 31 | */ 32 | .post(async(req, res) => { 33 | try { 34 | const {saveToRavelry, ...projectDetails} = req.body 35 | const newProject = new Project(projectDetails) 36 | 37 | let ravelryResponse; 38 | if(saveToRavelry){ 39 | ravelryResponse = await createRavelryProject(newProject.projectDetails) 40 | } 41 | 42 | await newProject.save() 43 | 44 | res.status(201).json({project: newProject, ravelry: ravelryResponse}) 45 | } catch (e) { 46 | console.error(e) 47 | 48 | res.status(400).json({ 49 | error: e.name, 50 | message: e.message 51 | }) 52 | } 53 | }) 54 | 55 | projectRouter 56 | .route('/:projectId') 57 | /** 58 | * GET /api/projects/:projectId returns a single project by id 59 | */ 60 | .get(async(req, res) => { 61 | try { 62 | const project = await Project.findById(req.params.projectId) 63 | 64 | if(!project) res.status(404).json({message: 'Project not found'}) 65 | else res.json(project) 66 | } catch (e) { 67 | console.error(e) 68 | 69 | res.status(400).json({ 70 | error: e.name, 71 | message: e.message 72 | }) 73 | } 74 | 75 | }) 76 | /** 77 | * PATCH /api/projects/:projectId update a project by id 78 | */ 79 | .patch(async(req, res) => { 80 | try { 81 | const project = await Project.findByIdAndUpdate(req.params.projectId, req.body, {new: true}) 82 | 83 | if(!project) res.status(404).json({message: 'Project not found'}) 84 | else res.json(project) 85 | 86 | } catch (e) { 87 | console.error(e) 88 | 89 | res.status(400).json({ 90 | error: e.name, 91 | message: e.message 92 | }) 93 | } 94 | }) 95 | /** 96 | * DELETE /api/projects/:projectId deletes a project by id 97 | */ 98 | .delete(async(req, res) => { 99 | try { 100 | const project = await Project.findByIdAndDelete(req.params.projectId) 101 | 102 | if(!project) res.status(404).json({message: 'Project not found'}) 103 | else res.send("Project successfully deleted") 104 | } catch (e) { 105 | console.error(e) 106 | 107 | res.status(400).json({ 108 | error: e.name, 109 | message: e.message 110 | }) 111 | } 112 | }) 113 | 114 | export default projectRouter; -------------------------------------------------------------------------------- /views/index.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang="en") 3 | head 4 | title API Documentation 5 | link(rel="stylesheet" href="/css/style.css") 6 | 7 | body 8 | h1.api-title API Documentation 9 | 10 | h2 Base URL 11 | p All endpoints are prefixed with: 12 | code https://capstone-backend-i1us.onrender.com/api 13 | 14 | h2 Authentication 15 | p Currently, no authentication is required for the API endpoints. 16 | 17 | h2 Endpoints 18 | 19 | h3 Health Check 20 | .endpoint 21 | span.method.get GET 22 | code /health 23 | p Returns the API health status. 24 | h4 Response 25 | pre 26 | code 27 | | { 28 | | "status": "OK" 29 | | } 30 | 31 | h3 Patterns 32 | 33 | .endpoint 34 | span.method.get GET 35 | code /patterns 36 | p Retrieve all patterns 37 | h4 Response 38 | pre 39 | code 40 | | [ 41 | | { 42 | | "_id": "string", 43 | | "title": "string", 44 | | "foot_circ": "number", 45 | | "foot_length": "number", 46 | | "thigh_circ": "number", 47 | | "sock_length": "number", 48 | | "stitch_gauge": "number", 49 | | "row_gauge": "number", 50 | | "pattern_repeat": "number" 51 | | } 52 | | ] 53 | 54 | .endpoint 55 | span.method.get GET 56 | code /patterns/:patternId 57 | p Retrieve a specific pattern by ID 58 | h4 Response 59 | pre 60 | code 61 | | { 62 | | "_id": "string", 63 | | "title": "string", 64 | | "foot_circ": "number", 65 | | "foot_length": "number", 66 | | "thigh_circ": "number", 67 | | "sock_length": "number", 68 | | "stitch_gauge": "number", 69 | | "row_gauge": "number", 70 | | "pattern_repeat": "number" 71 | | } 72 | 73 | .endpoint 74 | span.method.post POST 75 | code /patterns 76 | p Create a new pattern 77 | h4 Request Body 78 | table 79 | tr 80 | th Field 81 | th Type 82 | th Required 83 | th Description 84 | tr 85 | td title 86 | td string 87 | td yes 88 | td title of the pattern 89 | tr 90 | td foot_circ 91 | td number 92 | td yes 93 | td circumference of the foot (in inches) 94 | tr 95 | td foot_length 96 | td number 97 | td yes 98 | td foot length (in inches) 99 | tr 100 | td thigh_circ 101 | td number 102 | td yes 103 | td circumference at the tallest point of the sock (in inches) 104 | tr 105 | td sock_length 106 | td number 107 | td yes 108 | td desired length of the sock (in inches) 109 | tr 110 | td stitch_gauge 111 | td number 112 | td yes 113 | td number of stitches per inch 114 | tr 115 | td row_gauge 116 | td number 117 | td yes 118 | td number of rows per inch 119 | tr 120 | td pattern_repeat 121 | td number 122 | td yes 123 | td pattern repeat length in stitches 124 | h4 Response 125 | pre 126 | code 127 | | { 128 | | "_id": "string", 129 | | "title": "string", 130 | | "foot_circ": "number", 131 | | "foot_length": "number", 132 | | "thigh_circ": "number", 133 | | "sock_length": "number", 134 | | "stitch_gauge": "number", 135 | | "row_gauge": "number", 136 | | "pattern_repeat": "number" 137 | | } 138 | 139 | .endpoint 140 | span.method.patch PATCH 141 | code /patterns/:patternId 142 | p Update an existing pattern 143 | h4 Request Body 144 | table 145 | tr 146 | th Field 147 | th Type 148 | th Required 149 | th Description 150 | tr 151 | td title 152 | td string 153 | td yes 154 | td title of the pattern 155 | tr 156 | td foot_circ 157 | td number 158 | td yes 159 | td circumference of the foot (in inches) 160 | tr 161 | td foot_length 162 | td number 163 | td yes 164 | td foot length (in inches) 165 | tr 166 | td thigh_circ 167 | td number 168 | td yes 169 | td circumference at the tallest point of the sock (in inches) 170 | tr 171 | td sock_length 172 | td number 173 | td yes 174 | td desired length of the sock (in inches) 175 | tr 176 | td stitch_gauge 177 | td number 178 | td yes 179 | td number of stitches per inch 180 | tr 181 | td row_gauge 182 | td number 183 | td yes 184 | td number of rows per inch 185 | tr 186 | td pattern_repeat 187 | td number 188 | td yes 189 | td pattern repeat length in stitches 190 | h4 Response 191 | pre 192 | code 193 | | { 194 | | "_id": "string", 195 | | "title": "string", 196 | | "foot_circ": "number", 197 | | "foot_length": "number", 198 | | "thigh_circ": "number", 199 | | "sock_length": "number", 200 | | "stitch_gauge": "number", 201 | | "row_gauge": "number", 202 | | "pattern_repeat": "number" 203 | | } 204 | 205 | .endpoint 206 | span.method.delete DELETE 207 | code /patterns/:patternId 208 | p Delete a pattern 209 | h4 Response 210 | pre 211 | code "Pattern successfully deleted" 212 | 213 | h3 Projects 214 | 215 | .endpoint 216 | span.method.get GET 217 | code /projects 218 | p Retrieve all projects 219 | h4 Response 220 | pre 221 | code 222 | | [ 223 | | { 224 | | "_id": "string", 225 | | "saveToRavlery": "boolean", 226 | | "projectDetails": { 227 | | "craft_id": 2, 228 | | "made_for": "string", 229 | | "name": "string", 230 | | "notes:" "string" 231 | | } 232 | | } 233 | | ] 234 | 235 | .endpoint 236 | span.method.get GET 237 | code /projects/:projectId 238 | p Retrieve a specific project by ID 239 | h4 Response 240 | pre 241 | code 242 | | { 243 | | "_id": "string", 244 | | "saveToRavlery": "boolean", 245 | | "projectDetails": { 246 | | "craft_id": 2, 247 | | "made_for": "string", 248 | | "name": "string", 249 | | "notes:" "string" 250 | | } 251 | | } 252 | 253 | .endpoint 254 | span.method.post POST 255 | code /projects 256 | p Create a new project 257 | h4 Request Body 258 | table 259 | tr 260 | th Field 261 | th Type 262 | th Required 263 | th Description 264 | tr 265 | td saveToRavelry 266 | td boolean 267 | td yes 268 | td whether or not to save project to ravelry 269 | tr 270 | td projectDetails 271 | td object 272 | td yes 273 | td an object with the name, made_for, and notes properties 274 | tr 275 | td projectDetails.name 276 | td string 277 | td yes 278 | td the name of the project 279 | tr 280 | td projectDetails.made_for 281 | td string 282 | td no 283 | td the name of the project recipient 284 | tr 285 | td projectDetails.notes 286 | td string 287 | td no 288 | td notes for the project 289 | h4 Response 290 | p Note: the response will only contain the ravelry object if 291 | code saveToRavlery 292 | span is set to 293 | code true 294 | pre 295 | code 296 | | { 297 | | "project": { 298 | | "_id": "string", 299 | | "projectDetails": { 300 | | "name": "string", 301 | | "craft_id": 2, 302 | | "made_for": "string", 303 | | "notes": "string" 304 | | } 305 | | }, 306 | | "ravelry": { 307 | | "project": { 308 | | "comments_count": 0, 309 | | "completed": null, 310 | | "craft_id": 2, 311 | | "created_at": "date", 312 | | "favorites_count": 0, 313 | | "id": "number", 314 | | "made_for": "string" || null, 315 | | "made_for_user_id": null, 316 | | "name": "string", 317 | | "pattern_id": null, 318 | | "permalink": "string", 319 | | "progress": null, 320 | | "project_status_changed": "date", 321 | | "project_status_id": null, 322 | | "rating": null, 323 | | "size": null, 324 | | "started": null, 325 | | "updated_at": "date", 326 | | "user_id": "number", 327 | | "links": { 328 | | "self": { 329 | | "href": "string" 330 | | } 331 | | }, 332 | | "personal_attributes": null, 333 | | "pattern_name": null, 334 | | "craft_name": "Knitting", 335 | | "status_name": null, 336 | | "tag_names": [], 337 | | "notes_html": null, 338 | | "notes": "string" || null, 339 | | "needle_sizes": [], 340 | | "photos_count": 0, 341 | | "ends_per_inch": null, 342 | | "picks_per_inch": null, 343 | | "gauge": null, 344 | | "row_gauge": null, 345 | | "gauge_repeats": null, 346 | | "gauge_divisor": null, 347 | | "gauge_pattern": null, 348 | | "private_notes_html": null, 349 | | "private_notes": null, 350 | | "completed_day_set": false, 351 | | "started_day_set": false, 352 | | "photos": [], 353 | | "packs": [], 354 | | "user": { 355 | | "id": "number", 356 | | "username": "string", 357 | | "tiny_photo_url": "string", 358 | | "small_photo_url": "string", 359 | | "photo_url": "string" 360 | | }, 361 | | "tools": [] 362 | | }, 363 | | "user": { 364 | | "id": "number", 365 | | "username": "string", 366 | | "tiny_photo_url": "string", 367 | | "small_photo_url": "string", 368 | | "photo_url": "string" 369 | | } 370 | | } 371 | | } 372 | 373 | .endpoint 374 | span.method.patch PATCH 375 | code /projects/:projectId 376 | p Update an existing project 377 | h4 Request Body 378 | table 379 | tr 380 | th Field 381 | th Type 382 | th Required 383 | th Description 384 | tr 385 | td name 386 | td string 387 | td yes 388 | td the name of the project 389 | tr 390 | td made_for 391 | td string 392 | td no 393 | td the name of the project recipient 394 | tr 395 | td notes 396 | td string 397 | td no 398 | td notes for the project 399 | h4 Response 400 | pre 401 | code 402 | | { 403 | | "_id": "string", 404 | | "name": "string", 405 | | "made_for": "string", 406 | | "notes": "string", 407 | | "craft_id": 2 408 | | } 409 | 410 | .endpoint 411 | span.method.delete DELETE 412 | code /projects/:projectId 413 | p Delete a project by id 414 | h4 Response 415 | pre 416 | code "Project successfully deleted" 417 | 418 | h2 Error Responses 419 | p The API returns standard HTTP status codes and JSON error messages: 420 | pre 421 | code 422 | | { 423 | | "error": "Error Type", 424 | | "message": "Error description" 425 | | } 426 | 427 | script(src="/js/main.js") --------------------------------------------------------------------------------