gadget/gadget-code/src/services/api-client.ts
2026-05-05 05:28:09 -04:00

148 lines
3.5 KiB
TypeScript

// services/api-client.ts
// Copyright (C) 2026 Robert Colbert <rob.colbert@openplatform.us>
// 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<void> {}
async stop(): Promise<void> {}
middleware(): RequestHandler {
return async (
req: Request,
res: Response,
next: NextFunction,
): Promise<void> => {
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<IApiClient>): Promise<IApiClient> {
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<IApiClient> {
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<IApiClient | null> {
const client = await ApiClient.findOne({ _id: clientId })
.populate(this.populateApiClient)
.lean();
return client;
}
async logRequest(client: IApiClient, req: Request): Promise<IApiClientLog> {
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<void> {
this.log.info("removing API client", { _id: client._id });
await ApiClient.deleteOne({ _id: client._id });
}
}
export default new ApiClientService();