refactor: replace all explicit any types with proper TypeScript types
Backend resolvers: typed enrichArticle/enrichVideo/enrichModel with DB and $inferSelect types, SQL<unknown>[] for conditions arrays, proper enum casts for status/role fields, $inferInsert for .set() updates, typed raw SQL result rows in gamification, ReplyLike interface for ctx.reply in auth. Frontend: typed catch blocks with Error/interface casts, isActiveLink param, adminGetUser response, tags filter callback. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { builder } from "../builder";
|
||||
import { ArticleType, ArticleListType, AdminArticleListType } from "../types/index";
|
||||
import { articles, users } from "../../db/schema/index";
|
||||
import { eq, and, lte, desc, asc, ilike, or, count, arrayContains } from "drizzle-orm";
|
||||
import { eq, and, lte, desc, asc, ilike, or, count, arrayContains, type SQL } from "drizzle-orm";
|
||||
import { requireAdmin } from "../../lib/acl";
|
||||
import type { DB } from "../../db/connection";
|
||||
|
||||
async function enrichArticle(db: any, article: any) {
|
||||
async function enrichArticle(db: DB, article: typeof articles.$inferSelect) {
|
||||
let author = null;
|
||||
if (article.author) {
|
||||
const authorUser = await db
|
||||
@@ -39,7 +40,7 @@ builder.queryField("articles", (t) =>
|
||||
const pageSize = args.limit ?? 24;
|
||||
const offset = args.offset ?? 0;
|
||||
|
||||
const conditions: any[] = [lte(articles.publish_date, new Date())];
|
||||
const conditions: SQL<unknown>[] = [lte(articles.publish_date, new Date())];
|
||||
if (args.featured !== null && args.featured !== undefined) {
|
||||
conditions.push(eq(articles.featured, args.featured));
|
||||
}
|
||||
@@ -50,28 +51,24 @@ builder.queryField("articles", (t) =>
|
||||
or(
|
||||
ilike(articles.title, `%${args.search}%`),
|
||||
ilike(articles.excerpt, `%${args.search}%`),
|
||||
),
|
||||
) as SQL<unknown>,
|
||||
);
|
||||
}
|
||||
|
||||
const orderArgs =
|
||||
args.sortBy === "name"
|
||||
? [asc(articles.title)]
|
||||
: args.sortBy === "featured"
|
||||
? [desc(articles.featured), desc(articles.publish_date)]
|
||||
: [desc(articles.publish_date)];
|
||||
|
||||
const where = and(...conditions);
|
||||
const baseQuery = ctx.db.select().from(articles).where(where);
|
||||
const ordered =
|
||||
args.sortBy === "name"
|
||||
? baseQuery.orderBy(asc(articles.title))
|
||||
: args.sortBy === "featured"
|
||||
? baseQuery.orderBy(desc(articles.featured), desc(articles.publish_date))
|
||||
: baseQuery.orderBy(desc(articles.publish_date));
|
||||
|
||||
const [articleList, totalRows] = await Promise.all([
|
||||
(ctx.db.select().from(articles).where(where) as any)
|
||||
.orderBy(...orderArgs)
|
||||
.limit(pageSize)
|
||||
.offset(offset),
|
||||
ordered.limit(pageSize).offset(offset),
|
||||
ctx.db.select({ total: count() }).from(articles).where(where),
|
||||
]);
|
||||
const items = await Promise.all(
|
||||
articleList.map((article: any) => enrichArticle(ctx.db, article)),
|
||||
);
|
||||
const items = await Promise.all(articleList.map((article) => enrichArticle(ctx.db, article)));
|
||||
return { items, total: totalRows[0]?.total ?? 0 };
|
||||
},
|
||||
}),
|
||||
@@ -130,13 +127,13 @@ builder.queryField("adminListArticles", (t) =>
|
||||
const limit = args.limit ?? 50;
|
||||
const offset = args.offset ?? 0;
|
||||
|
||||
const conditions: any[] = [];
|
||||
const conditions: SQL<unknown>[] = [];
|
||||
if (args.search) {
|
||||
conditions.push(
|
||||
or(
|
||||
ilike(articles.title, `%${args.search}%`),
|
||||
ilike(articles.excerpt, `%${args.search}%`),
|
||||
),
|
||||
) as SQL<unknown>,
|
||||
);
|
||||
}
|
||||
if (args.category) conditions.push(eq(articles.category, args.category));
|
||||
@@ -154,9 +151,7 @@ builder.queryField("adminListArticles", (t) =>
|
||||
.offset(offset),
|
||||
ctx.db.select({ total: count() }).from(articles).where(where),
|
||||
]);
|
||||
const items = await Promise.all(
|
||||
articleList.map((article: any) => enrichArticle(ctx.db, article)),
|
||||
);
|
||||
const items = await Promise.all(articleList.map((article) => enrichArticle(ctx.db, article)));
|
||||
return { items, total: totalRows[0]?.total ?? 0 };
|
||||
},
|
||||
}),
|
||||
@@ -232,7 +227,7 @@ builder.mutationField("updateArticle", (t) =>
|
||||
|
||||
const updated = await ctx.db
|
||||
.update(articles)
|
||||
.set(updates as any)
|
||||
.set(updates as Partial<typeof articles.$inferInsert>)
|
||||
.where(eq(articles.id, args.id))
|
||||
.returning();
|
||||
if (!updated[0]) return null;
|
||||
|
||||
@@ -3,6 +3,10 @@ import { builder } from "../builder";
|
||||
import { CurrentUserType } from "../types/index";
|
||||
import { users } from "../../db/schema/index";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
interface ReplyLike {
|
||||
header?: (name: string, value: string) => void;
|
||||
}
|
||||
import { hash, verify as verifyArgon } from "../../lib/argon";
|
||||
import { setSession, deleteSession } from "../../lib/auth";
|
||||
import { sendVerification, sendPasswordReset } from "../../lib/email";
|
||||
@@ -46,12 +50,7 @@ builder.mutationField("login", (t) =>
|
||||
// Set session cookie
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
const cookieValue = `session_token=${token}; HttpOnly; Path=/; SameSite=Strict; Max-Age=86400${isProduction ? "; Secure" : ""}`;
|
||||
(ctx.reply as any).header?.("Set-Cookie", cookieValue);
|
||||
|
||||
// For graphql-yoga response
|
||||
if ((ctx as any).serverResponse) {
|
||||
(ctx as any).serverResponse.setHeader("Set-Cookie", cookieValue);
|
||||
}
|
||||
(ctx.reply as ReplyLike).header?.("Set-Cookie", cookieValue);
|
||||
|
||||
return user[0];
|
||||
},
|
||||
@@ -76,7 +75,7 @@ builder.mutationField("logout", (t) =>
|
||||
// Clear cookie
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
const cookieValue = `session_token=; HttpOnly; Path=/; SameSite=Strict; Max-Age=0${isProduction ? "; Secure" : ""}`;
|
||||
(ctx.reply as any).header?.("Set-Cookie", cookieValue);
|
||||
(ctx.reply as ReplyLike).header?.("Set-Cookie", cookieValue);
|
||||
return true;
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -20,7 +20,7 @@ builder.queryField("commentsForVideo", (t) =>
|
||||
.orderBy(desc(comments.date_created));
|
||||
|
||||
return Promise.all(
|
||||
commentList.map(async (c: any) => {
|
||||
commentList.map(async (c) => {
|
||||
const user = await ctx.db
|
||||
.select({
|
||||
id: users.id,
|
||||
@@ -125,7 +125,7 @@ builder.queryField("adminListComments", (t) =>
|
||||
]);
|
||||
|
||||
const items = await Promise.all(
|
||||
commentList.map(async (c: any) => {
|
||||
commentList.map(async (c) => {
|
||||
const user = await ctx.db
|
||||
.select({
|
||||
id: users.id,
|
||||
|
||||
@@ -37,7 +37,7 @@ builder.queryField("leaderboard", (t) =>
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
|
||||
return entries.map((e: any, i: number) => ({ ...e, rank: offset + i + 1 }));
|
||||
return entries.map((e, i) => ({ ...e, rank: offset + i + 1 }));
|
||||
},
|
||||
}),
|
||||
);
|
||||
@@ -101,7 +101,7 @@ builder.queryField("userGamification", (t) =>
|
||||
|
||||
return {
|
||||
stats: stats[0] ? { ...stats[0], rank } : null,
|
||||
achievements: userAchievements.map((a: any) => ({
|
||||
achievements: userAchievements.map((a) => ({
|
||||
...a,
|
||||
date_unlocked: a.date_unlocked!,
|
||||
})),
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { builder } from "../builder";
|
||||
import { ModelType, ModelListType } from "../types/index";
|
||||
import { users, user_photos, files } from "../../db/schema/index";
|
||||
import { eq, and, desc, asc, ilike, count, arrayContains } from "drizzle-orm";
|
||||
import { eq, and, desc, asc, ilike, count, arrayContains, type SQL } from "drizzle-orm";
|
||||
import type { DB } from "../../db/connection";
|
||||
|
||||
async function enrichModel(db: any, user: any) {
|
||||
async function enrichModel(db: DB, user: typeof users.$inferSelect) {
|
||||
// Fetch photos
|
||||
const photoRows = await db
|
||||
.select({ id: files.id, filename: files.filename })
|
||||
@@ -14,8 +15,8 @@ async function enrichModel(db: any, user: any) {
|
||||
|
||||
const seen = new Set<string>();
|
||||
const photos = photoRows
|
||||
.filter((p: any) => p.id && !seen.has(p.id) && seen.add(p.id))
|
||||
.map((p: any) => ({ id: p.id, filename: p.filename }));
|
||||
.filter((p) => p.id && !seen.has(p.id) && seen.add(p.id))
|
||||
.map((p) => ({ id: p.id, filename: p.filename }));
|
||||
|
||||
return { ...user, photos };
|
||||
}
|
||||
@@ -35,7 +36,7 @@ builder.queryField("models", (t) =>
|
||||
const pageSize = args.limit ?? 24;
|
||||
const offset = args.offset ?? 0;
|
||||
|
||||
const conditions: any[] = [eq(users.role, "model")];
|
||||
const conditions: SQL<unknown>[] = [eq(users.role, "model")];
|
||||
if (args.search) conditions.push(ilike(users.artist_name, `%${args.search}%`));
|
||||
if (args.tag) conditions.push(arrayContains(users.tags, [args.tag]));
|
||||
|
||||
@@ -46,7 +47,7 @@ builder.queryField("models", (t) =>
|
||||
ctx.db.select().from(users).where(where).orderBy(order).limit(pageSize).offset(offset),
|
||||
ctx.db.select({ total: count() }).from(users).where(where),
|
||||
]);
|
||||
const items = await Promise.all(modelList.map((m: any) => enrichModel(ctx.db, m)));
|
||||
const items = await Promise.all(modelList.map((m) => enrichModel(ctx.db, m)));
|
||||
return { items, total: totalRows[0]?.total ?? 0 };
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -2,7 +2,7 @@ import { GraphQLError } from "graphql";
|
||||
import { builder } from "../builder";
|
||||
import { RecordingType, AdminRecordingListType } from "../types/index";
|
||||
import { recordings, recording_plays } from "../../db/schema/index";
|
||||
import { eq, and, desc, ilike, count } from "drizzle-orm";
|
||||
import { eq, and, desc, ilike, count, type SQL } from "drizzle-orm";
|
||||
import { slugify } from "../../lib/slugify";
|
||||
import { awardPoints, checkAchievements } from "../../lib/gamification";
|
||||
import { requireAdmin } from "../../lib/acl";
|
||||
@@ -21,7 +21,7 @@ builder.queryField("recordings", (t) =>
|
||||
if (!ctx.currentUser) throw new GraphQLError("Unauthorized");
|
||||
|
||||
const conditions = [eq(recordings.user_id, ctx.currentUser.id)];
|
||||
if (args.status) conditions.push(eq(recordings.status, args.status as any));
|
||||
if (args.status) conditions.push(eq(recordings.status, args.status as "draft" | "published"));
|
||||
if (args.linkedVideoId) conditions.push(eq(recordings.linked_video, args.linkedVideoId));
|
||||
|
||||
const limit = args.limit || 50;
|
||||
@@ -115,7 +115,7 @@ builder.mutationField("createRecording", (t) =>
|
||||
user_id: ctx.currentUser.id,
|
||||
tags: args.tags || [],
|
||||
linked_video: args.linkedVideoId || null,
|
||||
status: (args.status as any) || "draft",
|
||||
status: (args.status as "draft" | "published") || "draft",
|
||||
public: false,
|
||||
})
|
||||
.returning();
|
||||
@@ -174,7 +174,7 @@ builder.mutationField("updateRecording", (t) =>
|
||||
|
||||
const updated = await ctx.db
|
||||
.update(recordings)
|
||||
.set(updates as any)
|
||||
.set(updates as Partial<typeof recordings.$inferInsert>)
|
||||
.where(eq(recordings.id, args.id))
|
||||
.returning();
|
||||
|
||||
@@ -355,9 +355,9 @@ builder.queryField("adminListRecordings", (t) =>
|
||||
const limit = args.limit ?? 50;
|
||||
const offset = args.offset ?? 0;
|
||||
|
||||
const conditions: any[] = [];
|
||||
const conditions: SQL<unknown>[] = [];
|
||||
if (args.search) conditions.push(ilike(recordings.title, `%${args.search}%`));
|
||||
if (args.status) conditions.push(eq(recordings.status, args.status as any));
|
||||
if (args.status) conditions.push(eq(recordings.status, args.status as "draft" | "published"));
|
||||
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
||||
|
||||
const [rows, totalRows] = await Promise.all([
|
||||
|
||||
@@ -2,7 +2,7 @@ import { GraphQLError } from "graphql";
|
||||
import { builder } from "../builder";
|
||||
import { CurrentUserType, UserType, AdminUserListType, AdminUserDetailType } from "../types/index";
|
||||
import { users, user_photos, files } from "../../db/schema/index";
|
||||
import { eq, ilike, or, count, and } from "drizzle-orm";
|
||||
import { eq, ilike, or, count, and, asc, type SQL } from "drizzle-orm";
|
||||
import { requireAdmin } from "../../lib/acl";
|
||||
|
||||
builder.queryField("me", (t) =>
|
||||
@@ -63,7 +63,7 @@ builder.mutationField("updateProfile", (t) =>
|
||||
|
||||
await ctx.db
|
||||
.update(users)
|
||||
.set(updates as any)
|
||||
.set(updates as Partial<typeof users.$inferInsert>)
|
||||
.where(eq(users.id, ctx.currentUser.id));
|
||||
|
||||
const updated = await ctx.db
|
||||
@@ -93,27 +93,27 @@ builder.queryField("adminListUsers", (t) =>
|
||||
const limit = args.limit ?? 50;
|
||||
const offset = args.offset ?? 0;
|
||||
|
||||
let query = ctx.db.select().from(users);
|
||||
let countQuery = ctx.db.select({ total: count() }).from(users);
|
||||
|
||||
const conditions: any[] = [];
|
||||
const conditions: SQL<unknown>[] = [];
|
||||
if (args.role) {
|
||||
conditions.push(eq(users.role, args.role as any));
|
||||
conditions.push(eq(users.role, args.role as "model" | "viewer" | "admin"));
|
||||
}
|
||||
if (args.search) {
|
||||
const pattern = `%${args.search}%`;
|
||||
conditions.push(or(ilike(users.email, pattern), ilike(users.artist_name, pattern)));
|
||||
}
|
||||
|
||||
if (conditions.length > 0) {
|
||||
const where = conditions.length === 1 ? conditions[0] : and(...conditions);
|
||||
query = (query as any).where(where);
|
||||
countQuery = (countQuery as any).where(where);
|
||||
conditions.push(
|
||||
or(ilike(users.email, pattern), ilike(users.artist_name, pattern)) as SQL<unknown>,
|
||||
);
|
||||
}
|
||||
|
||||
const where = conditions.length > 0 ? and(...conditions) : undefined;
|
||||
const [items, totalRows] = await Promise.all([
|
||||
(query as any).limit(limit).offset(offset),
|
||||
countQuery,
|
||||
ctx.db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(where)
|
||||
.orderBy(asc(users.artist_name))
|
||||
.limit(limit)
|
||||
.offset(offset),
|
||||
ctx.db.select({ total: count() }).from(users).where(where),
|
||||
]);
|
||||
|
||||
return { items, total: totalRows[0]?.total ?? 0 };
|
||||
@@ -139,7 +139,8 @@ builder.mutationField("adminUpdateUser", (t) =>
|
||||
requireAdmin(ctx);
|
||||
|
||||
const updates: Record<string, unknown> = { date_updated: new Date() };
|
||||
if (args.role !== undefined && args.role !== null) updates.role = args.role as any;
|
||||
if (args.role !== undefined && args.role !== null)
|
||||
updates.role = args.role as "model" | "viewer" | "admin";
|
||||
if (args.isAdmin !== undefined && args.isAdmin !== null) updates.is_admin = args.isAdmin;
|
||||
if (args.firstName !== undefined && args.firstName !== null)
|
||||
updates.first_name = args.firstName;
|
||||
@@ -151,7 +152,7 @@ builder.mutationField("adminUpdateUser", (t) =>
|
||||
|
||||
const updated = await ctx.db
|
||||
.update(users)
|
||||
.set(updates as any)
|
||||
.set(updates as Partial<typeof users.$inferInsert>)
|
||||
.where(eq(users.id, args.userId))
|
||||
.returning();
|
||||
|
||||
@@ -194,8 +195,8 @@ builder.queryField("adminGetUser", (t) =>
|
||||
.orderBy(user_photos.sort);
|
||||
const seen = new Set<string>();
|
||||
const photos = photoRows
|
||||
.filter((p: any) => p.id && !seen.has(p.id) && seen.add(p.id))
|
||||
.map((p: any) => ({ id: p.id, filename: p.filename }));
|
||||
.filter((p) => p.id && !seen.has(p.id) && seen.add(p.id))
|
||||
.map((p) => ({ id: p.id, filename: p.filename }));
|
||||
return { ...user[0], photos };
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -28,10 +28,12 @@ import {
|
||||
lt,
|
||||
gte,
|
||||
arrayContains,
|
||||
type SQL,
|
||||
} from "drizzle-orm";
|
||||
import { requireAdmin } from "../../lib/acl";
|
||||
import type { DB } from "../../db/connection";
|
||||
|
||||
async function enrichVideo(db: any, video: any) {
|
||||
async function enrichVideo(db: DB, video: typeof videos.$inferSelect) {
|
||||
// Fetch models
|
||||
const modelRows = await db
|
||||
.select({
|
||||
@@ -87,7 +89,7 @@ builder.queryField("videos", (t) =>
|
||||
const pageSize = args.limit ?? 24;
|
||||
const offset = args.offset ?? 0;
|
||||
|
||||
const conditions: any[] = [lte(videos.upload_date, new Date())];
|
||||
const conditions: SQL<unknown>[] = [lte(videos.upload_date, new Date())];
|
||||
if (!ctx.currentUser) conditions.push(eq(videos.premium, false));
|
||||
if (args.featured !== null && args.featured !== undefined) {
|
||||
conditions.push(eq(videos.featured, args.featured));
|
||||
@@ -107,7 +109,7 @@ builder.queryField("videos", (t) =>
|
||||
conditions.push(
|
||||
inArray(
|
||||
videos.id,
|
||||
videoIds.map((v: any) => v.video_id),
|
||||
videoIds.map((v) => v.video_id),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -148,8 +150,8 @@ builder.queryField("videos", (t) =>
|
||||
.leftJoin(files, eq(videos.movie, files.id))
|
||||
.where(fullWhere),
|
||||
]);
|
||||
const videoList = rows.map((r: any) => r.v || r);
|
||||
const items = await Promise.all(videoList.map((v: any) => enrichVideo(ctx.db, v)));
|
||||
const videoList = rows.map((r) => r.v);
|
||||
const items = await Promise.all(videoList.map((v) => enrichVideo(ctx.db, v)));
|
||||
return { items, total: totalRows[0]?.total ?? 0 };
|
||||
}
|
||||
|
||||
@@ -157,7 +159,7 @@ builder.queryField("videos", (t) =>
|
||||
ctx.db.select().from(videos).where(where).orderBy(order).limit(pageSize).offset(offset),
|
||||
ctx.db.select({ total: count() }).from(videos).where(where),
|
||||
]);
|
||||
const items = await Promise.all(rows.map((v: any) => enrichVideo(ctx.db, v)));
|
||||
const items = await Promise.all(rows.map((v) => enrichVideo(ctx.db, v)));
|
||||
return { items, total: totalRows[0]?.total ?? 0 };
|
||||
},
|
||||
}),
|
||||
@@ -421,7 +423,7 @@ builder.queryField("analytics", (t) =>
|
||||
};
|
||||
}
|
||||
|
||||
const videoIds = modelVideoIds.map((v: any) => v.video_id);
|
||||
const videoIds = modelVideoIds.map((v) => v.video_id);
|
||||
const videoList = await ctx.db.select().from(videos).where(inArray(videos.id, videoIds));
|
||||
const plays = await ctx.db
|
||||
.select()
|
||||
@@ -435,14 +437,14 @@ builder.queryField("analytics", (t) =>
|
||||
const totalLikes = videoList.reduce((sum, v) => sum + (v.likes_count || 0), 0);
|
||||
const totalPlays = videoList.reduce((sum, v) => sum + (v.plays_count || 0), 0);
|
||||
|
||||
const playsByDate = plays.reduce((acc: any, play) => {
|
||||
const playsByDate = plays.reduce((acc: Record<string, number>, play) => {
|
||||
const date = new Date(play.date_created).toISOString().split("T")[0];
|
||||
if (!acc[date]) acc[date] = 0;
|
||||
acc[date]++;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const likesByDate = likes.reduce((acc: any, like) => {
|
||||
const likesByDate = likes.reduce((acc: Record<string, number>, like) => {
|
||||
const date = new Date(like.date_created).toISOString().split("T")[0];
|
||||
if (!acc[date]) acc[date] = 0;
|
||||
acc[date]++;
|
||||
@@ -499,7 +501,7 @@ builder.queryField("adminListVideos", (t) =>
|
||||
const limit = args.limit ?? 50;
|
||||
const offset = args.offset ?? 0;
|
||||
|
||||
const conditions: any[] = [];
|
||||
const conditions: SQL<unknown>[] = [];
|
||||
if (args.search) conditions.push(ilike(videos.title, `%${args.search}%`));
|
||||
if (args.premium !== null && args.premium !== undefined)
|
||||
conditions.push(eq(videos.premium, args.premium));
|
||||
@@ -517,7 +519,7 @@ builder.queryField("adminListVideos", (t) =>
|
||||
.offset(offset),
|
||||
ctx.db.select({ total: count() }).from(videos).where(where),
|
||||
]);
|
||||
const items = await Promise.all(rows.map((v: any) => enrichVideo(ctx.db, v)));
|
||||
const items = await Promise.all(rows.map((v) => enrichVideo(ctx.db, v)));
|
||||
return { items, total: totalRows[0]?.total ?? 0 };
|
||||
},
|
||||
}),
|
||||
@@ -590,7 +592,7 @@ builder.mutationField("updateVideo", (t) =>
|
||||
|
||||
const updated = await ctx.db
|
||||
.update(videos)
|
||||
.set(updates as any)
|
||||
.set(updates as Partial<typeof videos.$inferInsert>)
|
||||
.where(eq(videos.id, args.id))
|
||||
.returning();
|
||||
if (!updated[0]) return null;
|
||||
|
||||
@@ -47,7 +47,7 @@ export async function calculateWeightedScore(db: DB, userId: string): Promise<nu
|
||||
FROM user_points
|
||||
WHERE user_id = ${userId}
|
||||
`);
|
||||
return parseFloat((result.rows[0] as any)?.weighted_score || "0");
|
||||
return parseFloat((result.rows[0] as { weighted_score?: string })?.weighted_score || "0");
|
||||
}
|
||||
|
||||
export async function updateUserStats(db: DB, userId: string): Promise<void> {
|
||||
@@ -84,7 +84,7 @@ export async function updateUserStats(db: DB, userId: string): Promise<void> {
|
||||
sql`, `,
|
||||
)})
|
||||
`);
|
||||
playbacksCount = parseInt((playbacksResult.rows[0] as any)?.count || "0");
|
||||
playbacksCount = parseInt((playbacksResult.rows[0] as { count?: string })?.count || "0");
|
||||
} else {
|
||||
const playbacksResult = await db
|
||||
.select({ count: count() })
|
||||
@@ -242,7 +242,7 @@ async function getAchievementProgress(
|
||||
WHERE rp.user_id = ${userId}
|
||||
AND r.user_id != ${userId}
|
||||
`);
|
||||
return parseInt((result.rows[0] as any)?.count || "0");
|
||||
return parseInt((result.rows[0] as { count?: string })?.count || "0");
|
||||
}
|
||||
|
||||
if (["completionist_10", "completionist_100"].includes(code)) {
|
||||
@@ -293,7 +293,7 @@ async function getAchievementProgress(
|
||||
WHERE rp.user_id = ${userId} AND r.user_id != ${userId}
|
||||
`);
|
||||
const rc = recordingsResult[0]?.count || 0;
|
||||
const pc = parseInt((playsResult.rows[0] as any)?.count || "0");
|
||||
const pc = parseInt((playsResult.rows[0] as { count?: string })?.count || "0");
|
||||
return rc >= 50 && pc >= 100 ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user