import * as queries from "./queries" import * as youtube from "./youtube" import { log, LogLevel } from "./logging" import pg from "pg"; import db from "./db"; export async function getRequests(count: number, offset: number, sort: string) { var query = { text: "SELECT * FROM requests_vw \ JOIN states ON requests_vw.state = states.state WHERE active \ ORDER BY " + sort + " LIMIT $1 OFFSET $2", values: [count,offset] }; return db.query(query) .then((result: pg.QueryResult) => result.rows); }; export async function getRequestsVoted(count: number, offset: number, sort: string, user: number) { var query = { text: "SELECT * FROM get_requests_voted($3) \ JOIN states ON get_requests_voted.state = states.state WHERE active \ ORDER BY " + sort + " LIMIT $1 OFFSET $2", values: [count,offset,user] }; return db.query(query) .then((result: pg.QueryResult) => result.rows); }; export async function getRequestsTotal() { return db.query(queries.getRequestsTotal) .then((result: pg.QueryResult) => result.rows[0]["count"]); }; export async function getAllRequests(count: number, offset: number, sort: string) { var query = { text: "SELECT * FROM requests_vw \ ORDER BY " + sort + " LIMIT $1 OFFSET $2", values: [count,offset] }; return db.query(query) .then((result: pg.QueryResult) => result.rows); }; export async function getAllRequestsVoted(count: number, offset: number, sort: string, user: number) { var query = { text: "SELECT * FROM get_requests_voted($3) \ ORDER BY " + sort + " LIMIT $1 OFFSET $2", values: [count,offset,user] }; return db.query(query) .then((result: pg.QueryResult) => result.rows); }; export async function getAllRequestsTotal() { return db.query(queries.getAllRequestsTotal) .then((result: pg.QueryResult) => result.rows[0]["count"]); }; const validUrlRegexes = [ /^https:\/\/www\.youtube\.com\/watch\?v=[a-zA-Z0-9_-]{11}$/ ]; async function checkRequestExists(url: string) { var query = Object.assign(queries.checkRequestExists, { values: [url] }); var result = await db.query(query); if (result.rowCount > 0) { return result; } else { return false; } } async function retrieveYoutubeMetadata(url: string) { var videoId = url.match(/^https:\/\/www\.youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})$/)![1]; try { var ytResponse = await youtube.apiRequest('/videos', new URLSearchParams({ id: videoId, part: 'snippet' })); if (ytResponse) { var title = ytResponse['items'][0]['snippet']['title']; db.query(Object.assign(queries.updateRequestMetadata, { values: [url,title] })); } } catch(e) { log(LogLevel.ERROR,"Failed to fetch YouTube metadata"); log(LogLevel.ERROR,"Video ID: " + videoId); log(LogLevel.ERROR,"Error info:"); log(LogLevel.ERROR,e); } } export async function addRequest(url: string, requester: string): Promise<[number,string]> { // Check that URL is of an accepted format var validUrl = false; for (var regex of validUrlRegexes) { if (regex.test(url)) { validUrl = true; break; } } if (!validUrl) return [400, "Invalid song URL."]; // Check whether the URL has already been requested var existsResult = await checkRequestExists(url) if (existsResult) { console.log(existsResult); return [200,`Song already requested by ${existsResult.rows[0].requester}. State: ${existsResult.rows[0].state}`] } // Check whether the user has hit their rate limit var rateLimitQuery = Object.assign(queries.getRateLimitStatus, { values: [requester] }); var rateLimitResult = (await db.query(rateLimitQuery)).rows[0]; if (rateLimitResult.status) { return [429,`You have reached your maximum of ${rateLimitResult.max} requests per day. Please try again later.\n Tip: Removing one of your requests from the past 24 hours by retracting your vote will allow you to replace it with another.`]; } // Add the request var query = Object.assign(queries.addRequest, { values: [url,requester] }); return db.query(query) .then(async () => { if (url.includes('youtube')){ retrieveYoutubeMetadata(url); } return [201,"Song request added."] as [number,string]; }) }; export async function updateRequestMetadata(url: string): Promise <[number,string]> { if (!checkRequestExists(url)) { return [400,"Request does not exist."]; } if (url.includes('youtube')){ retrieveYoutubeMetadata(url); } return [200,"Metadata update requested."]; } export async function updateRequestState(url: string, state: string): Promise<[number,string]> { var query = Object.assign(queries.checkValidState, { values: [state] }); var result = await db.query(query); if (result.rowCount == 0) { return [400,"Invalid state"] } if (!checkRequestExists(url)) { return [400,"Request does not exist."]; } var query = Object.assign(queries.updateRequestState, { values: [url,state] }); return db.query(query) .then(() => [200,"Song request state updated."] as [number,string]); }; export async function updateRequestScoreModifier(url: string, scoreDiff: number): Promise<[number,string]> { var query = Object.assign(queries.updateRequestScoreModifier, { values: [url,scoreDiff] }); return db.query(query) .then(() => [200,"Song request score updated."] as [number,string]); }; export async function deleteRequest(url: string): Promise<[number,string]> { var query = Object.assign(queries.deleteRequest, { values: [url] }); return db.query(query) .then(() => [200,"Song request deleted."] as [number,string]); }; export async function addVote(url: string, user: string): Promise<[number,string]> { var query = Object.assign(queries.checkVoteExists, { values: [url,user] }); var result = await db.query(query); if (result.rowCount > 0) { return [200,`Song already voted on`] } return db.query("CALL add_vote($1,$2)",[url,user]) .then(() => [201,"Successfully cast vote."] as [number,string]); }; export async function deleteVote(url: string, user: string): Promise<[number,string]> { return db.query("CALL delete_vote($1,$2)",[url,user]) .then(() => [201,"Successfully retracted vote."] as [number,string]); };