├── .gitignore ├── .vscode └── settings.json ├── assets └── images │ ├── appwrite.png │ ├── mongodb.png │ └── CSMS_coloured.png ├── deno.json ├── env.ts ├── import_map.json ├── database.ts ├── README.md └── main.ts /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | test.ts 3 | data.json 4 | *.lock -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "deno.enable": true 3 | } -------------------------------------------------------------------------------- /assets/images/appwrite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xditya/csms-api/main/assets/images/appwrite.png -------------------------------------------------------------------------------- /assets/images/mongodb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xditya/csms-api/main/assets/images/mongodb.png -------------------------------------------------------------------------------- /assets/images/CSMS_coloured.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xditya/csms-api/main/assets/images/CSMS_coloured.png -------------------------------------------------------------------------------- /deno.json: -------------------------------------------------------------------------------- 1 | { 2 | "importMap": "./import_map.json", 3 | "tasks": { 4 | "start": "deno run --allow-net --allow-read --allow-env --watch index.ts" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /env.ts: -------------------------------------------------------------------------------- 1 | import { config } from "std"; 2 | import { cleanEnv, url } from "envalid"; 3 | 4 | await config({ export: true }); 5 | 6 | export default cleanEnv(Deno.env.toObject(), { 7 | MONGO_URL: url(), 8 | }); 9 | -------------------------------------------------------------------------------- /import_map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "dotenv": "https://deno.land/std@0.154.0/dotenv/mod.ts", 4 | "envalid": "https://deno.land/x/envalid@0.1.2/mod.ts", 5 | "oak": "https://deno.land/x/oak@v11.1.0/mod.ts", 6 | "mongo": "https://deno.land/x/mongo@v0.31.1/mod.ts", 7 | "std": "https://deno.land/std@0.154.0/dotenv/mod.ts", 8 | "$env": "./env.ts" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /database.ts: -------------------------------------------------------------------------------- 1 | /* 2 | (c) @xditya 3 | */ 4 | 5 | import { MongoClient } from "mongo"; 6 | 7 | import config from "$env"; 8 | 9 | console.log("Connecting to MongoDB..."); 10 | const client = new MongoClient(); 11 | const MONGO_URL = new URL(config.MONGO_URL); 12 | if (!MONGO_URL.searchParams.has("authMechanism")) { 13 | MONGO_URL.searchParams.set("authMechanism", "SCRAM-SHA-1"); 14 | } 15 | try { 16 | await client.connect(MONGO_URL.href); 17 | } catch (err) { 18 | console.error("Error connecting to MongoDB", err); 19 | throw err; 20 | } 21 | const db = client.database("CSMS"); 22 | 23 | interface OrderDetails { 24 | hash: string; 25 | items: { [key: string]: number }; 26 | timestamp: string; 27 | status: "Placed" | "Done"; 28 | totalCost: number; 29 | } 30 | 31 | interface OrderDocument { 32 | email: string; 33 | orders: { [hash: string]: OrderDetails[] }; 34 | } 35 | 36 | const orders = db.collection("vending_orders"); 37 | 38 | async function getOrderByHash(email: string, hash: string) { 39 | const result = await orders.findOne({ 40 | email, 41 | [`orders.${hash}`]: { $exists: true }, 42 | }); 43 | 44 | if (!result || !result.orders[hash]) { 45 | return null; 46 | } 47 | 48 | return result.orders[hash][0]; 49 | } 50 | 51 | async function updateOrderStatus( 52 | email: string, 53 | hash: string, 54 | status: "Placed" | "Done" 55 | ) { 56 | await orders.updateOne( 57 | { 58 | email, 59 | [`orders.${hash}`]: { $exists: true }, 60 | }, 61 | { 62 | $set: { 63 | [`orders.${hash}.0.status`]: status, 64 | }, 65 | } 66 | ); 67 | } 68 | 69 | export { getOrderByHash, updateOrderStatus }; 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSMS Backend API 2 | 3 | This is the backend API for the [CSMS (Campus Services Management System)](https://github.com/xditya/CampusServicesManagementSystem) project. It provides endpoints for managing vending machine orders and monitoring system data. 4 | 5 | ## Setup 6 | 7 | 1. Clone the repository 8 | ```bash 9 | git clone https://github.com/yourusername/csms-backend.git 10 | cd csms-backend 11 | ``` 12 | 13 | 2. Install dependencies 14 | - Install [Deno](https://deno.land/#installation) 15 | 16 | 3. Configure environment variables 17 | Create a `.env` file with: 18 | ```env 19 | MONGO_URL=your_mongodb_connection_string 20 | ``` 21 | 4. Run the server 22 | ```bash 23 | deno run --allow-net --allow-env index.ts 24 | ``` 25 | 26 | 27 | ## API Endpoints 28 | 29 | ### Order Management 30 | 31 | #### Get Order Details 32 | ```http 33 | GET /order?email={email}&hash={hash} 34 | ``` 35 | Returns order details for the given email and hash combination. 36 | 37 | **Parameters:** 38 | - `email`: User's email address 39 | - `hash`: Order hash 40 | 41 | **Response:** 42 | ```json 43 | { 44 | "hash": "string", 45 | "items": { 46 | "itemId": "quantity" 47 | }, 48 | "timestamp": "string", 49 | "status": "Placed|Done", 50 | "totalCost": number 51 | } 52 | ``` 53 | 54 | #### Complete Order 55 | ```http 56 | GET /order/complete?email={email}&hash={hash} 57 | ``` 58 | 59 | Marks an order as completed. 60 | 61 | **Parameters:** 62 | - `email`: User's email address 63 | - `hash`: Order hash 64 | 65 | **Response:** 66 | ```json 67 | { 68 | "status": "success" 69 | } 70 | ``` 71 | 72 | ## Error Handling 73 | 74 | The API returns appropriate HTTP status codes: 75 | - 200: Success 76 | - 400: Bad Request (missing parameters) 77 | - 404: Not Found (order doesn't exist) 78 | - 500: Internal Server Error 79 | 80 | ## Technologies Used 81 | 82 | - Deno 83 | - Oak (web framework) 84 | - MongoDB 85 | - TypeScript 86 | 87 | --- 88 | 89 |
90 | 91 | 92 | 93 | 94 | 95 | 96 |
CSMSAppwriteMongoDB
97 |
-------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { Application, Router } from "oak"; 2 | import { getOrderByHash, updateOrderStatus } from "./database.ts"; 3 | 4 | const router = new Router(); 5 | 6 | router.get("/", (ctx) => { 7 | ctx.response.redirect("https://xditya.me"); 8 | }); 9 | 10 | router.get("/order", async (ctx) => { 11 | const url = ctx.request.url; 12 | const searchParams = url.searchParams; 13 | 14 | const email = searchParams.get("email"); 15 | const hash = searchParams.get("hash"); 16 | 17 | if (!email || !hash) { 18 | ctx.response.status = 400; 19 | ctx.response.body = { error: "Missing email or hash parameter" }; 20 | return; 21 | } 22 | 23 | const orderDetails = await getOrderByHash(email, hash); 24 | let dataToReturn; 25 | 26 | if (orderDetails?.status === "Placed") { 27 | dataToReturn = { 28 | error: null, 29 | hash: orderDetails?.hash, 30 | items: orderDetails?.items, 31 | totalCost: orderDetails?.totalCost, 32 | }; 33 | } else { 34 | dataToReturn = { 35 | error: "Order deliverd!", 36 | }; 37 | } 38 | 39 | if (!orderDetails) { 40 | ctx.response.status = 404; 41 | ctx.response.body = { error: "Order not found" }; 42 | return; 43 | } 44 | 45 | ctx.response.status = 200; 46 | ctx.response.body = dataToReturn; 47 | }); 48 | 49 | router.get("/order/complete", async (ctx) => { 50 | const url = ctx.request.url; 51 | const searchParams = url.searchParams; 52 | 53 | const email = searchParams.get("email"); 54 | const hash = searchParams.get("hash"); 55 | 56 | if (!email || !hash) { 57 | ctx.response.status = 400; 58 | ctx.response.body = { error: "Missing email or hash parameter" }; 59 | return; 60 | } 61 | 62 | const orderDetails = await getOrderByHash(email, hash); 63 | 64 | if (!orderDetails) { 65 | ctx.response.status = 404; 66 | ctx.response.body = { error: "Order not found" }; 67 | return; 68 | } 69 | 70 | await updateOrderStatus(email, hash, "Done"); 71 | 72 | ctx.response.status = 200; 73 | ctx.response.body = { status: "success" }; 74 | }); 75 | 76 | const app = new Application(); 77 | app.use(async (ctx, next) => { 78 | ctx.response.headers.set("Access-Control-Allow-Origin", "*"); 79 | ctx.response.headers.set( 80 | "Access-Control-Allow-Headers", 81 | "Origin, X-Requested-With, Content-Type, Accept, Authorization" 82 | ); 83 | ctx.response.headers.set( 84 | "Access-Control-Allow-Methods", 85 | "GET, POST, PUT, DELETE, OPTIONS" 86 | ); 87 | await next(); 88 | }); 89 | app.use(router.routes()); 90 | app.use(router.allowedMethods()); 91 | 92 | app.addEventListener("error", (e) => console.log(e)); 93 | 94 | console.log("> Started listening on PORT 8000!"); 95 | 96 | await app.listen({ port: 8000 }); 97 | --------------------------------------------------------------------------------