174 lines
4.8 KiB
JavaScript
174 lines
4.8 KiB
JavaScript
import config, { getEnvironment } from "./config.js";
|
|
import log4js from "log4js";
|
|
import express from "express";
|
|
import cors from "cors";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
import { createServer } from "http";
|
|
import session from "express-session";
|
|
import MongoStore from "connect-mongo";
|
|
import { Server } from "socket.io";
|
|
import { connectMongo } from "./db/connect.js";
|
|
import { syncPatchesFromConfig } from "./services/patches.js";
|
|
import { recalculateAllPatchesGrowingState } from "./services/patchGrowingState.js";
|
|
import { syncDeviceFromConfig } from "./services/devices.js";
|
|
import { SocketManager } from "./socket/SocketManager.js";
|
|
import { MqttTtnIngest } from "./mqtt/MqttTtnIngest.js";
|
|
import { getWateringSessionsState } from "./services/allotmentState.js";
|
|
import { listWaterSchedulesForConfiguredDevice } from "./services/waterSchedules.js";
|
|
import { SubscriptionRegistry } from "./socket/SubscriptionRegistry.js";
|
|
import { registerAllotmentSocketApi } from "./socket/allotmentSocketApi.js";
|
|
import { startHistorySampler } from "./services/history.js";
|
|
import {
|
|
SESSION_COOKIE,
|
|
bearerSessionMiddleware,
|
|
createOidcClient,
|
|
createOidcRouter,
|
|
} from "./auth/oidc.js";
|
|
import { createImagesRouter } from "./routes/images.js";
|
|
import { createHubRouter } from "./routes/hub.js";
|
|
import { getLogger, getLogLevel } from "./logger.js";
|
|
|
|
const logger = getLogger("App");
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const isProd = getEnvironment() === "production";
|
|
const clientUrl = config.app.urlClient;
|
|
const mongoUri = config.database.mongo.url;
|
|
|
|
await connectMongo(mongoUri);
|
|
|
|
await syncDeviceFromConfig(config);
|
|
await syncPatchesFromConfig(config);
|
|
|
|
const PATCH_GROWING_RECALC_MS = 10 * 1000;
|
|
try {
|
|
await recalculateAllPatchesGrowingState();
|
|
} catch (e) {
|
|
logger.error("Initial patch growing state recalc failed:", e);
|
|
}
|
|
|
|
try {
|
|
await createOidcClient();
|
|
} catch (e) {
|
|
logger.error("OIDC discovery/init failed:", e.message);
|
|
}
|
|
|
|
const app = express();
|
|
app.set("trust proxy", 1);
|
|
|
|
app.use(log4js.connectLogger(logger, { level: "trace" }));
|
|
|
|
app.use(
|
|
cors({
|
|
origin: clientUrl,
|
|
credentials: true,
|
|
}),
|
|
);
|
|
|
|
app.use("/api/hub", createHubRouter());
|
|
|
|
const sessionMiddleware = session({
|
|
name: SESSION_COOKIE,
|
|
secret: config.auth.sessionSecret,
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
store: MongoStore.create({
|
|
mongoUrl: mongoUri,
|
|
ttl: 14 * 24 * 60 * 60,
|
|
}),
|
|
cookie: {
|
|
httpOnly: true,
|
|
secure: isProd,
|
|
sameSite: isProd ? "none" : "lax",
|
|
maxAge: 14 * 24 * 60 * 60 * 1000,
|
|
},
|
|
});
|
|
|
|
app.use(bearerSessionMiddleware);
|
|
app.use(sessionMiddleware);
|
|
app.use(express.json());
|
|
|
|
app.use(createOidcRouter(clientUrl));
|
|
|
|
app.use("/api/images", createImagesRouter());
|
|
|
|
app.get("/api/me", (req, res) => {
|
|
if (req.session?.user) {
|
|
res.json({ status: "ok", user: req.session.user });
|
|
} else {
|
|
res.status(401).json({ status: "error", error: "Not authenticated" });
|
|
}
|
|
});
|
|
|
|
const server = createServer(app);
|
|
const io = new Server(server, {
|
|
cors: {
|
|
origin: clientUrl,
|
|
methods: ["GET", "POST"],
|
|
credentials: true,
|
|
},
|
|
});
|
|
|
|
io.engine.use(bearerSessionMiddleware);
|
|
io.engine.use((req, res, next) => {
|
|
sessionMiddleware(req, res, next);
|
|
});
|
|
|
|
const subscriptionRegistry = new SubscriptionRegistry();
|
|
const socketManager = new SocketManager(io, { subscriptionRegistry });
|
|
|
|
async function notifyWateringSubscribers() {
|
|
const data = await getWateringSessionsState();
|
|
socketManager.emitWateringSessionsUpdated(data);
|
|
}
|
|
|
|
async function notifyWaterSchedulesSubscribers() {
|
|
const data = await listWaterSchedulesForConfiguredDevice();
|
|
socketManager.emitWaterSchedulesUpdated(data);
|
|
}
|
|
|
|
socketManager.registerSocketApi = (socket, client) => {
|
|
registerAllotmentSocketApi(socket, {
|
|
socketClient: client,
|
|
subscriptionRegistry,
|
|
notifyWateringSubscribers,
|
|
notifyWaterSchedulesSubscribers,
|
|
emitPatchUpdated: (patch) => socketManager.emitPatchUpdated(patch),
|
|
});
|
|
};
|
|
|
|
const mqttIngest = new MqttTtnIngest(socketManager);
|
|
mqttIngest.start();
|
|
|
|
startHistorySampler();
|
|
|
|
setInterval(async () => {
|
|
try {
|
|
const changed = await recalculateAllPatchesGrowingState();
|
|
for (const patch of changed) {
|
|
socketManager.emitPatchUpdated(patch);
|
|
}
|
|
} catch (e) {
|
|
logger.error("Periodic patch growing state recalc failed:", e);
|
|
}
|
|
}, PATCH_GROWING_RECALC_MS);
|
|
|
|
if (isProd) {
|
|
const staticPath = path.join(__dirname, "../client/dist");
|
|
app.use(express.static(staticPath));
|
|
app.get("*", (req, res, next) => {
|
|
if (req.path.startsWith("/api") || req.path.startsWith("/auth")) {
|
|
return next();
|
|
}
|
|
res.sendFile(path.join(staticPath, "index.html"));
|
|
});
|
|
}
|
|
|
|
const PORT = config.server.port;
|
|
server.listen(PORT, () => {
|
|
logger.info(
|
|
`Tom's Allotment server http://localhost:${PORT} (env=${getEnvironment()}, logLevel=${getLogLevel()})`,
|
|
);
|
|
});
|