// services/api-client.ts // Copyright (C) 2026 Robert Colbert // All Rights Reserved // import env, { getCountryName } from "../config/env.js"; import assert from "node:assert"; import { NextFunction, Request, RequestHandler, Response } from "express"; import { filterText } from "dtp-cleantext"; import { v4 as uuidv4 } from "uuid"; import ApiClient, { ApiClientStatus, IApiClient, } from "../models/api-client.js"; import ApiClientLog, { IApiClientLog } from "../models/api-client-log.js"; import { DtpService } from "../lib/service.js"; import { GadgetId } from "@gadget/api"; import { PopulateOptions } from "mongoose"; class ApiClientService extends DtpService { private populateApiClient: PopulateOptions[] = [ { path: "user", select: "-passwordSalt -password", }, ]; get name(): string { return "ApiClientService"; } get slug(): string { return "apiClient"; } constructor() { super(); } async start(): Promise {} async stop(): Promise {} middleware(): RequestHandler { return async ( req: Request, res: Response, next: NextFunction, ): Promise => { try { const apiClientId = req.header("X-Gadget-Key") as string; if (!apiClientId) { return next(); } const apiClient = await this.getById(apiClientId); if (!apiClient) { return next(); } if (apiClient.user && !req.user) { req.user = apiClient.user; res.locals.user = apiClient.user; } await this.logRequest(apiClient, req); return next(); } catch (error) { this.log.error("failed to process ApiClient request", { error }); return next(error); } }; } async create(definition: Partial): Promise { const NOW = new Date(); const apiClient = new ApiClient(); apiClient.createdAt = NOW; apiClient.updatedAt = NOW; apiClient.status = ApiClientStatus.Active; apiClient.user = definition.user; assert(definition.name, "ApiClient name is required"); apiClient.name = filterText(definition.name); if (definition.description) { apiClient.description = filterText(definition.description); } apiClient.secret = uuidv4(); await apiClient.save(); return apiClient.toObject(); } async setStatus( client: IApiClient, status: ApiClientStatus, ): Promise { this.log.info("updating ApiClient status", { _id: client._id, status }); const newClient = await ApiClient.findOneAndUpdate( { _id: client._id }, { $set: { status } }, { new: true }, ); if (!newClient) { const error = new Error("ApiClient not found"); error.statusCode = 404; throw error; } return newClient; } async getById(clientId: GadgetId): Promise { const client = await ApiClient.findOne({ _id: clientId }) .populate(this.populateApiClient) .lean(); return client; } async logRequest(client: IApiClient, req: Request): Promise { const NOW = new Date(); const log = new ApiClientLog(); log.client = client._id; log.createdAt = NOW; log.method = req.method; log.url = req.url; await log.save(); return log.toObject(); } async remove(client: IApiClient): Promise { this.log.info("removing API client", { _id: client._id }); await ApiClient.deleteOne({ _id: client._id }); } } export default new ApiClientService();