From 713ced9c68cd70a8e56e8ce5ed27cb9bbe3e55c4 Mon Sep 17 00:00:00 2001 From: Jan Tuomi Date: Wed, 6 May 2026 21:46:21 +0300 Subject: Add shopping list features, rework --- src/sheets.ts | 203 ---------------------------------------------------------- 1 file changed, 203 deletions(-) delete mode 100644 src/sheets.ts (limited to 'src/sheets.ts') diff --git a/src/sheets.ts b/src/sheets.ts deleted file mode 100644 index 3578198..0000000 --- a/src/sheets.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { google } from "googleapis"; -import { - add, - set, - Duration, - parse, - format, - getDay, - subDays, - addDays, -} from "date-fns"; - -import config from "./config"; - -interface SheetsRawEntry { - intervalStr: string; - name: string; - lastDoneDateStr: string; -} - -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`); - } - - if (!Object.keys(INTERVAL_ABBREVS).includes(abbrev)) { - throw new Error(`"${abbrev}" is an invalid interval string`); - } - - 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 currentDateTime = new Date(); - const today = set(currentDateTime, { - hours: 0, - minutes: 0, - seconds: 0, - milliseconds: 0, - }); - 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. - scopes: [ - "https://www.googleapis.com/auth/spreadsheets", - "https://www.googleapis.com/auth/spreadsheets.readonly", - ], - keyFile: config.googleSaJsonPath, - }); - - google.options({ auth }); - const sheets = google.sheets({ version: "v4" }); - - const fetchSheetData = async (): Promise => { - try { - const res = await sheets.spreadsheets.values.get({ - spreadsheetId: config.sheetsSpreadsheetId, - range: config.sheetsRange, - }); - - 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; - } - }; - - 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); - - const body = { - values: newDateStamps.map((nds) => [nds]), - }; - - await sheets.spreadsheets.values.update({ - spreadsheetId: config.sheetsSpreadsheetId, - range: lastDoneColumnRange, - valueInputOption: "RAW", - requestBody: body, - }); - }; - - const processRows = (rows: SheetsRawEntry[]) => rows.map(processRow); - - const filterOnlyThisWeek = ( - entries: SheetsProcessedEntry[], - ): SheetsProcessedEntry[] => entries.filter(isThisWeek); - - const updatedLastDoneDateStamps = ( - entries: SheetsProcessedEntry[], - ): string[] => - entries.map((e) => (isThisWeek(e) ? e.nextDateStamp : e.lastDateStamp)); - - return { - fetchSheetData, - getRightmostColumnInRange, - updateSheetLastDoneColumn, - processRows, - filterOnlyThisWeek, - updatedLastDoneDateStamps, - }; -}; -- cgit v1.3