Skip to content

Commit

Permalink
Merge pull request #107 from Better-Boy/upload-file
Browse files Browse the repository at this point in the history
upload file
  • Loading branch information
mpacheco12 authored Nov 26, 2024
2 parents 4d06d49 + a3a41bb commit c6c4b5a
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 5 deletions.
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export default class Constants {
/** MindsDB ML Engines endpoint. */
public static readonly BASE_MLENGINES_URI = '/api/handlers/byom';

public static readonly FILES_URI = '/api/files';

public static readonly BASE_CALLBACK_URI = '/cloud/callback/model_status';


Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const Projects = new ProjectsModule.ProjectsRestApiClient(
defaultAxiosInstance,
httpAuthenticator
);
const Tables = new TablesModule.TablesRestApiClient(SQL);
const Tables = new TablesModule.TablesRestApiClient(SQL, defaultAxiosInstance, httpAuthenticator);
const Views = new ViewsModule.ViewsRestApiClient(SQL);
const Jobs = new JobsModule.JobsRestApiClient(SQL);
const MLEngines = new MLEnginesModule.MLEnginesRestApiClient(
Expand Down
13 changes: 13 additions & 0 deletions src/tables/tablesApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,19 @@ export default abstract class TablesApiClient {
*/
abstract deleteFile(name: string): Promise<void>;

/**
* Uploads a file to a remote server or storage service.
*
* @param filePath - The local path to the file that needs to be uploaded.
* @param fileName - The name that the file should have on the remote server after the upload.
*
* @returns A promise that resolves when the file has been successfully uploaded.
* The promise does not return any value upon success.
*
* @throws {Error} - If there is an error during the file upload process, the promise is rejected with an error message.
*/
abstract uploadFile(filePath: string, fileName: string, original_file_name ?: string): Promise<void>;

}

/**
Expand Down
83 changes: 82 additions & 1 deletion src/tables/tablesRestApiClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,38 @@ import Table from './table';
import TablesApiClient from './tablesApiClient';
import mysql from 'mysql';
import { MindsDbError } from '../errors';
import HttpAuthenticator from '../httpAuthenticator';
import { Axios } from 'axios';
import FormData from 'form-data';
import * as fs from 'fs';
import Constants from '../constants';
import { getBaseRequestConfig } from '../util/http';
import path from 'path';

/** Implementation of TablesApiClient that goes through the REST API */
export default class TablesRestApiClient extends TablesApiClient {
/** SQL API client to send all SQL query requests. */
sqlClient: SqlApiClient;

/** Axios instance to send all requests. */
client: Axios;

/** Authenticator to use for reauthenticating if needed. */
authenticator: HttpAuthenticator;

/**
*
* @param {SqlApiClient} sqlClient - SQL API client to send all SQL query requests.
*/
constructor(sqlClient: SqlApiClient) {
constructor(
sqlClient: SqlApiClient,
client: Axios,
authenticator: HttpAuthenticator
) {
super();
this.sqlClient = sqlClient;
this.client = client;
this.authenticator = authenticator;
}

/**
Expand Down Expand Up @@ -174,6 +193,68 @@ export default class TablesRestApiClient extends TablesApiClient {
throw new MindsDbError(sqlQueryResult.error_message);
}
}

private getFilesUrl(): string {
const baseUrl =
this.client.defaults.baseURL || Constants.BASE_CLOUD_API_ENDPOINT;
const filesUrl = new URL(Constants.FILES_URI, baseUrl);
return filesUrl.toString();
}

/**
* Uploads a file asynchronously to a specified location.
*
* This method handles the process of uploading a file to a server or cloud storage. It requires the path to the
* file on the local filesystem, the desired name for the uploaded file, and optionally, the original name of the file.
* The file will be uploaded with the specified file name, but the original file name can be preserved if provided.
*
* @param {string} filePath - The local path to the file to be uploaded.
* @param {string} fileName - The desired name for the file once it is uploaded.
* @param {string} [original_file_name] - (Optional) The original name of the file before renaming. This is typically
* used for logging, tracking, or maintaining the original file's identity.
*
* @returns {Promise<void>} A promise that resolves when the file upload is complete. If the upload fails,
* an error will be thrown.
*
* @throws {Error} If there is an issue with the upload, such as network errors, permission issues, or invalid file paths.
*/
override async uploadFile(filePath: string, fileName: string, original_file_name ?: string): Promise<void> {
const formData = new FormData();

if(original_file_name)
formData.append('original_file_name', original_file_name);

if (fs.existsSync(filePath)) {
formData.append('file', fs.createReadStream(filePath), {
filename: path.basename(filePath),
contentType: 'multipart/form-data',
});
} else {
console.error('File does not exist:', filePath);
}

// Axios request configuration
const { authenticator, client } = this;

const config = getBaseRequestConfig(authenticator);
const filesUrl = this.getFilesUrl();
config.method = 'PUT';
config.url = `${filesUrl}/${fileName}`;
(config.headers = {
...config.headers,
...formData.getHeaders(),
}),
(config.data = formData);

try {
const uploadFileResponse = await client.request(config);
} catch (error) {
console.error(error);
throw MindsDbError.fromHttpError(error, filesUrl);
}

}

}

/**
Expand Down
6 changes: 3 additions & 3 deletions tests/tables/tablesRestApiClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('Testing Models REST API client', () => {
mockedSqlRestApiClient.runQuery.mockClear();
});
test('should create table', async () => {
const tablesRestApiClient = new TablesRestApiClient(mockedSqlRestApiClient);
const tablesRestApiClient = new TablesRestApiClient(mockedSqlRestApiClient, mockedAxios, mockedHttpAuthenticator);
mockedSqlRestApiClient.runQuery.mockImplementation(() => {
return Promise.resolve({
columnNames: [],
Expand Down Expand Up @@ -50,7 +50,7 @@ describe('Testing Models REST API client', () => {
});

test('should create or replace table', async () => {
const tablesRestApiClient = new TablesRestApiClient(mockedSqlRestApiClient);
const tablesRestApiClient = new TablesRestApiClient(mockedSqlRestApiClient, mockedAxios, mockedHttpAuthenticator);
mockedSqlRestApiClient.runQuery.mockImplementation(() => {
return Promise.resolve({
columnNames: [],
Expand Down Expand Up @@ -78,7 +78,7 @@ describe('Testing Models REST API client', () => {
});

test('should delete table', async () => {
const tablesRestApiClient = new TablesRestApiClient(mockedSqlRestApiClient);
const tablesRestApiClient = new TablesRestApiClient(mockedSqlRestApiClient, mockedAxios, mockedHttpAuthenticator);
mockedSqlRestApiClient.runQuery.mockImplementation(() => {
return Promise.resolve({
columnNames: [],
Expand Down

0 comments on commit c6c4b5a

Please sign in to comment.