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")
|
2024-09-30 17:10:33 +02:00
|
|
|
const 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
|
|
|
|
}`
|
|
|
|
)
|