diff --git a/docs/Components.md b/docs/Components.md new file mode 100644 index 0000000..9467dcc --- /dev/null +++ b/docs/Components.md @@ -0,0 +1,15 @@ +## Components & Data + +### `public/js/teamData.js` +- Exports `teamInfo` array containing team member metadata used by `aboutRouter`. +- Shape: +```js +[ + { id: number, name: string, role: string, bio: string, type: string } +] +``` +- Usage: +```js +const teamInfo = require('../public/js/teamData'); +res.render('member', { data: teamInfo[0], categories }); +``` \ No newline at end of file diff --git a/docs/Controllers.md b/docs/Controllers.md new file mode 100644 index 0000000..04a00e6 --- /dev/null +++ b/docs/Controllers.md @@ -0,0 +1,44 @@ +## Controllers Reference + +### `controllers/userController.js` + +- `signUp(req, res)` + - Input: `firstName, lastName, email, password` (body) + - Validates input, ensures email uniqueness via `userModel.findUserByEmail`, creates user via `userModel.createUser`, initializes session, redirects to `/dashboard`. + +- `login(req, res)` + - Input: `email, password` (body) + - Validates credentials using `bcrypt.compare` and `userModel.findUserByEmail`, sets session, redirects to `/dashboard`, or returns 401 on failure. + +- `logout(req, res)` + - Destroys the session and redirects to `/`. + +Example usage in a router: +```js +const express = require('express'); +const router = express.Router(); +const userController = require('../controllers/userController'); + +router.post('/signup', userController.signUp); +router.post('/login', userController.login); +router.post('/logout', userController.logout); +``` + +### `controllers/fileUploadController.js` + +- `uploadImage` (Multer instance) + - Use as `uploadImage.single('image')` in a route to accept an uploaded file. + - Side effects: populates `req.body.imagePathForDb` and `req.body.thumbnailPathForDb` with paths to store in DB. + +- `generateAndUploadThumbnail(sourceImagePath, targetImagePath)` + - Creates a 200x200 thumbnail using Sharp. + +Example usage in a route: +```js +const { uploadImage, generateAndUploadThumbnail } = require('../controllers/fileUploadController'); +const { ensureUserIsLoggedIn } = require('../middleware/authentication'); + +router.post('/submit', uploadImage.single('image'), ensureUserIsLoggedIn, async (req, res) => { + // use req.body.imagePathForDb and req.body.thumbnailPathForDb +}); +``` \ No newline at end of file diff --git a/docs/Middleware.md b/docs/Middleware.md new file mode 100644 index 0000000..14ebbd6 --- /dev/null +++ b/docs/Middleware.md @@ -0,0 +1,59 @@ +## Middleware Reference + +All middleware are CommonJS modules exported from files in `application/middleware/`. + +### `authentication.ensureUserIsLoggedIn(req, res, next)` +- Redirects to `/` if `req.session.userID` is missing; otherwise calls `next()`. +- Use to protect routes that require authentication. +- Example: + ```js + const { ensureUserIsLoggedIn } = require('../middleware/authentication'); + app.get('/dashboard', ensureUserIsLoggedIn, handler); + ``` + +### `getCategories.getCategories(req, res, next)` +- Returns `Promise>` with category names. +- Intended for navbar usage. + +### `getCategories.getCategoriesWithPictures(req, res, next)` +- Returns `Promise>` for homepage display. + +### `posts.getPostInfo(req, res, next)` +- Reads `req.params.postId` and returns a single post joined with seller data, or `null` if not found. + +### `posts.getUserPosts(userID, req, res, next)` +- Returns an array of posts for the given `userID`, ordered by `postDate DESC`. + +### `posts.createPost(title, course, category, price, location, description, image, thumbnail, userID, req, res, next)` +- Inserts a new post. Handles presence/absence of `course`. Resolves on success, rejects on error. + +### `posts.deletePost(postID, req, res, next)` +- Deletes a post by id. Resolves on success, rejects on error. + +### `posts.updatePost(title, course, category, price, location, description, postID, req, res, next)` +- Updates a post record. Returns 404 on errors via response. + +### `messages.getUserMessages(userID, req, res, next)` +- Returns a user’s received messages with item and sender info, ordered by `sendTime DESC`. + +### `messages.getMessageDetails(req, res, next)` +- Expects `req.params.messageId`. On success sets `res.locals.message` and calls `next()`; 404 if not found. + +### `messages.messageUser(req, res, next)` +- Expects `req.params.postId`. On success sets `res.locals.postDetails` and calls `next()`. + +### `messages.sendMessage(recipientID, senderID, content, postID, req, res, next)` +- Inserts a message. Resolves on success, throws on error. + +### `messages.deleteMessage(msgID, req, res, next)` +- Deletes a message by id. Resolves on success, rejects on error. + +### `search.searchItems(req, res, next)` +- Reads `query` and `category` from `req.query`. Sets `res.locals.items` and `res.locals.message` then calls `next()`. +- Supports sorting via `sort` query param and returns curated items if there are no results. + +### `search.getRecentPosts(req, res, next)` +- Sets `res.locals.recentPosts` to the last 9 authorized, unsold items. + +### `dashboard.fetchUserInfo(userID, req, res, next)` +- Returns an array with `{ firstName, lastName, email, bio }` for the given user. \ No newline at end of file diff --git a/docs/Models.md b/docs/Models.md new file mode 100644 index 0000000..7b6cc25 --- /dev/null +++ b/docs/Models.md @@ -0,0 +1,12 @@ +## Models Reference + +### `models/userModel.js` + +- `createUser(firstName, lastName, email, password) -> Promise` + - Hashes `password` with bcrypt, inserts a new user, and returns the new `userID`. + +- `findUserByEmail(email) -> Promise` + - Returns the user row if found, otherwise `null`. + +### Database Connection +- `conf/database.js` creates a mysql2 connection pool using env vars: `DB_HOST`, `DB_USER`, `DB_PASSWORD`, `DB_NAME`. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..31dde0d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,16 @@ +# Project Documentation + +Welcome. This folder contains comprehensive documentation for all public APIs, functions, and components in this codebase. + +- REST API: see `REST_API.md` +- Routes overview: see `Routes.md` +- Middleware reference: see `Middleware.md` +- Controllers reference: see `Controllers.md` +- Models reference: see `Models.md` +- OpenAPI spec (for tooling): see `openapi.yaml` + +Quick start +- Base URL: `http://localhost:3000` +- Auth base: `http://localhost:3000/auth` +- Search base: `http://localhost:3000/search` +- HTML pages are rendered via EJS; JSON APIs are noted explicitly in the docs. \ No newline at end of file diff --git a/docs/REST_API.md b/docs/REST_API.md new file mode 100644 index 0000000..12993a3 --- /dev/null +++ b/docs/REST_API.md @@ -0,0 +1,112 @@ +## REST API + +Base URL: `http://localhost:3000` + +Authentication & Sessions +- Session cookie is set after successful login or signup. Some endpoints require a valid session. +- Content types supported: `application/json` and `application/x-www-form-urlencoded` unless otherwise stated. + +### Auth + +- POST `/auth/signup` + - Body: `{ firstName, lastName, email, password }` + - 302 → Redirect to `/dashboard` on success + - 400 → Missing fields or email in use; 500 on DB error + - Example: + ```bash + curl -i -X POST http://localhost:3000/auth/signup \ + -H 'Content-Type: application/json' \ + -d '{"firstName":"Ada","lastName":"Lovelace","email":"ada@example.com","password":"Secret123!"}' + ``` + +- POST `/auth/login` + - Body: `{ email, password }` + - 302 → Redirect to `/dashboard` on success; sets session + - 401 → Invalid credentials; 500 on DB error + - Example: + ```bash + curl -i -X POST http://localhost:3000/auth/login \ + -H 'Content-Type: application/json' \ + -d '{"email":"ada@example.com","password":"Secret123!"}' + ``` + +- POST `/auth/logout` + - Ends the session; clears cookie if present + - 302 → Redirect to `/` + - Example: + ```bash + curl -i -X POST http://localhost:3000/auth/logout + ``` + +### Search + +- GET `/search` + - Query params: `query` (string), `category` (string or "all"), `sort` ("default"|"price_asc"|"price_desc"|"alpha_asc"|"alpha_desc"), `page` (int, default 1), `limit` (int, default 9) + - Renders HTML page `search.ejs` with paginated, sorted results + - Example: + ```bash + curl 'http://localhost:3000/search?query=laptop&category=Electronics&sort=price_desc&page=1&limit=9' + ``` + +### Posts + +- POST `/submit` (multipart/form-data, requires session) + - Fields: `title`, `course` (optional), `category` (categoryId), `price`, `location`, `description`, `image` (file) + - Behavior: stores image under `public/img/items`, generates 200x200 thumbnail under `public/img/thumbnails`, inserts post in DB + - 302 → Redirect to `/dashboard` on success; 400 on validation errors; 500 on failures + - Example: + ```bash + curl -i -X POST http://localhost:3000/submit \ + -H 'Cookie: connect.sid=YOUR_SESSION_COOKIE' \ + -F 'title=Textbook' \ + -F 'course=CSC648' \ + -F 'category=3' \ + -F 'price=25.00' \ + -F 'location=Campus' \ + -F 'description=Gently used' \ + -F 'image=@/path/to/photo.jpg' + ``` + +- DELETE `/delete-post/:id` + - Deletes the post by id; returns text + - 200 on success, 500 on failure + - Example: + ```bash + curl -i -X DELETE http://localhost:3000/delete-post/123 + ``` + +### Messaging + +- GET `/message/:postId` + - Returns JSON with post details for composing a message + - Response: `{ postDetails: { ... } }` + - Example: + ```bash + curl -s http://localhost:3000/message/42 | jq . + ``` + +- POST `/send-message` (requires session) + - Body: `{ recipientId, content, postId }` + - 200 → "Message sent successfully"; 500 on error + - Example: + ```bash + curl -i -X POST http://localhost:3000/send-message \ + -H 'Content-Type: application/json' \ + -H 'Cookie: connect.sid=YOUR_SESSION_COOKIE' \ + -d '{"recipientId":7,"content":"Is this still available?","postId":42}' + ``` + +- DELETE `/delete-message/:id` + - Deletes a message by id; returns text + - 200 on success, 500 on failure + - Example: + ```bash + curl -i -X DELETE http://localhost:3000/delete-message/987 + ``` + +### Pages (HTML) +- GET `/` → Home +- GET `/dashboard` (requires session) +- GET `/createpost` +- GET `/about`, `/about/1..6` +- GET `/members` \ No newline at end of file diff --git a/docs/Routes.md b/docs/Routes.md new file mode 100644 index 0000000..a88df34 --- /dev/null +++ b/docs/Routes.md @@ -0,0 +1,28 @@ +### Route Modules and Endpoints + +- `routes/homeRouter.js` (mounted at `/`) + - GET `/` → Renders home with recent posts and categories. + +- `routes/aboutRouter.js` (mounted at `/about`) + - GET `/about` → Renders About page with `teamInfo` and categories. + - GET `/about/1..6` → Renders specific team member page. + +- `routes/authRouter.js` (mounted at `/auth`) + - POST `/auth/signup` → Sign up. + - POST `/auth/login` → Log in. + - POST `/auth/logout` → Log out. + +- `routes/searchRouter.js` (mounted at `/search`) + - GET `/search` → Search items with query params; renders results. + +- `routes/submitRouter.js` (mounted at `/`) + - POST `/submit` → Create a new post (multipart/form-data). Requires login. + +Additional endpoints defined in `server.js` +- GET `/dashboard` → Requires login. Renders dashboard with categories, messages, and posts. +- GET `/createpost` → Renders create-post page. +- GET `/members` → Renders member listing using `teamData`. +- GET `/message/:postId` → Returns JSON `{ postDetails }` for a post. +- POST `/send-message` → Requires login. Sends a message. +- DELETE `/delete-post/:id` → Deletes a post by id. +- DELETE `/delete-message/:id` → Deletes a message by id. \ No newline at end of file diff --git a/docs/openapi.yaml b/docs/openapi.yaml new file mode 100644 index 0000000..b67bc75 --- /dev/null +++ b/docs/openapi.yaml @@ -0,0 +1,211 @@ +openapi: 3.0.3 +info: + title: Application API + version: 1.0.0 + description: Core JSON endpoints for the application. Many routes render HTML and are omitted here. +servers: + - url: http://localhost:3000 +paths: + /auth/signup: + post: + summary: Sign up + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SignUpRequest' + responses: + '302': + description: Redirect to /dashboard on success + '400': + description: Missing fields or email already registered + '500': + description: Database error + /auth/login: + post: + summary: Log in + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/LoginRequest' + responses: + '302': + description: Redirect to /dashboard on success + '401': + description: Invalid credentials + '500': + description: Database error + /auth/logout: + post: + summary: Log out + responses: + '302': + description: Redirect to / + /search: + get: + summary: Search items (renders HTML) + parameters: + - in: query + name: query + schema: + type: string + - in: query + name: category + schema: + type: string + - in: query + name: sort + schema: + type: string + enum: [default, price_asc, price_desc, alpha_asc, alpha_desc] + - in: query + name: page + schema: + type: integer + default: 1 + - in: query + name: limit + schema: + type: integer + default: 9 + responses: + '200': + description: Search results page + /message/{postId}: + get: + summary: Get post details for messaging + parameters: + - in: path + name: postId + required: true + schema: + type: integer + responses: + '200': + description: Post details + content: + application/json: + schema: + type: object + properties: + postDetails: + type: object + '404': + description: Item not found + '500': + description: Error fetching message + /send-message: + post: + summary: Send a message + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SendMessageRequest' + responses: + '200': + description: Message sent successfully + '500': + description: Internal server error + /delete-message/{id}: + delete: + summary: Delete a message + parameters: + - in: path + name: id + required: true + schema: + type: integer + responses: + '200': + description: Message deleted successfully + '500': + description: Failed to delete message + /delete-post/{id}: + delete: + summary: Delete a post + parameters: + - in: path + name: id + required: true + schema: + type: integer + responses: + '200': + description: Post deleted successfully + '500': + description: Failed to delete post + /submit: + post: + summary: Create a new post (multipart/form-data) + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + required: [title, category, price, location, description, image] + properties: + title: + type: string + course: + type: string + category: + type: integer + price: + type: number + format: float + location: + type: string + description: + type: string + image: + type: string + format: binary + responses: + '302': + description: Redirect to /dashboard on success + '400': + description: Validation error + '500': + description: Failed to submit post +components: + schemas: + SignUpRequest: + type: object + required: [firstName, lastName, email, password] + properties: + firstName: + type: string + lastName: + type: string + email: + type: string + format: email + password: + type: string + format: password + LoginRequest: + type: object + required: [email, password] + properties: + email: + type: string + format: email + password: + type: string + format: password + SendMessageRequest: + type: object + required: [recipientId, content, postId] + properties: + recipientId: + type: integer + content: + type: string + postId: + type: integer \ No newline at end of file