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:
2026-03-07 19:25:04 +01:00
parent 8313664d70
commit e236ced12a
30 changed files with 392 additions and 375 deletions

View File

@@ -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;