diff options
Diffstat (limited to 'src/sheets.ts')
| -rw-r--r-- | src/sheets.ts | 194 |
1 files changed, 142 insertions, 52 deletions
diff --git a/src/sheets.ts b/src/sheets.ts index 8f6c81c..e99f089 100644 --- a/src/sheets.ts +++ b/src/sheets.ts @@ -1,30 +1,101 @@ -import got from "got"; import { google } from "googleapis"; +import { add, Duration, parse, format, getDay, subDays, addDays } from "date-fns"; + import config from "./config"; -export const fetchIdToken = async function (aud: string): Promise<string> { - const metadataServerTokenURL = `http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=${aud}`; +interface SheetsRawEntry { + intervalStr: string; + name: string; + lastDoneDateStr: string; +} - let resp; - try { - resp = await got(metadataServerTokenURL, { - headers: { - "Metadata-Flavor": "Google", - }, - }); - } catch (err) { - console.error(err); - throw new Error("Failed to fetch ID token from Google metadata endpoint"); +interface SheetsProcessedEntry { + name: string; + nextDateStamp: string; + nextDate: Date; + lastDateStamp: string; + lastDate?: Date; + interval: string; +} + +const INTERVAL_ABBREVS = { + pv: 1, + vk: 7, + kk: 30, + v: 365, +} as const; + +type IntervalAbbrevKey = keyof typeof INTERVAL_ABBREVS; + +const strToDate = (s: string) => parse(s, "yyyy-MM-dd", new Date()); +const dateToStr = (d: Date) => format(d, "yyyy-MM-dd"); + +const calculateDuration = (intervalStr: string) => { + const match = /(\d+)(\w+)/.exec(intervalStr); + + if (!match) { + throw new Error("Given interval string does not match regular expression"); + } + + const number = Number.parseInt(match[1]); + const abbrev = match[2]; + + if (number <= 0) { + throw new Error(`"${number}" is an invalid interval number`); } - const token = resp.body; - if (!token) { - throw new Error("ID token from Google metadata endpoint is empty"); + if (!Object.keys(INTERVAL_ABBREVS).includes(abbrev)) { + throw new Error(`"${abbrev}" is an invalid interval string`); } - return token; + const days = INTERVAL_ABBREVS[abbrev as IntervalAbbrevKey]; + + const totalDuration: Duration = { + days: number * days, + }; + + return totalDuration; +}; + +const processRow = (row: SheetsRawEntry) => { + const duration = calculateDuration(row.intervalStr); + + const calcValues = () => { + if (row.lastDoneDateStr) { + const lastDate = strToDate(row.lastDoneDateStr); + const nextDate = add(lastDate, duration); + const nextDateStamp = dateToStr(nextDate); + + return { lastDate, nextDate, nextDateStamp }; + } else { + const lastDate = undefined; + const nextDate = new Date(); + const nextDateStamp = dateToStr(nextDate); + + return { lastDate, nextDate, nextDateStamp }; + } + }; + + const processedEntry: SheetsProcessedEntry = { + ...calcValues(), + name: row.name, + lastDateStamp: row.lastDoneDateStr, + interval: row.intervalStr, + }; + + return processedEntry; +}; + +const isThisWeek = (entry: SheetsProcessedEntry): boolean => { + const nextDate = entry.nextDate; + const today = new Date(); + const dayOfWeek = (getDay(today) + 7 - 1) % 7; // getDay returns sunday = 0 + const currentWeekStart = subDays(today, dayOfWeek); + const currentWeekEnd = addDays(currentWeekStart, 7); + return nextDate >= currentWeekStart && nextDate < currentWeekEnd; }; +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types export const buildSheetsClient = async () => { const auth = new google.auth.GoogleAuth({ // Scopes can be specified either as an array or as a single, space-delimited string. @@ -32,64 +103,83 @@ export const buildSheetsClient = async () => { "https://www.googleapis.com/auth/spreadsheets", "https://www.googleapis.com/auth/spreadsheets.readonly", ], + keyFile: config.googleSaJsonPath, }); // Acquire an auth client, and bind it to all future calls const authClient = await auth.getClient(); google.options({ auth: authClient }); + const sheets = google.sheets({ version: "v4" }); - const fetchSheetData = async (): Promise<void> => { - // TODO + const fetchSheetData = async (): Promise<SheetsRawEntry[]> => { try { - const sheets = google.sheets({ version: "v4" }); const res = await sheets.spreadsheets.values.get({ spreadsheetId: config.sheetsSpreadsheetId, range: config.sheetsRange, }); - console.log("res", res); + const entriesAsLists = res.data.values || []; + const rawEntries: SheetsRawEntry[] = entriesAsLists.map(row => ({ + intervalStr: row[0], + name: row[1], + lastDoneDateStr: row[2], + })); + + console.log(`Fetched ${rawEntries.length} rows from Sheets.`); + return rawEntries; } catch (err) { console.error(err); throw err; } }; - return { - fetchSheetData, + const getRightmostColumnInRange = (range: string): string => { + const match = /(.+!)?([A-Z]+?)(\d+):([A-Z]+?)(\d+)/.exec(range); + + if (!match) { + throw new Error("Given range does not match regular expression"); + } + + const sheetName = match[1]; + const topLeftRow = Number.parseInt(match[3]); + const bottomRightCol = match[4]; + const bottomRightRow = Number.parseInt(match[5]); + + const rmCol = bottomRightCol; + const rmRowStart = topLeftRow; + const rmRowEnd = bottomRightRow; + + return `${sheetName}${rmCol}${rmRowStart}:${rmCol}${rmRowEnd}`; }; -}; + const updateSheetLastDoneColumn = async (newDateStamps: string[]) => { + const lastDoneColumnRange = getRightmostColumnInRange(config.sheetsRange); -/* -def fetch_sheet_data(service: Resource) -> list[SheetsRow]: - logging.info("Calling Sheets API to fetch data...") - sheet = service.spreadsheets() - result = ( - sheet.values() - .get( - spreadsheetId=getenv("SHEETS_SPREADSHEET_ID"), - range=getenv("SHEETS_RANGE"), - ) - .execute() - ) + const body = { + values: newDateStamps.map(nds => [nds]), + }; - rows = result.get("values", []) - logging.info(f"Fetched {len(rows)} rows of data") - return [_row_to_dataclass(row) for row in rows] + await sheets.spreadsheets.values.update({ + spreadsheetId: config.sheetsSpreadsheetId, + range: lastDoneColumnRange, + valueInputOption: "RAW", + requestBody: body, + }); + }; + const processRows = (rows: SheetsRawEntry[]) => rows.map(processRow); -def update_sheet_last_done_column(service: Resource, new_datestamps: list[str]): - logging.info("Calling Sheets API to update last done column...") - range = getenv("SHEETS_RANGE") - sheet = service.spreadsheets() - last_done_column_range = _get_rightmost_column_in_range(range) + const filterOnlyThisWeek = (entries: SheetsProcessedEntry[]): SheetsProcessedEntry[] => entries.filter(isThisWeek); - body = {"values": [[datestamp] for datestamp in new_datestamps]} + const updatedLastDoneDateStamps = (entries: SheetsProcessedEntry[]): string[] => + entries.map(e => isThisWeek(e) ? e.nextDateStamp : e.lastDateStamp); - sheet.values().update( - spreadsheetId=getenv("SHEETS_SPREADSHEET_ID"), - range=last_done_column_range, - valueInputOption="RAW", - body=body, - ).execute() -*/
\ No newline at end of file + return { + fetchSheetData, + getRightmostColumnInRange, + updateSheetLastDoneColumn, + processRows, + filterOnlyThisWeek, + updatedLastDoneDateStamps, + }; +}; |
