mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-07-27 13:58:59 -05:00
Member join number widget (#2939)
This commit is contained in:
parent
fab0f7840c
commit
ff27d6739b
|
|
@ -1050,6 +1050,7 @@ export interface User {
|
|||
preferences: JSONColumnTypeNullable<UserPreferences>;
|
||||
/** User creation date. Can be null because we did not always save this. */
|
||||
createdAt: number | null;
|
||||
joinOrder: number | null;
|
||||
/** Last message used when creating a tournament sub post */
|
||||
lastSubMessage: string | null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import { sortBadgesByFavorites } from "./core/badge-sorting.server";
|
|||
import { findWidgetById } from "./core/widgets/portfolio";
|
||||
import { WIDGET_LOADERS } from "./core/widgets/portfolio-loaders.server";
|
||||
import type { LoadedWidget } from "./core/widgets/types";
|
||||
import { SPL2_JOIN_ORDER_CUTOFF } from "./user-page-constants";
|
||||
|
||||
export const identifierToUserIdQuery = (identifier: string) =>
|
||||
db
|
||||
|
|
@ -926,6 +927,21 @@ export async function patronSinceByUserId(userId: number) {
|
|||
)?.patronSince;
|
||||
}
|
||||
|
||||
export async function joinOrderByUserId(userId: number) {
|
||||
const row = await db
|
||||
.selectFrom("User")
|
||||
.select("User.joinOrder")
|
||||
.where("id", "=", userId)
|
||||
.executeTakeFirst();
|
||||
|
||||
if (!row?.joinOrder) return null;
|
||||
|
||||
return {
|
||||
joinOrder: row.joinOrder,
|
||||
isSpl2: row.joinOrder <= SPL2_JOIN_ORDER_CUTOFF,
|
||||
};
|
||||
}
|
||||
|
||||
export async function commissionsByUserId(userId: number) {
|
||||
return await db
|
||||
.selectFrom("User")
|
||||
|
|
@ -958,7 +974,19 @@ export function upsert(
|
|||
) {
|
||||
return db
|
||||
.insertInto("User")
|
||||
.values({ ...args, createdAt: databaseTimestampNow() })
|
||||
.values((eb) => ({
|
||||
...args,
|
||||
createdAt: databaseTimestampNow(),
|
||||
joinOrder: eb
|
||||
.selectFrom("User")
|
||||
.select(
|
||||
eb(
|
||||
eb.fn.coalesce(eb.fn.max("joinOrder"), eb.val(0)),
|
||||
"+",
|
||||
eb.val(1),
|
||||
).as("nextJoinOrder"),
|
||||
),
|
||||
}))
|
||||
.onConflict((oc) => {
|
||||
return oc.column("discordId").doUpdateSet({
|
||||
...R.omit(args, ["discordId"]),
|
||||
|
|
|
|||
|
|
@ -62,6 +62,49 @@ describe("UserRepository", () => {
|
|||
expect(updatedUser?.createdAt).toEqual(createdAt);
|
||||
});
|
||||
|
||||
test("new user gets joinOrder of 1", async () => {
|
||||
const { id } = await UserRepository.upsert({
|
||||
discordId: "1",
|
||||
discordName: "TestUser",
|
||||
discordAvatar: null,
|
||||
});
|
||||
|
||||
const result = await UserRepository.joinOrderByUserId(id);
|
||||
|
||||
expect(result?.joinOrder).toBe(1);
|
||||
});
|
||||
|
||||
test("joinOrder increments for each new user and does not change on update", async () => {
|
||||
const { id: firstId } = await UserRepository.upsert({
|
||||
discordId: "1",
|
||||
discordName: "FirstUser",
|
||||
discordAvatar: null,
|
||||
});
|
||||
|
||||
const { id: secondId } = await UserRepository.upsert({
|
||||
discordId: "2",
|
||||
discordName: "SecondUser",
|
||||
discordAvatar: null,
|
||||
});
|
||||
|
||||
expect((await UserRepository.joinOrderByUserId(firstId))?.joinOrder).toBe(
|
||||
1,
|
||||
);
|
||||
expect((await UserRepository.joinOrderByUserId(secondId))?.joinOrder).toBe(
|
||||
2,
|
||||
);
|
||||
|
||||
await UserRepository.upsert({
|
||||
discordId: "1",
|
||||
discordName: "UpdatedFirstUser",
|
||||
discordAvatar: null,
|
||||
});
|
||||
|
||||
expect((await UserRepository.joinOrderByUserId(firstId))?.joinOrder).toBe(
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
describe("userRoles", () => {
|
||||
test("returns empty array for basic user", async () => {
|
||||
await UserRepository.upsert({
|
||||
|
|
|
|||
|
|
@ -174,6 +174,14 @@ export function Widget({
|
|||
})}
|
||||
/>
|
||||
);
|
||||
case "join-date":
|
||||
if (!widget.data) return null;
|
||||
return (
|
||||
<BigValue
|
||||
value={`#${widget.data.joinOrder}`}
|
||||
footer={widget.data.isSpl2 ? "SPL2" : undefined}
|
||||
/>
|
||||
);
|
||||
case "timezone":
|
||||
return <TimezoneWidget timezone={widget.data.timezone} />;
|
||||
case "favorite-stage":
|
||||
|
|
|
|||
|
|
@ -191,6 +191,9 @@ export const WIDGET_LOADERS = {
|
|||
"patron-since": async (userId: number) => {
|
||||
return UserRepository.patronSinceByUserId(userId);
|
||||
},
|
||||
"join-date": async (userId: number) => {
|
||||
return UserRepository.joinOrderByUserId(userId);
|
||||
},
|
||||
videos: async (userId: number) => {
|
||||
return VodRepository.findByUserId(userId, 3);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export const ALL_WIDGETS = {
|
|||
}),
|
||||
defineWidget({ id: "organizations", slot: "side" }),
|
||||
defineWidget({ id: "patron-since", slot: "side" }),
|
||||
defineWidget({ id: "join-date", slot: "side" }),
|
||||
defineWidget({
|
||||
id: "timezone",
|
||||
slot: "side",
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ export const USER = {
|
|||
GAME_BADGES_SMALL_MAX: 4,
|
||||
};
|
||||
|
||||
export const SPL2_JOIN_ORDER_CUTOFF = 13_589;
|
||||
|
||||
export const IN_GAME_NAME_REGEXP = /^.{1,10}#[0-9a-z]{4,5}$/u;
|
||||
|
||||
export const MATCHES_PER_SEASONS_PAGE = 8;
|
||||
|
|
|
|||
BIN
db-test.sqlite3
BIN
db-test.sqlite3
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "Tournament Results",
|
||||
"widget.placement-results": "Tournament Placements",
|
||||
"widget.patron-since": "Supporter Since",
|
||||
"widget.join-date": "Member #",
|
||||
"widget.timezone": "User's Current Time",
|
||||
"widget.favorite-stage": "Favorite Stage",
|
||||
"widget.videos": "Videos",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "Display your (highlighted) tournament results",
|
||||
"widgets.description.placement-results": "Show your tournament placements by 1st, 2nd, and 3rd place",
|
||||
"widgets.description.patron-since": "Show the date when you became a supporter",
|
||||
"widgets.description.join-date": "Display your sendou.ink member number",
|
||||
"widgets.description.timezone": "Display your current local time",
|
||||
"widgets.description.favorite-stage": "Show your favorite stage",
|
||||
"widgets.description.videos": "Display your 3 most recent videos",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "Resultados de torneos",
|
||||
"widget.placement-results": "Clasificaciones en torneos",
|
||||
"widget.patron-since": "Mecenas desde",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "Hora actual del usuario",
|
||||
"widget.favorite-stage": "Escenario favorito",
|
||||
"widget.videos": "Vídeos",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "Muestra tus resultados de torneos (resaltados)",
|
||||
"widgets.description.placement-results": "Muestra tus clasificaciones en torneos por 1º, 2º y 3º lugar",
|
||||
"widgets.description.patron-since": "Muestra la fecha desde la que eres mecenas",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "Muestra tu hora local actual",
|
||||
"widgets.description.favorite-stage": "Muestra tu escenario favorito",
|
||||
"widgets.description.videos": "Muestra tus 3 vídeos más recientes",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"widget.highlighted-results": "",
|
||||
"widget.placement-results": "",
|
||||
"widget.patron-since": "",
|
||||
"widget.join-date": "",
|
||||
"widget.timezone": "",
|
||||
"widget.favorite-stage": "",
|
||||
"widget.videos": "",
|
||||
|
|
@ -85,6 +86,7 @@
|
|||
"widgets.description.highlighted-results": "",
|
||||
"widgets.description.placement-results": "",
|
||||
"widgets.description.patron-since": "",
|
||||
"widgets.description.join-date": "",
|
||||
"widgets.description.timezone": "",
|
||||
"widgets.description.favorite-stage": "",
|
||||
"widgets.description.videos": "",
|
||||
|
|
|
|||
34
migrations/130-join-order.js
Normal file
34
migrations/130-join-order.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export function up(db) {
|
||||
const dir = dirname(fileURLToPath(import.meta.url));
|
||||
const jsonPath = resolve(dir, "../scripts/join-order.json");
|
||||
const mapping = JSON.parse(readFileSync(jsonPath, "utf-8"));
|
||||
|
||||
db.transaction(() => {
|
||||
db.prepare(/* sql */ `alter table "User" add "joinOrder" integer`).run();
|
||||
|
||||
const stmt = db.prepare(
|
||||
/* sql */ `update "User" set "joinOrder" = ? where "id" = ?`,
|
||||
);
|
||||
for (const [sqliteId, joinOrder] of Object.entries(mapping)) {
|
||||
stmt.run(joinOrder, Number(sqliteId));
|
||||
}
|
||||
|
||||
const maxJoinOrder = db
|
||||
.prepare(/* sql */ `select max("joinOrder") as m from "User"`)
|
||||
.get().m;
|
||||
|
||||
const unmapped = db
|
||||
.prepare(
|
||||
/* sql */ `select "id" from "User" where "joinOrder" is null order by "id" asc`,
|
||||
)
|
||||
.all();
|
||||
|
||||
for (let i = 0; i < unmapped.length; i++) {
|
||||
stmt.run(maxJoinOrder + 1 + i, unmapped[i].id);
|
||||
}
|
||||
})();
|
||||
}
|
||||
55621
scripts/join-order.json
Normal file
55621
scripts/join-order.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user