chore: remove unused code and visitor tracking
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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,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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
Reference in New Issue
Block a user