Support copying teams into one's builder
Some checks are pending
Node.js CI / build (22.x) (push) Waiting to run

This commit is contained in:
Mia 2025-07-23 22:47:35 -05:00
parent ddcacf0f94
commit bc0886b12f
3 changed files with 69 additions and 32 deletions

View File

@ -112,7 +112,7 @@ export function checkSuspectVerified(
userData.coil = coilNum;
break;
case 'elo': case 'gxe':
if (suspect[k] && rating[k] >= suspect[k]) {
if (suspect[k] && rating[k] >= suspect[k]!) {
reqsMet++;
}
userData[k] = rating[k];
@ -139,6 +139,14 @@ export function checkSuspectVerified(
return false;
}
function exportTeam(team: string) {
if (!Config.pspath) return team;
const { Teams } = require(Config.pspath);
const teamData = Teams.unpack(team);
if (!teamData) return team;
return Teams.export(teamData);
}
export const actions: { [k: string]: QueryHandler } = {
async register(params) {
this.verifyCrossDomainRequest();
@ -849,7 +857,7 @@ export const actions: { [k: string]: QueryHandler } = {
return { loggedIn: this.user.id, teams };
},
async getteam(params) {
let { teamid, password, full } = params;
let { teamid, password, full, raw } = params;
teamid = toID(teamid);
password = toID(password);
if (!teamid) {
@ -873,6 +881,7 @@ export const actions: { [k: string]: QueryHandler } = {
await tables.teams.query()`UPDATE teams SET views = views + 1 WHERE teamid = ${teamid}`;
data.views += 1;
}
if (raw) data.team = exportTeam(data.team) || data.team;
return data;
} catch (e) {
Server.crashlog(e, 'a teams database request', params);
@ -900,13 +909,11 @@ export const actions: { [k: string]: QueryHandler } = {
if (![1, 0].includes(priv)) {
throw new ActionError(`Invalid privacy setting: ${params.private || "none"}`);
}
if (priv !== team.private) {
if (team.private === 1) {
edit.password = null;
edit.private = 0;
if (Boolean(priv) !== !!team.private) {
if (priv === 1) {
edit.private = Replays.generatePassword(20);
} else {
edit.password = Replays.generatePassword(20);
edit.private = 1;
edit.private = null;
}
}
}
@ -923,6 +930,35 @@ export const actions: { [k: string]: QueryHandler } = {
}
return { success: true, team: await tables.teams.get(team.teamid) };
},
async copyteam(params) {
let { teamid, password } = params;
if (!this.user.loggedIn) {
throw new ActionError("Must be logged in to copy teams.");
}
teamid = toID(teamid);
password = toID(password);
if (!teamid) {
throw new ActionError("Invalid team ID");
}
const data = await tables.teams.selectOne(
SQL`team, private, ownerid, format, title`
)`WHERE teamid = ${teamid}`;
const owns = data?.ownerid === this.user.id;
if (!data || (owns ? false : (data.private && (password !== toID(data.private))))) {
throw new ActionError("Access denied");
}
const newPw = Replays.generatePassword(20);
const result = await tables.teams.query()`INSERT INTO teams (${{
team: data.team,
private: newPw,
format: data.format,
title: `Copy of '${data.title}' by ${data.ownerid}`,
views: 0,
ownerid: this.user.id,
date: new Date().toISOString(),
}}) RETURNING *;`;
return { teamid: `${result[0].teamid}-${newPw}` };
},
async searchteams(params) {
let count = Number(params.count) || 20;
if (!this.user.loggedIn || this.user.id === 'guest') {

View File

@ -8,6 +8,7 @@
import { toID, time } from './utils';
import { replayPlayers, replays } from './tables';
import { SQL } from './database';
import * as crypto from 'crypto';
// must be a type and not an interface to qualify as an SQLRow
export type ReplayRow = {
@ -132,7 +133,7 @@ export const Replays = new class {
generatePassword(length = 31) {
let password = '';
for (let i = 0; i < length; i++) {
password += this.passwordCharacters[Math.floor(Math.random() * this.passwordCharacters.length)];
password += this.passwordCharacters[crypto.randomInt(0, this.passwordCharacters.length - 1)];
}
return password;
@ -157,32 +158,32 @@ export const Replays = new class {
if (args.usernames.length > 1) {
const userid2 = toID(args.usernames[1]);
if (format) {
return replays.query()`SELECT
p1.uploadtime AS uploadtime, p1.id AS id, p1.format AS format, p1.players AS players,
p1.rating AS rating, p1.password AS password, p1.private AS private
FROM replayplayers p1 INNER JOIN replayplayers p2 ON p2.id = p1.id
return replays.query()`SELECT
p1.uploadtime AS uploadtime, p1.id AS id, p1.format AS format, p1.players AS players,
p1.rating AS rating, p1.password AS password, p1.private AS private
FROM replayplayers p1 INNER JOIN replayplayers p2 ON p2.id = p1.id
WHERE p1.playerid = ${userid} AND p1.formatid = ${format} AND p1.private = ${isPrivate}
AND p2.playerid = ${userid2}
AND p2.playerid = ${userid2}
${before} ${order} ${paginate};`.then(this.toReplays);
} else {
return replays.query()`SELECT
p1.uploadtime AS uploadtime, p1.id AS id, p1.format AS format, p1.players AS players,
p1.rating AS rating, p1.password AS password, p1.private AS private
FROM replayplayers p1 INNER JOIN replayplayers p2 ON p2.id = p1.id
return replays.query()`SELECT
p1.uploadtime AS uploadtime, p1.id AS id, p1.format AS format, p1.players AS players,
p1.rating AS rating, p1.password AS password, p1.private AS private
FROM replayplayers p1 INNER JOIN replayplayers p2 ON p2.id = p1.id
WHERE p1.playerid = ${userid} AND p1.private = ${isPrivate}
AND p2.playerid = ${userid2}
AND p2.playerid = ${userid2}
${before} ${order} ${paginate};`.then(this.toReplays);
}
} else {
if (format) {
return replays.query()`SELECT
uploadtime, id, format, players, rating, private, password FROM replayplayers
WHERE playerid = ${userid} AND formatid = ${format} AND "private" = ${isPrivate}
return replays.query()`SELECT
uploadtime, id, format, players, rating, private, password FROM replayplayers
WHERE playerid = ${userid} AND formatid = ${format} AND "private" = ${isPrivate}
${before} ${order} ${paginate};`.then(this.toReplays);
} else {
return replays.query()`SELECT
uploadtime, id, format, players, rating, private, password FROM replayplayers
WHERE playerid = ${userid} AND private = ${isPrivate}
return replays.query()`SELECT
uploadtime, id, format, players, rating, private, password FROM replayplayers
WHERE playerid = ${userid} AND private = ${isPrivate}
${before} ${order} ${paginate};`.then(this.toReplays);
}
}
@ -191,13 +192,13 @@ export const Replays = new class {
if (!format) return this.recent(args);
if (args.byRating) {
return replays.query()`SELECT uploadtime, id, format, players, rating, private, password
FROM replays
return replays.query()`SELECT uploadtime, id, format, players, rating, private, password
FROM replays
WHERE private = ${isPrivate} AND formatid = ${format} ${before} ORDER BY rating DESC ${paginate}`
.then(this.toReplays);
} else {
return replays.query()`SELECT uploadtime, id, format, players, rating, private, password
FROM replays
return replays.query()`SELECT uploadtime, id, format, players, rating, private, password
FROM replays
WHERE private = ${isPrivate} AND formatid = ${format} ${before} ORDER BY uploadtime DESC ${paginate}`
.then(this.toReplays);
}
@ -215,8 +216,8 @@ export const Replays = new class {
const secondPattern = patterns.length >= 2 ? SQL`AND log LIKE ${patterns[1]} ` : SQL``;
const HOUR = 60 * 60;
return replays.query()`SELECT
uploadtime, id, format, players, rating FROM replays
return replays.query()`SELECT
uploadtime, id, format, players, rating FROM replays
WHERE private = 0 AND uploadtime > ${time() - HOUR} AND log LIKE ${patterns[0]} ${secondPattern}
ORDER BY uploadtime DESC LIMIT 50;`.then(this.toReplays);
}

View File

@ -141,7 +141,7 @@ export const teams = pgdb.getTable<{
team: string,
format: string,
title: string,
private: number,
private: string,
views: number,
}>('teams', 'teamid');