mirror of
https://github.com/Spengreb/sync.git
synced 2026-06-09 23:02:05 +00:00
Fix shows/bot API auth gaps, handle missing channels as 404, make recurrence DST-safe, and clear lint regressions
This commit is contained in:
parent
e3dd961430
commit
12696452aa
5 changed files with 97 additions and 11 deletions
|
|
@ -39,7 +39,7 @@ class Database {
|
||||||
database: Config.get('mysql.database'),
|
database: Config.get('mysql.database'),
|
||||||
multipleStatements: true, // Legacy thing
|
multipleStatements: true, // Legacy thing
|
||||||
charset: 'utf8mb4'
|
charset: 'utf8mb4'
|
||||||
}
|
};
|
||||||
} else {
|
} else {
|
||||||
connection = {
|
connection = {
|
||||||
host: Config.get('mysql.server'),
|
host: Config.get('mysql.server'),
|
||||||
|
|
@ -49,9 +49,9 @@ class Database {
|
||||||
database: Config.get('mysql.database'),
|
database: Config.get('mysql.database'),
|
||||||
multipleStatements: true, // Legacy thing
|
multipleStatements: true, // Legacy thing
|
||||||
charset: 'utf8mb4'
|
charset: 'utf8mb4'
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
knexConfig = {
|
knexConfig = {
|
||||||
client: 'mysql',
|
client: 'mysql',
|
||||||
connection,
|
connection,
|
||||||
|
|
|
||||||
88
src/shows.js
88
src/shows.js
|
|
@ -22,15 +22,93 @@ function makeSystemProxy(name) {
|
||||||
|
|
||||||
function computeNextRunAt(show) {
|
function computeNextRunAt(show) {
|
||||||
const base = Number(show.next_run_at || show.scheduled_for || Date.now());
|
const base = Number(show.next_run_at || show.scheduled_for || Date.now());
|
||||||
if (show.recurrence === 'daily') {
|
const recurrence = show.recurrence;
|
||||||
return base + 24 * 60 * 60 * 1000;
|
const timezone = show.timezone || 'UTC';
|
||||||
|
|
||||||
|
if (recurrence !== 'daily' && recurrence !== 'weekly') {
|
||||||
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (show.recurrence === 'weekly') {
|
const daysToAdd = recurrence === 'weekly' ? 7 : 1;
|
||||||
return base + 7 * 24 * 60 * 60 * 1000;
|
const source = new Date(base);
|
||||||
|
const local = toZonedParts(source, timezone);
|
||||||
|
if (!local) {
|
||||||
|
return base + (daysToAdd * 24 * 60 * 60 * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
return base;
|
const targetDate = addDaysUTC(local.year, local.month, local.day, daysToAdd);
|
||||||
|
const zonedTarget = {
|
||||||
|
year: targetDate.year,
|
||||||
|
month: targetDate.month,
|
||||||
|
day: targetDate.day,
|
||||||
|
hour: local.hour,
|
||||||
|
minute: local.minute,
|
||||||
|
second: local.second
|
||||||
|
};
|
||||||
|
|
||||||
|
const next = zonedDateTimeToUtc(zonedTarget, timezone);
|
||||||
|
return next || (base + (daysToAdd * 24 * 60 * 60 * 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toZonedParts(date, timezone) {
|
||||||
|
try {
|
||||||
|
const dtf = new Intl.DateTimeFormat('en-US', {
|
||||||
|
timeZone: timezone,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hour12: false
|
||||||
|
});
|
||||||
|
const parts = dtf.formatToParts(date);
|
||||||
|
const out = {};
|
||||||
|
for (const part of parts) {
|
||||||
|
if (part.type === 'literal') continue;
|
||||||
|
out[part.type] = parseInt(part.value, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
year: out.year,
|
||||||
|
month: out.month,
|
||||||
|
day: out.day,
|
||||||
|
hour: out.hour,
|
||||||
|
minute: out.minute,
|
||||||
|
second: out.second
|
||||||
|
};
|
||||||
|
} catch (_err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDaysUTC(year, month, day, days) {
|
||||||
|
const d = new Date(Date.UTC(year, month - 1, day + days));
|
||||||
|
return {
|
||||||
|
year: d.getUTCFullYear(),
|
||||||
|
month: d.getUTCMonth() + 1,
|
||||||
|
day: d.getUTCDate()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function zonedDateTimeToUtc(local, timezone) {
|
||||||
|
let guess = Date.UTC(local.year, local.month - 1, local.day, local.hour, local.minute, local.second);
|
||||||
|
|
||||||
|
// Iterate to resolve timezone offset for the target wall-clock time (handles DST shifts).
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
const zoned = toZonedParts(new Date(guess), timezone);
|
||||||
|
if (!zoned) return null;
|
||||||
|
|
||||||
|
const desired = Date.UTC(local.year, local.month - 1, local.day, local.hour, local.minute, local.second);
|
||||||
|
const current = Date.UTC(zoned.year, zoned.month - 1, zoned.day, zoned.hour, zoned.minute, zoned.second);
|
||||||
|
const delta = desired - current;
|
||||||
|
if (delta === 0) {
|
||||||
|
return guess;
|
||||||
|
}
|
||||||
|
guess += delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
return guess;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizePlaylist(rawPlaylist) {
|
function normalizePlaylist(rawPlaylist) {
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,11 @@ async function getChannelEmotes(channelId) {
|
||||||
(err, rows) => {
|
(err, rows) => {
|
||||||
if (err) return reject(new Error(err));
|
if (err) return reject(new Error(err));
|
||||||
if (!rows || rows.length === 0) return resolve([]);
|
if (!rows || rows.length === 0) return resolve([]);
|
||||||
try { resolve(JSON.parse(rows[0].value)); }
|
try {
|
||||||
catch (e) { resolve([]); }
|
resolve(JSON.parse(rows[0].value));
|
||||||
|
} catch (e) {
|
||||||
|
resolve([]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -92,7 +95,6 @@ router.put('/:name', botAuth, requireRank(4), async (req, res) => {
|
||||||
return res.status(409).json({ error: 'An emote with that name already exists' });
|
return res.status(409).json({ error: 'An emote with that name already exists' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const old = emotes[idx];
|
|
||||||
emotes[idx] = validated;
|
emotes[idx] = validated;
|
||||||
await saveChannelEmotes(req.bot.channel_id, emotes);
|
await saveChannelEmotes(req.bot.channel_id, emotes);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,7 @@ async function getChannelRow(channelName) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
db.channels.lookup(channelName, (err, row) => {
|
db.channels.lookup(channelName, (err, row) => {
|
||||||
if (err) reject(new Error(err));
|
if (err) reject(new Error(err));
|
||||||
|
else if (!row) reject(new Error('Channel not found'));
|
||||||
else resolve(row);
|
else resolve(row);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,11 @@ async function authorizeChannel(req, res) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (bot.rank < 2) {
|
||||||
|
res.status(403).json({ error: 'Insufficient rank' });
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
actorName: bot.name,
|
actorName: bot.name,
|
||||||
rank: bot.rank,
|
rank: bot.rank,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue