148 lines
3.5 KiB
TypeScript
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();
|