mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-09 17:33:55 +00:00
refactor: extract metadata from sheet
This commit is contained in:
committed by
Carlos Valente
parent
37dc9a620d
commit
42dcf35f45
@@ -6,12 +6,19 @@
|
||||
import { readFileSync } from 'fs';
|
||||
|
||||
import { Request, Response } from 'express';
|
||||
import type { AuthenticationStatus, CustomFields, ErrorResponse, Rundown, RundownSummary } from 'ontime-types';
|
||||
import type {
|
||||
AuthenticationStatus,
|
||||
ErrorResponse,
|
||||
SpreadsheetPreviewResponse,
|
||||
SpreadsheetWorksheetMetadata,
|
||||
SpreadsheetWorksheetOptions,
|
||||
} from 'ontime-types';
|
||||
import { getErrorMessage } from 'ontime-utils';
|
||||
|
||||
import { deleteFile } from '../../utils/fileManagement.js';
|
||||
import {
|
||||
download,
|
||||
getWorksheetMetadata,
|
||||
getWorksheetOptions,
|
||||
handleClientSecret,
|
||||
handleInitialConnection,
|
||||
@@ -69,10 +76,13 @@ export async function revokeAuthentication(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getWorksheetNamesFromSheet(req: Request, res: Response<string[] | ErrorResponse>) {
|
||||
export async function getWorksheetNamesFromSheet(
|
||||
req: Request,
|
||||
res: Response<SpreadsheetWorksheetOptions | ErrorResponse>,
|
||||
) {
|
||||
try {
|
||||
const { sheetId } = req.params;
|
||||
const { worksheetOptions } = await getWorksheetOptions(sheetId);
|
||||
const worksheetOptions = await getWorksheetOptions(sheetId);
|
||||
res.status(200).send(worksheetOptions);
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
@@ -80,16 +90,21 @@ export async function getWorksheetNamesFromSheet(req: Request, res: Response<str
|
||||
}
|
||||
}
|
||||
|
||||
export async function getWorksheetMetadataFromSheet(req: Request, res: Response<SpreadsheetWorksheetMetadata | ErrorResponse>) {
|
||||
try {
|
||||
const { sheetId } = req.params;
|
||||
const { worksheet } = req.body;
|
||||
const metadata = await getWorksheetMetadata(sheetId, worksheet);
|
||||
res.status(200).send(metadata);
|
||||
} catch (error) {
|
||||
const message = getErrorMessage(error);
|
||||
res.status(500).send({ message });
|
||||
}
|
||||
}
|
||||
|
||||
export async function readFromSheet(
|
||||
req: Request,
|
||||
res: Response<
|
||||
| {
|
||||
rundown: Rundown;
|
||||
customFields: CustomFields;
|
||||
summary: RundownSummary;
|
||||
}
|
||||
| ErrorResponse
|
||||
>,
|
||||
res: Response<SpreadsheetPreviewResponse | ErrorResponse>,
|
||||
) {
|
||||
try {
|
||||
const { sheetId } = req.params;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import express from 'express';
|
||||
|
||||
import {
|
||||
getWorksheetMetadataFromSheet,
|
||||
getWorksheetNamesFromSheet,
|
||||
readFromSheet,
|
||||
requestConnection,
|
||||
@@ -13,7 +14,12 @@ import {
|
||||
writeToSheet,
|
||||
} from './sheets.controller.js';
|
||||
import { uploadClientSecret } from './sheets.middleware.js';
|
||||
import { validateRequestConnection, validateSheetId, validateSheetOptions } from './sheets.validation.js';
|
||||
import {
|
||||
validateRequestConnection,
|
||||
validateSheetId,
|
||||
validateSheetOptions,
|
||||
validateWorksheetMetadata,
|
||||
} from './sheets.validation.js';
|
||||
|
||||
export const router = express.Router();
|
||||
|
||||
@@ -23,6 +29,7 @@ router.post('/:sheetId/connect', uploadClientSecret, validateRequestConnection,
|
||||
router.post('/revoke', revokeAuthentication);
|
||||
|
||||
router.post('/:sheetId/worksheets', validateSheetId, getWorksheetNamesFromSheet);
|
||||
router.post('/:sheetId/metadata', validateWorksheetMetadata, getWorksheetMetadataFromSheet);
|
||||
|
||||
router.post('/:sheetId/read', validateSheetOptions, readFromSheet);
|
||||
router.post('/:sheetId/write', validateSheetOptions, writeToSheet);
|
||||
|
||||
@@ -26,6 +26,9 @@ import { logger } from '../../classes/Logger.js';
|
||||
import { consoleSubdued } from '../../utils/console.js';
|
||||
import { parseCustomFields } from '../custom-fields/customFields.parser.js';
|
||||
import { parseExcel } from '../excel/excel.parser.js';
|
||||
import type { SpreadsheetWorksheetMetadata } from 'ontime-types';
|
||||
|
||||
import { getWorksheetMetadataFromRows } from '../excel/spreadsheetMetadata.utils.js';
|
||||
import { getCurrentRundown, getProjectCustomFields, processRundown } from '../rundown/rundown.dao.js';
|
||||
import { parseRundowns } from '../rundown/rundown.parser.js';
|
||||
import { catchCommonImportXlsxError } from './googleApi.utils.js';
|
||||
@@ -224,7 +227,7 @@ export function hasAuth(): { authenticated: AuthenticationStatus; sheetId: strin
|
||||
async function verifySheet(
|
||||
sheetId = currentSheetId,
|
||||
authClient = currentAuthClient,
|
||||
): Promise<{ worksheetOptions: string[] }> {
|
||||
): Promise<string[]> {
|
||||
if (!sheetId || !authClient) {
|
||||
throw new Error('Missing sheet ID or authentication');
|
||||
}
|
||||
@@ -245,7 +248,7 @@ async function verifySheet(
|
||||
if (worksheets.length === 0) {
|
||||
throw new Error('No worksheets found');
|
||||
}
|
||||
return { worksheetOptions: worksheets };
|
||||
return worksheets;
|
||||
} catch (error) {
|
||||
// attempt to catch errors caused by importing xlsx
|
||||
catchCommonImportXlsxError(error);
|
||||
@@ -284,13 +287,61 @@ export async function handleInitialConnection(
|
||||
* Allow calling verification for sheetId
|
||||
* @returns
|
||||
*/
|
||||
export async function getWorksheetOptions(sheetId: string): ReturnType<typeof verifySheet> {
|
||||
export async function getWorksheetOptions(
|
||||
sheetId: string,
|
||||
): Promise<{ worksheets: string[]; metadata: SpreadsheetWorksheetMetadata | null }> {
|
||||
if (!currentAuthClient) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
currentSheetId = sheetId;
|
||||
|
||||
return verifySheet(sheetId);
|
||||
const worksheets = await verifySheet(sheetId);
|
||||
const metadata = await getInitialWorksheetMetadata(sheetId, worksheets);
|
||||
|
||||
return {
|
||||
worksheets,
|
||||
metadata,
|
||||
};
|
||||
}
|
||||
|
||||
async function getInitialWorksheetMetadata(
|
||||
sheetId: string,
|
||||
worksheets: string[],
|
||||
): Promise<SpreadsheetWorksheetMetadata | null> {
|
||||
for (const worksheet of worksheets) {
|
||||
try {
|
||||
return await getWorksheetMetadata(sheetId, worksheet);
|
||||
} catch {
|
||||
// Continue looking for the first worksheet with usable headers.
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function getWorksheetMetadata(sheetId: string, worksheet: string) {
|
||||
if (!currentAuthClient) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
||||
const { range } = await verifyWorksheet(sheetId, worksheet);
|
||||
|
||||
const googleResponse = await sheets({ version: 'v4', auth: currentAuthClient }).spreadsheets.values.get({
|
||||
spreadsheetId: sheetId,
|
||||
valueRenderOption: 'FORMATTED_VALUE',
|
||||
majorDimension: 'ROWS',
|
||||
range,
|
||||
});
|
||||
|
||||
if (googleResponse.status !== 200) {
|
||||
throw new Error(`Sheet read failed: ${googleResponse.statusText}`);
|
||||
}
|
||||
|
||||
if (!googleResponse.data.values) {
|
||||
throw new Error('Sheet: No data found in the worksheet');
|
||||
}
|
||||
|
||||
return getWorksheetMetadataFromRows(worksheet, googleResponse.data.values);
|
||||
}
|
||||
|
||||
async function verifyWorksheet(sheetId: string, worksheet: string): Promise<{ worksheetId: number; range: string }> {
|
||||
|
||||
@@ -30,6 +30,12 @@ export const validateRequestConnection = [
|
||||
|
||||
export const validateSheetId = [param('sheetId').isString().trim().notEmpty(), requestValidationFunction];
|
||||
|
||||
export const validateWorksheetMetadata = [
|
||||
param('sheetId').isString().trim().notEmpty(),
|
||||
body('worksheet').isString().trim().notEmpty(),
|
||||
requestValidationFunction,
|
||||
];
|
||||
|
||||
export const validateSheetOptions = [
|
||||
param('sheetId').isString().trim().notEmpty(),
|
||||
body('options')
|
||||
|
||||
Reference in New Issue
Block a user