| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  | import * as fs from "node:fs" | 
					
						
							|  |  |  | import * as http from "node:http" | 
					
						
							|  |  |  | import * as path from "node:path" | 
					
						
							|  |  |  | import ScriptUtils from "./ScriptUtils" | 
					
						
							| 
									
										
										
										
											2023-10-30 13:45:44 +01:00
										 |  |  | import * as meta from "../package.json" | 
					
						
							| 
									
										
										
										
											2023-11-05 12:05:00 +01:00
										 |  |  | import { ServerResponse } from "http" | 
					
						
							| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | const PORT = 1235 | 
					
						
							|  |  |  | const CORS = "http://localhost:1234,https://mapcomplete.org,https://dev.mapcomplete.org" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const MIME_TYPES = { | 
					
						
							|  |  |  |     default: "application/octet-stream", | 
					
						
							|  |  |  |     html: "text/html; charset=UTF-8", | 
					
						
							|  |  |  |     js: "application/javascript", | 
					
						
							|  |  |  |     css: "text/css", | 
					
						
							|  |  |  |     png: "image/png", | 
					
						
							|  |  |  |     jpg: "image/jpg", | 
					
						
							|  |  |  |     gif: "image/gif", | 
					
						
							|  |  |  |     ico: "image/x-icon", | 
					
						
							|  |  |  |     svg: "image/svg+xml", | 
					
						
							|  |  |  |     json: "application/json", | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const STATIC_PATH = path.join(process.cwd(), "./assets") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-07 03:06:01 +02:00
										 |  |  | async function prepareFile(url: string): Promise<string> { | 
					
						
							| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  |     const paths = [STATIC_PATH, url] | 
					
						
							|  |  |  |     if (url.endsWith("/")) paths.push("index.html") | 
					
						
							|  |  |  |     const filePath = path.join(...paths) | 
					
						
							| 
									
										
										
										
											2023-10-11 04:16:52 +02:00
										 |  |  |     if (fs.existsSync(filePath)) { | 
					
						
							|  |  |  |         return fs.readFileSync(filePath, "utf8") | 
					
						
							| 
									
										
										
										
											2023-10-07 03:06:01 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-10-24 22:01:10 +02:00
										 |  |  |     while (url.startsWith("/")) { | 
					
						
							|  |  |  |         url = url.slice(1) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const sliced = url.split("/").slice(1) | 
					
						
							|  |  |  |     if (!sliced) { | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const backupFile = path.join(STATIC_PATH, ...sliced) | 
					
						
							| 
									
										
										
										
											2023-11-05 12:05:00 +01:00
										 |  |  |     console.log("Using backup path", backupFile) | 
					
						
							| 
									
										
										
										
											2023-10-24 22:01:10 +02:00
										 |  |  |     if (fs.existsSync(backupFile)) { | 
					
						
							|  |  |  |         return fs.readFileSync(backupFile, "utf8") | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-10-11 04:16:52 +02:00
										 |  |  |     return null | 
					
						
							| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2023-12-01 22:33:08 +01:00
										 |  |  | async function handleDelete(req: http.IncomingMessage, res: ServerResponse) { | 
					
						
							|  |  |  |     let body = "" | 
					
						
							|  |  |  |     req.on("data", (chunk) => { | 
					
						
							|  |  |  |         body = body + chunk | 
					
						
							|  |  |  |     }) | 
					
						
							|  |  |  |     const paths = req.url.split("/") | 
					
						
							|  |  |  |     console.log("Got a valid delete to:", paths.join("/")) | 
					
						
							|  |  |  |     for (let i = 1; i < paths.length; i++) { | 
					
						
							|  |  |  |         const p = paths.slice(0, i) | 
					
						
							|  |  |  |         const dir = STATIC_PATH + p.join("/") | 
					
						
							|  |  |  |         if (!fs.existsSync(dir)) { | 
					
						
							|  |  |  |             res.writeHead(304, { "Content-Type": MIME_TYPES.html }) | 
					
						
							|  |  |  |             res.write("<html><body>No parent directory, nothing deleted</body></html>", "utf8") | 
					
						
							|  |  |  |             res.end() | 
					
						
							| 
									
										
										
										
											2023-12-19 22:08:00 +01:00
										 |  |  |             return | 
					
						
							| 
									
										
										
										
											2023-12-01 22:33:08 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const path = STATIC_PATH + paths.join("/") | 
					
						
							| 
									
										
										
										
											2023-12-19 22:08:00 +01:00
										 |  |  |     if (!fs.existsSync(path)) { | 
					
						
							| 
									
										
										
										
											2023-12-01 22:33:08 +01:00
										 |  |  |         res.writeHead(304, { "Content-Type": MIME_TYPES.html }) | 
					
						
							|  |  |  |         res.write("<html><body>File not found</body></html>", "utf8") | 
					
						
							|  |  |  |         res.end() | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-19 22:08:00 +01:00
										 |  |  |     fs.renameSync(path, path + ".bak") | 
					
						
							| 
									
										
										
										
											2023-12-01 22:33:08 +01:00
										 |  |  |     res.writeHead(200, { "Content-Type": MIME_TYPES.html }) | 
					
						
							|  |  |  |     res.write("<html><body>File moved to backup</body></html>", "utf8") | 
					
						
							|  |  |  |     res.end() | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-05 12:05:00 +01:00
										 |  |  | async function handlePost(req: http.IncomingMessage, res: ServerResponse) { | 
					
						
							|  |  |  |     let body = "" | 
					
						
							|  |  |  |     req.on("data", (chunk) => { | 
					
						
							|  |  |  |         body = body + chunk | 
					
						
							|  |  |  |     }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     await new Promise((resolve) => req.on("end", resolve)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-01 22:34:09 +01:00
										 |  |  |     console.log(new Date().toISOString()) | 
					
						
							| 
									
										
										
										
											2023-11-05 12:05:00 +01:00
										 |  |  |     let parsed: any | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |         parsed = JSON.parse(body) | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |         console.error("ERROR: probably did not receive the full JSON", e) | 
					
						
							|  |  |  |         res.writeHead(400, { "Content-Type": MIME_TYPES.html }) | 
					
						
							|  |  |  |         res.write("<html><body>Invalid JSON:" + e + "</body></html>", "utf8") | 
					
						
							|  |  |  |         res.end() | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const paths = req.url.split("/") | 
					
						
							|  |  |  |     console.log("Got a valid update to:", paths.join("/")) | 
					
						
							|  |  |  |     for (let i = 1; i < paths.length; i++) { | 
					
						
							|  |  |  |         const p = paths.slice(0, i) | 
					
						
							|  |  |  |         const dir = STATIC_PATH + p.join("/") | 
					
						
							|  |  |  |         if (!fs.existsSync(dir)) { | 
					
						
							|  |  |  |             console.log("Creating new directory", dir) | 
					
						
							|  |  |  |             fs.mkdirSync(dir) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const path = STATIC_PATH + paths.join("/") | 
					
						
							|  |  |  |     fs.writeFileSync(path, JSON.stringify(parsed, null, "  ")) | 
					
						
							|  |  |  |     res.writeHead(200, { "Content-Type": MIME_TYPES.html }) | 
					
						
							|  |  |  |     res.write("<html><body>OK</body></html>", "utf8") | 
					
						
							|  |  |  |     res.end() | 
					
						
							|  |  |  |     return | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | http.createServer(async (req: http.IncomingMessage, res) => { | 
					
						
							| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  |     try { | 
					
						
							| 
									
										
										
										
											2023-12-19 22:08:00 +01:00
										 |  |  |         console.log( | 
					
						
							|  |  |  |             req.method + " " + req.url, | 
					
						
							|  |  |  |             "from:", | 
					
						
							|  |  |  |             req.headers.origin, | 
					
						
							|  |  |  |             new Date().toISOString() | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  |         res.setHeader( | 
					
						
							|  |  |  |             "Access-Control-Allow-Headers", | 
					
						
							|  |  |  |             "Origin, X-Requested-With, Content-Type, Accept" | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         res.setHeader("Access-Control-Allow-Origin", req.headers.origin ?? "*") | 
					
						
							|  |  |  |         if (req.method === "OPTIONS") { | 
					
						
							|  |  |  |             res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, UPDATE") | 
					
						
							|  |  |  |             res.writeHead(204, { "Content-Type": MIME_TYPES.html }) | 
					
						
							|  |  |  |             res.end() | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (req.method === "POST" || req.method === "UPDATE") { | 
					
						
							| 
									
										
										
										
											2023-11-05 12:05:00 +01:00
										 |  |  |             await handlePost(req, res) | 
					
						
							| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-10-24 22:01:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-19 22:08:00 +01:00
										 |  |  |         if (req.method === "DELETE") { | 
					
						
							| 
									
										
										
										
											2023-12-01 22:42:02 +01:00
										 |  |  |             console.log("Got a DELETE", new Date()) | 
					
						
							| 
									
										
										
										
											2023-12-01 22:33:08 +01:00
										 |  |  |             await handleDelete(req, res) | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-24 22:01:10 +02:00
										 |  |  |         const url = new URL(`http://127.0.0.1/` + req.url) | 
					
						
							| 
									
										
										
										
											2023-10-25 15:53:36 +02:00
										 |  |  |         console.log("URL pathname is") | 
					
						
							| 
									
										
										
										
											2023-10-24 22:01:10 +02:00
										 |  |  |         if (url.pathname.endsWith("overview")) { | 
					
						
							| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  |             console.log("Giving overview") | 
					
						
							| 
									
										
										
										
											2023-10-24 22:01:10 +02:00
										 |  |  |             let userId = url.searchParams.get("userId") | 
					
						
							| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  |             const allFiles = ScriptUtils.readDirRecSync(STATIC_PATH) | 
					
						
							| 
									
										
										
										
											2023-10-24 22:01:10 +02:00
										 |  |  |                 .filter( | 
					
						
							|  |  |  |                     (p) => | 
					
						
							|  |  |  |                         p.endsWith(".json") && | 
					
						
							|  |  |  |                         !p.endsWith("license_info.json") && | 
					
						
							|  |  |  |                         (p.startsWith("layers") || | 
					
						
							|  |  |  |                             p.startsWith("themes") || | 
					
						
							|  |  |  |                             userId !== undefined || | 
					
						
							|  |  |  |                             p.startsWith(userId)) | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  |                 .map((p) => p.substring(STATIC_PATH.length + 1)) | 
					
						
							|  |  |  |             res.writeHead(200, { "Content-Type": MIME_TYPES.json }) | 
					
						
							|  |  |  |             res.write(JSON.stringify({ allFiles })) | 
					
						
							|  |  |  |             res.end() | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-10-24 22:01:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  |         const file = await prepareFile(req.url) | 
					
						
							| 
									
										
										
										
											2023-10-07 03:06:01 +02:00
										 |  |  |         if (file === null) { | 
					
						
							|  |  |  |             res.writeHead(404, { "Content-Type": MIME_TYPES.html }) | 
					
						
							|  |  |  |             res.write("<html><body><p>Not found...</p></body></html>") | 
					
						
							|  |  |  |             res.end() | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const statusCode = 200 | 
					
						
							|  |  |  |         const mimeType = MIME_TYPES.json || MIME_TYPES.default | 
					
						
							| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  |         res.writeHead(statusCode, { "Content-Type": mimeType }) | 
					
						
							| 
									
										
										
										
											2023-10-07 03:06:01 +02:00
										 |  |  |         res.write(file) | 
					
						
							| 
									
										
										
										
											2023-09-15 01:16:33 +02:00
										 |  |  |         res.end() | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							|  |  |  |         console.error(e) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | }).listen(PORT) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-30 13:45:44 +01:00
										 |  |  | console.log( | 
					
						
							|  |  |  |     `Server started at http://127.0.0.1:${PORT}/, the time is ${new Date().toISOString()}, version from package.json is ${ | 
					
						
							|  |  |  |         meta.version | 
					
						
							|  |  |  |     }`
 | 
					
						
							|  |  |  | ) |