chore: remove unused code and visitor tracking

This commit is contained in:
2026-08-23 13:46:47 +00:00 Unverified
parent c78bac8704
commit 03c5c69af9
12 changed files with 9 additions and 220 deletions
-8
View File
@@ -14,14 +14,6 @@ export function getCanonicalUrl() {
return requiredEnvironmentUrl("NEXTAUTH_URL");
}
export function getVisitorTrackingConfig() {
return {
secret: readFeatureEnvironment("Visitor tracking", [
"NEXTAUTH_SECRET",
] as const).NEXTAUTH_SECRET,
};
}
export function getDiscordServerConfig() {
const config = readFeatureEnvironment("Discord", [
"DISCORD_BOT_TOKEN",
-50
View File
@@ -85,53 +85,3 @@ export async function getTiktokProfile(): Promise<TiktokProfile> {
return empty;
}
}
/** Convenience wrapper that just returns the follower count */
export async function getTiktokFollowerCount(): Promise<number | null> {
const profile = await getTiktokProfile();
return profile.followerCount;
}
export interface TiktokVideo {
id: string;
title: string;
coverImageUrl: string;
shareUrl: string;
createTime: number;
}
export async function getLatestTiktokVideo(): Promise<TiktokVideo | null> {
const profile = await getTiktokProfile();
if (!profile.username) {
console.error("[TikTok API] Could not get username from profile to fetch TikWM.");
return null;
}
try {
const res = await fetch(`https://www.tikwm.com/api/user/posts?unique_id=@${profile.username}&count=1`, {
cache: "no-store",
signal: AbortSignal.timeout(5000),
});
if (!res.ok) return null;
const data = await res.json();
const videos = data.data?.videos;
if (!videos || videos.length === 0) return null;
const v = videos[0];
const shareUrl = `https://www.tiktok.com/@${profile.username}/video/${v.video_id}`;
return {
id: v.video_id,
title: v.title ?? "",
coverImageUrl: v.cover ?? "",
shareUrl: shareUrl,
createTime: v.create_time ?? 0,
};
} catch (e) {
console.error("[TikTok API] Error fetching latest video via TikWM:", e);
return null;
}
}
+1 -49
View File
@@ -1,5 +1,5 @@
import "server-only";
import { and, eq, gte, inArray, sql } from "drizzle-orm";
import { and, eq, gte, inArray } from "drizzle-orm";
import { drizzle } from "drizzle-orm/postgres-js";
import { integer, numeric, pgTable, text } from "drizzle-orm/pg-core";
import postgres from "postgres";
@@ -69,29 +69,6 @@ export async function getXpLeaderboardPage(
return buildXpLeaderboard(rows, requestedPage, searchQuery);
}
export async function getMemberXpStats(userId: string) {
const database = getLeaderboardDatabase();
const [sourceRows, leaderboard] = await Promise.all([
database
.select({
level: userLevels.level,
totalXp: userLevels.xp,
})
.from(userLevels)
.where(eq(userLevels.userId, userId))
.limit(1),
getXpLeaderboardPage(1, userId),
]);
const rankedRow = leaderboard.rows.find((row) => row.userId === userId);
const sourceRow = sourceRows[0];
return {
level: sourceRow?.level ?? 0,
totalXp: sourceRow?.totalXp ?? "0",
rank: rankedRow?.rank ?? null,
};
}
export async function getVoiceLeaderboardPage(
currentChannelIds: string[],
requestedPage: number,
@@ -131,28 +108,3 @@ export async function getVoiceLeaderboardPage(
searchQuery,
);
}
export async function getMemberVoiceStats(
userId: string,
currentChannelIds: string[],
) {
const leaderboard = await getVoiceLeaderboardPage(
currentChannelIds,
1,
undefined,
userId,
);
const rankedRow = leaderboard.rows.find((row) => row.userId === userId);
return {
totalSeconds: rankedRow?.totalSeconds ?? "0",
rank: rankedRow?.rank ?? null,
};
}
export async function getLeaderboardDatabaseReadOnlyStatus() {
const result = await getLeaderboardDatabase().execute<{
transaction_read_only: string;
}>(sql`show transaction_read_only`);
return result[0]?.transaction_read_only ?? null;
}
-31
View File
@@ -1,31 +0,0 @@
import { describe, expect, test } from "bun:test";
import {
getClientAddress,
hashClientAddress,
} from "./visitor-identifiers";
describe("privacy-preserving visitor tracking", () => {
test("uses the first valid forwarded address", () => {
const headers = new Headers({
"x-forwarded-for": "203.0.113.9, 10.0.0.1",
"x-real-ip": "192.0.2.2",
});
expect(getClientAddress(headers)).toBe("203.0.113.9");
});
test("rejects untrusted non-IP identifiers", () => {
expect(
getClientAddress(new Headers({ "x-forwarded-for": "unknown" })),
).toBeNull();
});
test("creates a stable identifier without retaining the address", () => {
const address = "203.0.113.9";
const identifier = hashClientAddress(address, "test-secret");
expect(identifier).toBe(hashClientAddress(address, "test-secret"));
expect(identifier).not.toContain(address);
expect(identifier).toHaveLength(64);
});
});
-38
View File
@@ -1,38 +0,0 @@
import "server-only";
import { getVisitorTrackingConfig } from "@/lib/config/server";
import { getRedisClient } from "@/lib/redis";
import {
getClientAddress,
hashClientAddress,
} from "@/lib/visitor/visitor-identifiers";
const VISITOR_SET_KEY = "erika:unique_visitors:v2";
const LEGACY_VISITOR_SET_KEY = "erika:unique_visitors";
export async function trackUniqueVisitor(headers: Headers) {
const client = await getRedisClient();
const address = getClientAddress(headers);
if (address) {
const { secret } = getVisitorTrackingConfig();
const identifier = hashClientAddress(address, secret);
const [isLegacyVisitor, isPrivateVisitor] = await Promise.all([
client.sismember(LEGACY_VISITOR_SET_KEY, address),
client.sismember(VISITOR_SET_KEY, identifier),
]);
if (isLegacyVisitor) {
// Keep the legacy visitor in the old total without counting a revisit twice.
if (isPrivateVisitor) await client.srem(VISITOR_SET_KEY, identifier);
} else {
await client.sadd(VISITOR_SET_KEY, identifier);
}
}
const [legacyCount, privateCount] = await Promise.all([
client.scard(LEGACY_VISITOR_SET_KEY),
client.scard(VISITOR_SET_KEY),
]);
return legacyCount + privateCount;
}
-15
View File
@@ -1,15 +0,0 @@
import { createHmac } from "node:crypto";
import { isIP } from "node:net";
export function getClientAddress(headers: Headers) {
const forwardedAddress = headers
.get("x-forwarded-for")
?.split(",", 1)[0]
?.trim();
const address = forwardedAddress || headers.get("x-real-ip")?.trim();
return address && isIP(address) ? address : null;
}
export function hashClientAddress(address: string, secret: string) {
return createHmac("sha256", secret).update(address).digest("hex");
}