Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add createdAt and updatedAt fields to public tables #587

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
0e53a4d
feat: create migration
TristanWasTaken Aug 3, 2024
8095eb7
feat(vipUsers): impl createdAt
TristanWasTaken Aug 3, 2024
1681271
feat(sponsorTimes): impl updatedAt
TristanWasTaken Aug 3, 2024
886c0ca
feat(userNames): impl createdAt and updatedAt
TristanWasTaken Aug 3, 2024
8645272
feat(categoryVotes): impl createdAt and updatedAt
TristanWasTaken Aug 3, 2024
ebc3b2c
feat(lockCategories): impl updatedAt
TristanWasTaken Aug 3, 2024
8859729
fix(sponsorTimes): update updatedAt on updates
TristanWasTaken Aug 3, 2024
0faffa0
feat(titleVotes): impl createdAt and updatedAt
TristanWasTaken Aug 3, 2024
3c6855c
fix(sponsorTimes): fix migration
TristanWasTaken Aug 3, 2024
484fa9f
fix: update config version
TristanWasTaken Aug 3, 2024
dad8e63
feat(thumbnailVotes): impl updatedAt
TristanWasTaken Aug 3, 2024
6530484
feat(vipUsers): change timestamps to ISO 8601
TristanWasTaken Aug 19, 2024
2ea44c9
feat(sponsorTimes): change timestamps to ISO 8601
TristanWasTaken Aug 19, 2024
81d5816
feat(userNames): change timestamps to ISO 8601
TristanWasTaken Aug 19, 2024
eca5abe
feat(categoryVotes): change timestamps to ISO 8601
TristanWasTaken Aug 19, 2024
106505a
feat(lockCategories): change timestamps to ISO 8601
TristanWasTaken Aug 19, 2024
f72d752
feat(lockCategories): change timestamps to ISO 8601
TristanWasTaken Aug 19, 2024
26faa36
feat(thumbnailVotes): change timestamps to ISO 8601
TristanWasTaken Aug 19, 2024
88e3bca
docs(DatabaseSchema): update timestamp field types
TristanWasTaken Aug 19, 2024
a7c81bc
test(vipUsers): impl createdAt
TristanWasTaken Sep 16, 2024
5ff8599
test(sponsorTimes): impl updatedAt for insert queries
TristanWasTaken Sep 16, 2024
2919d06
test(sponsorTimes): impl updatedAt for update queries
TristanWasTaken Sep 16, 2024
f35e50c
test(userNames): impl updatedAt
TristanWasTaken Sep 16, 2024
fae043f
test(lockCategories): impl createdAt and updatedAt
TristanWasTaken Sep 16, 2024
32fd897
test(titleVotes): impl createdAt and updatedAt
TristanWasTaken Sep 16, 2024
829fd5d
test(thumbnailVotes): impl createdAt and updatedAt
TristanWasTaken Sep 16, 2024
a701486
refactor: change isoTimestamp to isoDate
TristanWasTaken Sep 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions DatabaseSchema.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
| Name | Type | |
| -- | :--: | -- |
| userID | TEXT | not null, primary key |
| createdAt | TEXT | not null |

| index | field |
| -- | :--: |
Expand Down Expand Up @@ -53,6 +54,7 @@
| hashedVideoID | TEXT | not null, default '', sha256 |
| userAgent | TEXT | not null, default '' |
| description | TEXT | not null, default '' |
| updatedAt | TEXT | not null |

| index | field |
| -- | :--: |
Expand All @@ -71,6 +73,8 @@
| userID | TEXT | not null, primary key |
| userName | TEXT | not null |
| locked | INTEGER | not nul, default '0' |
| createdAt | TEXT | not null |
| updatedAt | TEXT | not null |

| index | field |
| -- | :--: |
Expand All @@ -84,6 +88,8 @@
| category | TEXT | not null |
| votes | INTEGER | not null, default 0 |
| id | SERIAL | primary key
| createdAt | TEXT | not null |
| updatedAt | TEXT | not null |

| index | field |
| -- | :--: |
Expand All @@ -101,6 +107,8 @@
| reason | TEXT | not null, default '' |
| service | TEXT | not null, default 'YouTube' |
| id | SERIAL | primary key
| createdAt | TEXT | not null |
| updatedAt | TEXT | not null |

| index | field |
| -- | :--: |
Expand Down Expand Up @@ -264,6 +272,8 @@
| verification | INTEGER | default 0 |
| downvotes | INTEGER | default 0 |
| removed | INTEGER | default 0 |
| createdAt | TEXT | not null |
| updatedAt | TEXT | not null |

| constraint | field |
| -- | :--: |
Expand Down Expand Up @@ -312,6 +322,8 @@
| shadowHidden | INTEGER | not null, default 0 |
| downvotes | INTEGER | default 0 |
| removed | INTEGER | default 0 |
| createdAt | TEXT | not null |
| updatedAt | TEXT | not null |

| constraint | field |
| -- | :--: |
Expand Down Expand Up @@ -404,7 +416,7 @@
| issuerUserID | TEXT | not null |
| targetUserID | TEXT | not null |
| enabled | BOOLEAN | not null |
| updatedAt | INTEGER | not null |
| updatedAt | TEXT | not null |
| id | SERIAL | primary key |

### userNameLogs
Expand All @@ -415,5 +427,5 @@
| newUserName | TEXT | not null |
| oldUserName | TEXT | not null |
| updatedByAdmin | BOOLEAN | not null |
| updatedAt | INTEGER | not null |
| updatedAt | TEXT | not null |
| id | SERIAL | primary key |
32 changes: 32 additions & 0 deletions databases/_upgrade_sponsorTimes_41.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
BEGIN TRANSACTION;

-- vipUsers
ALTER TABLE "vipUsers" ADD "createdAt" TEXT NOT NULL;

-- sponsorTimes
-- Don't create createdAt -> timeSubmitted field
ALTER TABLE "sponsorTimes" ADD "updatedAt" TEXT NOT NULL;

-- userNames
ALTER TABLE "userNames" ADD "createdAt" TEXT NOT NULL;
ALTER TABLE "userNames" ADD "updatedAt" TEXT NOT NULL;

-- categoryVotes
ALTER TABLE "categoryVotes" ADD "createdAt" TEXT NOT NULL;
ALTER TABLE "categoryVotes" ADD "updatedAt" TEXT NOT NULL;

-- lockCategories
ALTER TABLE "lockCategories" ADD "createdAt" TEXT NOT NULL;
ALTER TABLE "lockCategories" ADD "updatedAt" TEXT NOT NULL;

-- titleVotes
ALTER TABLE "titleVotes" ADD "createdAt" TEXT NOT NULL;
ALTER TABLE "titleVotes" ADD "updatedAt" TEXT NOT NULL;

-- thumbnailVotes
ALTER TABLE "thumbnailVotes" ADD "createdAt" TEXT NOT NULL;
ALTER TABLE "thumbnailVotes" ADD "updatedAt" TEXT NOT NULL;

UPDATE "config" SET value = 41 WHERE key = 'version';

COMMIT;
7 changes: 6 additions & 1 deletion src/routes/addUserAsVIP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,12 @@ export async function addUserAsVIP(req: AddUserAsVIPRequest, res: Response): Pro
try {
if (enabled && !userIsVIP) {
// add them to the vip list
await db.prepare("run", 'INSERT INTO "vipUsers" VALUES(?)', [userID]);
const isoDate = new Date().toISOString();
await db.prepare(
"run",
'INSERT INTO "vipUsers" ("userID", "createdAt") VALUES(?, ?)',
[userID, isoDate]
);
}

if (!enabled && userIsVIP) {
Expand Down
16 changes: 9 additions & 7 deletions src/routes/postBranding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export async function postBranding(req: Request, res: Response) {
}

const now = Date.now();
const isoDate = new Date(now).toISOString();
const voteType: BrandingVoteType = downvote ? BrandingVoteType.Downvote : BrandingVoteType.Upvote;

if (title && !isVip && title.title.length > config.maxTitleLength) {
Expand Down Expand Up @@ -104,15 +105,15 @@ export async function postBranding(req: Request, res: Response) {
[videoID, title.title, title.original ? 1 : 0, hashedUserID, service, hashedVideoID, now, UUID]);

const verificationValue = await getVerificationValue(hashedUserID, isVip);
await db.prepare("run", `INSERT INTO "titleVotes" ("UUID", "votes", "locked", "shadowHidden", "verification") VALUES (?, 0, ?, ?, ?);`,
[UUID, shouldLock ? 1 : 0, isBanned ? 1 : 0, verificationValue]);
await db.prepare("run", `INSERT INTO "titleVotes" ("UUID", "votes", "locked", "shadowHidden", "verification", "createdAt", "updatedAt") VALUES (?, 0, ?, ?, ?, ?, ?);`,
[UUID, shouldLock ? 1 : 0, isBanned ? 1 : 0, verificationValue, isoDate, isoDate]);

await verifyOldSubmissions(hashedUserID, verificationValue);
}

if (isVip && !downvote && shouldLock) {
// unlock all other titles
await db.prepare("run", `UPDATE "titleVotes" as tv SET "locked" = 0 FROM "titles" t WHERE tv."UUID" = t."UUID" AND tv."UUID" != ? AND t."videoID" = ?`, [UUID, videoID]);
await db.prepare("run", `UPDATE "titleVotes" as tv SET "locked" = 0, "updatedAt" = ? FROM "titles" t WHERE tv."UUID" = t."UUID" AND tv."UUID" != ? AND t."videoID" = ?`, [isoDate, UUID, videoID]);
}

sendWebhooks(videoID, UUID, voteType).catch((e) => Logger.error(e));
Expand Down Expand Up @@ -145,8 +146,8 @@ export async function postBranding(req: Request, res: Response) {
await db.prepare("run", `INSERT INTO "thumbnails" ("videoID", "original", "userID", "service", "hashedVideoID", "timeSubmitted", "UUID") VALUES (?, ?, ?, ?, ?, ?, ?)`,
[videoID, thumbnail.original ? 1 : 0, hashedUserID, service, hashedVideoID, now, UUID]);

await db.prepare("run", `INSERT INTO "thumbnailVotes" ("UUID", "votes", "locked", "shadowHidden") VALUES (?, 0, ?, ?)`,
[UUID, shouldLock ? 1 : 0, isBanned ? 1 : 0]);
await db.prepare("run", `INSERT INTO "thumbnailVotes" ("UUID", "votes", "locked", "shadowHidden", "createdAt", "updatedAt") VALUES (?, 0, ?, ?, ?, ?)`,
[UUID, shouldLock ? 1 : 0, isBanned ? 1 : 0, isoDate, isoDate]);

if (!thumbnail.original) {
await db.prepare("run", `INSERT INTO "thumbnailTimestamps" ("UUID", "timestamp") VALUES (?, ?)`,
Expand All @@ -156,7 +157,7 @@ export async function postBranding(req: Request, res: Response) {

if (isVip && !downvote && shouldLock) {
// unlock all other titles
await db.prepare("run", `UPDATE "thumbnailVotes" as tv SET "locked" = 0 FROM "thumbnails" t WHERE tv."UUID" = t."UUID" AND tv."UUID" != ? AND t."videoID" = ?`, [UUID, videoID]);
await db.prepare("run", `UPDATE "thumbnailVotes" as tv SET "locked" = 0, "updatedAt" = ? FROM "thumbnails" t WHERE tv."UUID" = t."UUID" AND tv."UUID" != ? AND t."videoID" = ?`, [isoDate, UUID, videoID]);
}
}
})()]);
Expand Down Expand Up @@ -284,6 +285,7 @@ export async function getVerificationValue(hashedUserID: HashedUserID, isVip: bo
export async function verifyOldSubmissions(hashedUserID: HashedUserID, verification: number): Promise<void> {
if (verification >= 0) {
const unverifiedSubmissions = await db.prepare("all", `SELECT "videoID", "hashedVideoID", "service" FROM "titles" JOIN "titleVotes" ON "titles"."UUID" = "titleVotes"."UUID" WHERE "titles"."userID" = ? AND "titleVotes"."verification" < ? GROUP BY "videoID", "hashedVideoID", "service"`, [hashedUserID, verification]);
const isoDate = new Date().toISOString();

if (unverifiedSubmissions.length > 0) {
for (const submission of unverifiedSubmissions) {
Expand All @@ -294,7 +296,7 @@ export async function verifyOldSubmissions(hashedUserID: HashedUserID, verificat
});
}

await db.prepare("run", `UPDATE "titleVotes" as tv SET "verification" = ? FROM "titles" WHERE "titles"."UUID" = tv."UUID" AND "titles"."userID" = ? AND tv."verification" < ?`, [verification, hashedUserID, verification]);
await db.prepare("run", `UPDATE "titleVotes" as tv SET "verification" = ?, "updatedAt" = ? FROM "titles" WHERE "titles"."UUID" = tv."UUID" AND "titles"."userID" = ? AND tv."verification" < ?`, [verification, isoDate, hashedUserID, verification]);
}
}
}
Expand Down
11 changes: 8 additions & 3 deletions src/routes/postLockCategories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,16 @@ export async function postLockCategories(req: Request, res: Response): Promise<s

// calculate hash of videoID
const hashedVideoID: VideoIDHash = await getHashCache(videoID, 1);
const isoDate = new Date().toISOString();

// create database entry
for (const lock of locksToApply) {
try {
await db.prepare("run", `INSERT INTO "lockCategories" ("videoID", "userID", "actionType", "category", "hashedVideoID", "reason", "service") VALUES(?, ?, ?, ?, ?, ?, ?)`, [videoID, userID, lock.actionType, lock.category, hashedVideoID, reason, service]);
await db.prepare(
"run",
`INSERT INTO "lockCategories" ("videoID", "userID", "actionType", "category", "hashedVideoID", "reason", "service", "createdAt", "updatedAt") VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[videoID, userID, lock.actionType, lock.category, hashedVideoID, reason, service, isoDate, isoDate]
);
} catch (err) /* istanbul ignore next */ {
Logger.error(`Error submitting 'lockCategories' marker for category '${lock.category}' and actionType '${lock.actionType}' for video '${videoID}' (${service})`);
Logger.error(err as string);
Expand All @@ -80,8 +85,8 @@ export async function postLockCategories(req: Request, res: Response): Promise<s
for (const lock of overwrittenLocks) {
try {
await db.prepare("run",
'UPDATE "lockCategories" SET "reason" = ?, "userID" = ? WHERE "videoID" = ? AND "actionType" = ? AND "category" = ? AND "service" = ?',
[reason, userID, videoID, lock.actionType, lock.category, service]);
'UPDATE "lockCategories" SET "reason" = ?, "userID" = ?, "updatedAt" = ? WHERE "videoID" = ? AND "actionType" = ? AND "category" = ? AND "service" = ?',
[reason, userID, isoDate, videoID, lock.actionType, lock.category, service]);
} catch (err) /* istanbul ignore next */ {
Logger.error(`Error submitting 'lockCategories' marker for category '${lock.category}' and actionType '${lock.actionType}' for video '${videoID}' (${service})`);
Logger.error(err as string);
Expand Down
3 changes: 2 additions & 1 deletion src/routes/postPurgeAllSegments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export async function postPurgeAllSegments(req: Request, res: Response): Promise
});
}

await db.prepare("run", `UPDATE "sponsorTimes" SET "hidden" = 1 WHERE "videoID" = ?`, [videoID]);
const isoDate = new Date().toISOString();
await db.prepare("run", `UPDATE "sponsorTimes" SET "hidden" = 1, "updatedAt" = ? WHERE "videoID" = ?`, [isoDate, videoID]);

const hashedVideoID: VideoIDHash = await getHashCache(videoID, 1);
QueryCacher.clearSegmentCache({
Expand Down
7 changes: 4 additions & 3 deletions src/routes/postSegmentShift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,19 +79,20 @@ export async function postSegmentShift(req: Request, res: Response): Promise<Res
startTime,
endTime,
};
const isoDate = new Date().toISOString();

for (const segment of segments) {
const result = shiftSegment(segment, shift);
switch (result.action) {
case ACTION_UPDATE:
await db.prepare("run", 'UPDATE "sponsorTimes" SET "startTime" = ?, "endTime" = ? WHERE "UUID" = ?', [result.segment.startTime, result.segment.endTime, result.segment.UUID]);
await db.prepare("run", 'UPDATE "sponsorTimes" SET "startTime" = ?, "endTime" = ?, "updatedAt" = ? WHERE "UUID" = ?', [result.segment.startTime, result.segment.endTime, isoDate, result.segment.UUID]);
break;
case ACTION_REMOVE:
await db.prepare("run", 'UPDATE "sponsorTimes" SET "startTime" = ?, "endTime" = ?, "votes" = -2 WHERE "UUID" = ?', [result.segment.startTime, result.segment.endTime, result.segment.UUID]);
await db.prepare("run", 'UPDATE "sponsorTimes" SET "startTime" = ?, "endTime" = ?, "votes" = -2, "updatedAt" = ? WHERE "UUID" = ?', [result.segment.startTime, result.segment.endTime, isoDate, result.segment.UUID]);
break;
}
}
} catch (err) /* istanbul ignore next */ {
} catch (err) /* istanbul ignore next */ {
Logger.error(err as string);
return res.sendStatus(500);
}
Expand Down
13 changes: 8 additions & 5 deletions src/routes/postSkipSegments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,12 +392,14 @@ async function updateDataIfVideoDurationChange(videoID: VideoID, service: Servic

// Only treat as difference if both the api duration and submitted duration have changed
if (videoDurationChanged(videoDuration) && (!videoDurationParam || videoDurationChanged(videoDurationParam))) {
const isoDate = new Date().toISOString();

// Hide all previous submissions
await db.prepare("run", `UPDATE "sponsorTimes" SET "hidden" = 1
await db.prepare("run", `UPDATE "sponsorTimes" SET "hidden" = 1, "updatedAt" = ?
WHERE "videoID" = ? AND "service" = ? AND "videoDuration" != ?
AND "hidden" = 0 AND "shadowHidden" = 0 AND
"actionType" != 'full' AND "votes" > -2`,
[videoID, service, videoDuration]);
[isoDate, videoID, service, videoDuration]);

lockedCategoryList = [];
deleteLockCategories(videoID, null, null, service).catch((e) => Logger.error(`deleting lock categories: ${e}`));
Expand Down Expand Up @@ -558,6 +560,7 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
const hashedIP = await getHashCache(rawIP + config.globalSalt) as HashedIP;

const timeSubmitted = Date.now();
const isoDate = new Date(timeSubmitted).toISOString();

// const rateLimitCheckResult = checkRateLimit(userID, videoID, service, timeSubmitted, hashedIP);
// if (!rateLimitCheckResult.pass) {
Expand Down Expand Up @@ -586,10 +589,10 @@ export async function postSkipSegments(req: Request, res: Response): Promise<Res
const startingLocked = isVIP ? 1 : 0;
try {
await db.prepare("run", `INSERT INTO "sponsorTimes"
("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "actionType", "service", "videoDuration", "reputation", "shadowHidden", "hashedVideoID", "userAgent", "description")
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
("videoID", "startTime", "endTime", "votes", "locked", "UUID", "userID", "timeSubmitted", "views", "category", "actionType", "service", "videoDuration", "reputation", "shadowHidden", "hashedVideoID", "userAgent", "description", "updatedAt")
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
videoID, segmentInfo.segment[0], segmentInfo.segment[1], startingVotes, startingLocked, UUID, userID, timeSubmitted, 0
, segmentInfo.category, segmentInfo.actionType, service, videoDuration, reputation, isBanned ? 1 : 0, hashedVideoID, userAgent, segmentInfo.description
, segmentInfo.category, segmentInfo.actionType, service, videoDuration, reputation, isBanned ? 1 : 0, hashedVideoID, userAgent, segmentInfo.description, isoDate
],
);

Expand Down
7 changes: 4 additions & 3 deletions src/routes/setUsername.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Request, Response } from "express";
import { isUserBanned } from "../utils/checkBan";
import { HashedUserID } from "../types/user.model";

function logUserNameChange(userID: string, newUserName: string, oldUserName: string, updatedByAdmin: boolean): Promise<Response> {
function logUserNameChange(userID: string, newUserName: string, oldUserName: string, updatedByAdmin: boolean): Promise<Response> {
return privateDB.prepare("run",
`INSERT INTO "userNameLogs"("userID", "newUserName", "oldUserName", "updatedByAdmin", "updatedAt") VALUES(?, ?, ?, ?, ?)`,
[userID, newUserName, oldUserName, + updatedByAdmin, new Date().getTime()]
Expand Down Expand Up @@ -79,6 +79,7 @@ export async function setUsername(req: Request, res: Response): Promise<Response
const row = await db.prepare("get", `SELECT "userName" FROM "userNames" WHERE "userID" = ? LIMIT 1`, [hashedUserID]);
const locked = adminUserIDInput === undefined ? 0 : 1;
let oldUserName = "";
const isoDate = new Date().toISOString();

timings.push(Date.now());

Expand All @@ -88,11 +89,11 @@ export async function setUsername(req: Request, res: Response): Promise<Response
if (userName == hashedUserID && !locked) {
await db.prepare("run", `DELETE FROM "userNames" WHERE "userID" = ?`, [hashedUserID]);
} else {
await db.prepare("run", `UPDATE "userNames" SET "userName" = ?, "locked" = ? WHERE "userID" = ?`, [userName, locked, hashedUserID]);
await db.prepare("run", `UPDATE "userNames" SET "userName" = ?, "locked" = ?, "updatedAt" = ? WHERE "userID" = ?`, [userName, locked, isoDate, hashedUserID]);
}
} else {
//add to the db
await db.prepare("run", `INSERT INTO "userNames"("userID", "userName", "locked") VALUES(?, ?, ?)`, [hashedUserID, userName, locked]);
await db.prepare("run", `INSERT INTO "userNames"("userID", "userName", "locked", "createdAt", "updatedAt") VALUES(?, ?, ?, ?, ?)`, [hashedUserID, userName, locked, isoDate, isoDate]);
}

timings.push(Date.now());
Expand Down
Loading
Loading