forked from Yara724/api
205 lines
6.2 KiB
TypeScript
205 lines
6.2 KiB
TypeScript
import { createReadStream, existsSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import {
|
|
HttpException,
|
|
HttpStatus,
|
|
Injectable,
|
|
OnModuleInit,
|
|
} from "@nestjs/common";
|
|
import { ConfigService } from "@nestjs/config";
|
|
import { AxiosRequestConfig } from "axios"; // TODO: Change all axios usages to HttpModule
|
|
|
|
@Injectable()
|
|
export class AiService implements OnModuleInit {
|
|
private apiKey: string;
|
|
private accessToken: string = null;
|
|
|
|
// These configurations are for authentication and getting the API key.
|
|
private readonly loginOptions: AxiosRequestConfig = {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
url: `${this.configService.get<string>("AI_URL_V2")}/auth/login`,
|
|
data: {
|
|
username: this.configService.get<string>("AI_USERNAME"),
|
|
password: this.configService.get<string>("AI_PASSWORD"),
|
|
},
|
|
timeout: 1000, // TODO: Make this ENV
|
|
};
|
|
|
|
constructor(private readonly configService: ConfigService) {}
|
|
|
|
private get profileOptions(): AxiosRequestConfig {
|
|
return {
|
|
method: "GET",
|
|
url: `${this.configService.get<string>("AI_URL_V2")}/auth/profile`,
|
|
headers: {
|
|
Authorization: `Bearer ${this.accessToken}`,
|
|
},
|
|
};
|
|
}
|
|
|
|
// This getter dynamically creates the base options for the image processing request.
|
|
private get imageProcessOptions(): AxiosRequestConfig {
|
|
return {
|
|
method: "POST",
|
|
url: `${process.env.AI_URL_V2}/services/car-damage/detector?version=ai-v7`,
|
|
headers: {
|
|
Authorization: `Bearer ${this.accessToken}`,
|
|
"gateway-api-key": `${this.apiKey}`,
|
|
},
|
|
};
|
|
}
|
|
|
|
async onModuleInit() {
|
|
try {
|
|
const res = await this.login();
|
|
// if (res?.accessToken) {
|
|
// this.accessToken = res.accessToken;
|
|
// await this.getApiKey();
|
|
// }
|
|
} catch (error) {
|
|
// Don't prevent app startup if AI service is temporarily unavailable
|
|
// The service will attempt to re-authenticate when aiRequestImage is called
|
|
// Reset tokens so re-authentication will be attempted
|
|
this.accessToken = null;
|
|
this.apiKey = null;
|
|
}
|
|
}
|
|
|
|
private async login() {
|
|
// const loginResponse = await axios.request(this.loginOptions);
|
|
// return loginResponse.data;
|
|
}
|
|
|
|
private async getApiKey() {
|
|
// const profileResponse = await axios.request(this.profileOptions);
|
|
// this.apiKey = profileResponse.data.apiKey.key;
|
|
// return this.apiKey;
|
|
}
|
|
|
|
public async aiRequestImage(file: {
|
|
path: string;
|
|
fileName?: string;
|
|
}): Promise<any> {
|
|
// Ensure authentication is set up
|
|
if (!this.accessToken || !this.apiKey) {
|
|
try {
|
|
const res = await this.login();
|
|
// if (res?.accessToken) {
|
|
// this.accessToken = res.accessToken;
|
|
// await this.getApiKey();
|
|
// } else {
|
|
// throw new HttpException(
|
|
// "AI Service authentication failed",
|
|
// HttpStatus.UNAUTHORIZED,
|
|
// );
|
|
// }
|
|
} catch (error) {
|
|
throw new HttpException(
|
|
"AI Service authentication failed",
|
|
HttpStatus.UNAUTHORIZED,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Resolve relative paths to absolute paths
|
|
const filePath = file.path.startsWith("/")
|
|
? file.path
|
|
: join(process.cwd(), file.path.replace(/^\.\//, ""));
|
|
|
|
// Check if file exists
|
|
if (!existsSync(filePath)) {
|
|
throw new HttpException(
|
|
`File not found: ${file.path}`,
|
|
HttpStatus.NOT_FOUND,
|
|
);
|
|
}
|
|
|
|
// const form = new FormData();
|
|
const fileStream = createReadStream(filePath);
|
|
|
|
// Append file with filename if available
|
|
if (file.fileName) {
|
|
// form.append("images", fileStream, file.fileName);
|
|
} else {
|
|
// Extract filename from path if not provided
|
|
const pathParts = filePath.split("/");
|
|
const extractedFileName = pathParts[pathParts.length - 1];
|
|
// form.append("images", fileStream, extractedFileName);
|
|
}
|
|
|
|
try {
|
|
const requestHeaders = {
|
|
...this.imageProcessOptions.headers,
|
|
// ...form.getHeaders(),
|
|
};
|
|
|
|
const fs = require("fs");
|
|
const stats = fs.statSync(filePath);
|
|
|
|
// const response = await axios.request({
|
|
// ...this.imageProcessOptions,
|
|
// headers: requestHeaders,
|
|
// // data: form,
|
|
// maxContentLength: Infinity,
|
|
// maxBodyLength: Infinity,
|
|
// });
|
|
|
|
// Validate response structure
|
|
// if (!response.data) {
|
|
// throw new HttpException(
|
|
// "AI Service returned empty response",
|
|
// HttpStatus.BAD_GATEWAY,
|
|
// );
|
|
// }
|
|
|
|
// // Check for error in response first (AI service returns 201 with error in body)
|
|
// if (response.data.error) {
|
|
// throw new HttpException(
|
|
// `AI Service error: ${response.data.error}`,
|
|
// HttpStatus.BAD_GATEWAY,
|
|
// );
|
|
// }
|
|
|
|
// // Check for processed image (downloadLink)
|
|
// if (!response.data.downloadLink) {
|
|
// throw new HttpException(
|
|
// "AI Service did not return processed image (downloadLink missing)",
|
|
// HttpStatus.BAD_GATEWAY,
|
|
// );
|
|
// }
|
|
|
|
// return response.data;
|
|
} catch (er) {
|
|
// Determine error source
|
|
let errorSource = "UNKNOWN";
|
|
let errorMessage = er.message;
|
|
let errorDetails = "No error details available";
|
|
|
|
if (er.response) {
|
|
errorSource = "AI_SERVICE_RESPONSE";
|
|
errorMessage =
|
|
er.response?.data?.message ||
|
|
er.message ||
|
|
`HTTP ${er.response.status}`;
|
|
errorDetails = er.response?.data
|
|
? JSON.stringify(er.response.data, null, 2)
|
|
: `Status: ${er.response.status}, StatusText: ${er.response.statusText}`;
|
|
} else if (er.request) {
|
|
errorSource = "NETWORK_ERROR";
|
|
errorMessage = "Network error - AI service did not respond";
|
|
errorDetails = "Request was made but no response received";
|
|
} else {
|
|
errorSource = "REQUEST_SETUP_ERROR";
|
|
errorMessage = er.message || "Error setting up request";
|
|
}
|
|
|
|
// Re-throw with detailed error information
|
|
throw new HttpException(
|
|
`[${errorSource}] ${errorMessage}`,
|
|
er.response?.status || HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
}
|