Merge pull request #200 from ajayyy/improve-types

Improve Types
This commit is contained in:
Ajay Ramachandran
2021-01-17 15:15:37 -05:00
committed by GitHub
5 changed files with 38 additions and 24 deletions

View File

@@ -1,7 +1,8 @@
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { config } from '../config'; import { config } from '../config';
import { db, privateDB } from '../databases/databases'; import { db, privateDB } from '../databases/databases';
import { Category, DBSegment, OverlappingSegmentGroup, Segment, SegmentCache, VideoData, VideoID, VideoIDHash, Visibility, VotableObject } from "../types/segments.model"; import { SBRecord } from '../types/lib.model';
import { Category, DBSegment, HashedIP, IPAddress, OverlappingSegmentGroup, Segment, SegmentCache, VideoData, VideoID, VideoIDHash, Visibility, VotableObject } from "../types/segments.model";
import { getHash } from '../utils/getHash'; import { getHash } from '../utils/getHash';
import { getIP } from '../utils/getIP'; import { getIP } from '../utils/getIP';
import { Logger } from '../utils/logger'; import { Logger } from '../utils/logger';
@@ -20,14 +21,14 @@ function prepareCategorySegments(req: Request, videoID: VideoID, category: Categ
} }
if (cache.shadowHiddenSegmentIPs[videoID] === undefined) { if (cache.shadowHiddenSegmentIPs[videoID] === undefined) {
cache.shadowHiddenSegmentIPs[videoID] = privateDB.prepare('all', 'SELECT hashedIP FROM sponsorTimes WHERE videoID = ?', [videoID]); cache.shadowHiddenSegmentIPs[videoID] = privateDB.prepare('all', 'SELECT hashedIP FROM sponsorTimes WHERE videoID = ?', [videoID]) as { hashedIP: HashedIP }[];
} }
//if this isn't their ip, don't send it to them //if this isn't their ip, don't send it to them
return cache.shadowHiddenSegmentIPs[videoID].some((shadowHiddenSegment) => { return cache.shadowHiddenSegmentIPs[videoID].some((shadowHiddenSegment) => {
if (cache.userHashedIP === undefined) { if (cache.userHashedIP === undefined) {
//hash the IP only if it's strictly necessary //hash the IP only if it's strictly necessary
cache.userHashedIP = getHash(getIP(req) + config.globalSalt); cache.userHashedIP = getHash((getIP(req) + config.globalSalt) as IPAddress);
} }
return shadowHiddenSegment.hashedIP === cache.userHashedIP; return shadowHiddenSegment.hashedIP === cache.userHashedIP;
@@ -46,12 +47,12 @@ function getSegmentsByVideoID(req: Request, videoID: string, categories: Categor
const segments: Segment[] = []; const segments: Segment[] = [];
try { try {
const segmentsByCategory: Record<Category, DBSegment[]> = db const segmentsByCategory: SBRecord<Category, DBSegment[]> = db
.prepare( .prepare(
'all', 'all',
`SELECT startTime, endTime, votes, UUID, category, shadowHidden FROM sponsorTimes WHERE videoID = ? AND category IN (${Array(categories.length).fill('?').join()}) ORDER BY startTime`, `SELECT startTime, endTime, votes, UUID, category, shadowHidden FROM sponsorTimes WHERE videoID = ? AND category IN (${Array(categories.length).fill('?').join()}) ORDER BY startTime`,
[videoID, categories] [videoID, categories]
).reduce((acc: Record<Category, DBSegment[]>, segment: DBSegment) => { ).reduce((acc: SBRecord<Category, DBSegment[]>, segment: DBSegment) => {
acc[segment.category] = acc[segment.category] || []; acc[segment.category] = acc[segment.category] || [];
acc[segment.category].push(segment); acc[segment.category].push(segment);
@@ -59,7 +60,7 @@ function getSegmentsByVideoID(req: Request, videoID: string, categories: Categor
}, {}); }, {});
for (const [category, categorySegments] of Object.entries(segmentsByCategory)) { for (const [category, categorySegments] of Object.entries(segmentsByCategory)) {
segments.push(...prepareCategorySegments(req, videoID, category, categorySegments, cache)); segments.push(...prepareCategorySegments(req, videoID as VideoID, category as Category, categorySegments, cache));
} }
return segments; return segments;
@@ -71,12 +72,12 @@ function getSegmentsByVideoID(req: Request, videoID: string, categories: Categor
} }
} }
function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, categories: Category[]): Record<VideoID, VideoData> { function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, categories: Category[]): SBRecord<VideoID, VideoData> {
const cache: SegmentCache = {shadowHiddenSegmentIPs: {}}; const cache: SegmentCache = {shadowHiddenSegmentIPs: {}};
const segments: Record<VideoID, VideoData> = {}; const segments: SBRecord<VideoID, VideoData> = {};
try { try {
type SegmentWithHashPerVideoID = Record<VideoID, {hash: VideoIDHash, segmentPerCategory: Record<Category, DBSegment[]>}>; type SegmentWithHashPerVideoID = SBRecord<VideoID, {hash: VideoIDHash, segmentPerCategory: SBRecord<Category, DBSegment[]>}>;
const segmentPerVideoID: SegmentWithHashPerVideoID = db const segmentPerVideoID: SegmentWithHashPerVideoID = db
.prepare( .prepare(
@@ -103,7 +104,7 @@ function getSegmentsByHash(req: Request, hashedVideoIDPrefix: VideoIDHash, categ
}; };
for (const [category, segmentPerCategory] of Object.entries(videoData.segmentPerCategory)) { for (const [category, segmentPerCategory] of Object.entries(videoData.segmentPerCategory)) {
segments[videoID].segments.push(...prepareCategorySegments(req, videoID, category, segmentPerCategory, cache)); segments[videoID].segments.push(...prepareCategorySegments(req, videoID as VideoID, category as Category, segmentPerCategory, cache));
} }
} }

View File

@@ -4,7 +4,7 @@ import {Request, Response} from 'express';
import { Category, VideoIDHash } from '../types/segments.model'; import { Category, VideoIDHash } from '../types/segments.model';
export async function getSkipSegmentsByHash(req: Request, res: Response) { export async function getSkipSegmentsByHash(req: Request, res: Response) {
let hashPrefix: VideoIDHash = req.params.prefix; let hashPrefix = req.params.prefix as VideoIDHash;
if (!hashPrefixTester(req.params.prefix)) { if (!hashPrefixTester(req.params.prefix)) {
res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix res.status(400).send("Hash prefix does not match format requirements."); // Exit early on faulty prefix
return; return;

7
src/types/lib.model.ts Normal file
View File

@@ -0,0 +1,7 @@
/**
* Better ecord that will work with branded types
* Keys still don't work properly though and are always string
*/
export type SBRecord<K extends string, T> = {
[P in string | K]: T;
};

View File

@@ -1,8 +1,12 @@
export type SegmentUUID = string; import { HashedValue } from "./hash.model";
export type VideoID = string; import { SBRecord } from "./lib.model";
export type Category = string;
export type VideoIDHash = string; export type SegmentUUID = string & { __segmentUUIDBrand: unknown };
export type IPHash = string; export type VideoID = string & { __videoIDBrand: unknown };
export type Category = string & { __categoryBrand: unknown };
export type VideoIDHash = VideoID & HashedValue;
export type IPAddress = string & { __ipAddressBrand: unknown };
export type HashedIP = IPAddress & HashedValue;
export interface Segment { export interface Segment {
category: Category; category: Category;
@@ -45,6 +49,6 @@ export interface VideoData {
} }
export interface SegmentCache { export interface SegmentCache {
shadowHiddenSegmentIPs: Record<VideoID, {hashedIP: IPHash}[]>, shadowHiddenSegmentIPs: SBRecord<VideoID, {hashedIP: HashedIP}[]>,
userHashedIP?: IPHash userHashedIP?: HashedIP
} }

View File

@@ -1,19 +1,21 @@
import {config} from '../config'; import {config} from '../config';
import {Request} from 'express'; import {Request} from 'express';
import { IPAddress } from '../types/segments.model';
export function getIP(req: Request): string { export function getIP(req: Request): IPAddress {
if (config.behindProxy === true || config.behindProxy === "true") { if (config.behindProxy === true || config.behindProxy === "true") {
config.behindProxy = "X-Forwarded-For"; config.behindProxy = "X-Forwarded-For";
} }
switch (config.behindProxy as string) { switch (config.behindProxy as string) {
case "X-Forwarded-For": case "X-Forwarded-For":
return req.headers['x-forwarded-for'] as string; return req.headers['x-forwarded-for'] as IPAddress;
case "Cloudflare": case "Cloudflare":
return req.headers['cf-connecting-ip'] as string; return req.headers['cf-connecting-ip'] as IPAddress;
case "X-Real-IP": case "X-Real-IP":
return req.headers['x-real-ip'] as string; return req.headers['x-real-ip'] as IPAddress;
default: default:
return req.connection.remoteAddress; return req.connection.remoteAddress as IPAddress;
} }
}
}