Skip to content

Commit 0eeff57

Browse files
committed
refactor: fixes, simplifications, bot filters, update deps
1 parent 61df397 commit 0eeff57

17 files changed

Lines changed: 464 additions & 466 deletions

File tree

bun.lock

Lines changed: 136 additions & 115 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE `links` DROP COLUMN `redirects`;

migrations/meta/0003_snapshot.json

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
{
2+
"version": "6",
3+
"dialect": "sqlite",
4+
"id": "bc8aa74b-be1c-46da-b0c0-4f869dc5be4b",
5+
"prevId": "a64c5a8c-6a50-48b4-a2ff-af94623a6a8d",
6+
"tables": {
7+
"links": {
8+
"name": "links",
9+
"columns": {
10+
"id": {
11+
"name": "id",
12+
"type": "integer",
13+
"primaryKey": true,
14+
"notNull": true,
15+
"autoincrement": true
16+
},
17+
"code": {
18+
"name": "code",
19+
"type": "text",
20+
"primaryKey": false,
21+
"notNull": true,
22+
"autoincrement": false
23+
},
24+
"url": {
25+
"name": "url",
26+
"type": "text",
27+
"primaryKey": false,
28+
"notNull": true,
29+
"autoincrement": false
30+
},
31+
"created_at": {
32+
"name": "created_at",
33+
"type": "integer",
34+
"primaryKey": false,
35+
"notNull": true,
36+
"autoincrement": false,
37+
"default": "(unixepoch())"
38+
}
39+
},
40+
"indexes": {
41+
"links_code_unique": {
42+
"name": "links_code_unique",
43+
"columns": [
44+
"code"
45+
],
46+
"isUnique": true
47+
}
48+
},
49+
"foreignKeys": {},
50+
"compositePrimaryKeys": {},
51+
"uniqueConstraints": {},
52+
"checkConstraints": {}
53+
},
54+
"redirects": {
55+
"name": "redirects",
56+
"columns": {
57+
"id": {
58+
"name": "id",
59+
"type": "integer",
60+
"primaryKey": true,
61+
"notNull": true,
62+
"autoincrement": true
63+
},
64+
"link_id": {
65+
"name": "link_id",
66+
"type": "integer",
67+
"primaryKey": false,
68+
"notNull": true,
69+
"autoincrement": false
70+
},
71+
"location": {
72+
"name": "location",
73+
"type": "text",
74+
"primaryKey": false,
75+
"notNull": false,
76+
"autoincrement": false
77+
},
78+
"language": {
79+
"name": "language",
80+
"type": "text",
81+
"primaryKey": false,
82+
"notNull": false,
83+
"autoincrement": false
84+
},
85+
"referrer": {
86+
"name": "referrer",
87+
"type": "text",
88+
"primaryKey": false,
89+
"notNull": false,
90+
"autoincrement": false
91+
},
92+
"user_agent": {
93+
"name": "user_agent",
94+
"type": "text",
95+
"primaryKey": false,
96+
"notNull": false,
97+
"autoincrement": false
98+
},
99+
"created_at": {
100+
"name": "created_at",
101+
"type": "integer",
102+
"primaryKey": false,
103+
"notNull": true,
104+
"autoincrement": false,
105+
"default": "(unixepoch())"
106+
}
107+
},
108+
"indexes": {},
109+
"foreignKeys": {
110+
"redirects_link_id_links_id_fk": {
111+
"name": "redirects_link_id_links_id_fk",
112+
"tableFrom": "redirects",
113+
"tableTo": "links",
114+
"columnsFrom": [
115+
"link_id"
116+
],
117+
"columnsTo": [
118+
"id"
119+
],
120+
"onDelete": "cascade",
121+
"onUpdate": "no action"
122+
}
123+
},
124+
"compositePrimaryKeys": {},
125+
"uniqueConstraints": {},
126+
"checkConstraints": {}
127+
}
128+
},
129+
"views": {},
130+
"enums": {},
131+
"_meta": {
132+
"schemas": {},
133+
"tables": {},
134+
"columns": {}
135+
},
136+
"internal": {
137+
"indexes": {}
138+
}
139+
}

migrations/meta/_journal.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@
2222
"when": 1713383605861,
2323
"tag": "0002_plain_the_spike",
2424
"breakpoints": true
25+
},
26+
{
27+
"idx": 3,
28+
"version": "6",
29+
"when": 1774441754128,
30+
"tag": "0003_smooth_mac_gargan",
31+
"breakpoints": true
2532
}
2633
]
2734
}

package.json

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,23 @@
1717
"validate": "bun typecheck && bun fmt && bun lint"
1818
},
1919
"dependencies": {
20-
"@elysiajs/bearer": "^1.4.2",
20+
"@elysiajs/bearer": "^1.4.3",
2121
"@elysiajs/cors": "^1.4.1",
22-
"@libsql/client": "^0.17.0",
23-
"@lucia-auth/adapter-drizzle": "^1.0.7",
24-
"dotenv": "^17.2.3",
22+
"@libsql/client": "^0.17.2",
23+
"dotenv": "^17.3.1",
2524
"drizzle-orm": "^0.45.1",
26-
"elysia": "^1.4.22",
25+
"elysia": "^1.4.28",
2726
"elysia-helmet": "^3.0.0",
2827
"elysia-ip": "^1.0.10",
29-
"isbot": "^5.1.33",
30-
"nanoid": "^5.1.6",
31-
"ua-parser-js": "^2.0.8"
28+
"isbot": "^5.1.36",
29+
"nanoid": "^5.1.7",
30+
"ua-parser-js": "^2.0.9"
3231
},
3332
"devDependencies": {
34-
"@types/bun": "^1.3.6",
35-
"@types/ua-parser-js": "^0.7.39",
36-
"drizzle-kit": "^0.31.8",
37-
"oxfmt": "^0.26.0",
38-
"oxlint": "^1.41.0"
33+
"@elysiajs/eden": "^1.4.8",
34+
"@types/bun": "^1.3.11",
35+
"drizzle-kit": "^0.31.10",
36+
"oxfmt": "^0.42.0",
37+
"oxlint": "^1.57.0"
3938
}
4039
}

src/app.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ export const createApp = () =>
1818
.use(cors())
1919
.use(helmet())
2020
.use(ip({ headersOnly: true }))
21-
2221
// Logging & performance
2322
.onBeforeHandle(({ path, request }) => console.log(`🦊 ${request.method} - ${path}`))
2423
.trace(async ({ onHandle }) => {
@@ -42,15 +41,14 @@ export const createApp = () =>
4241
async afterResponse({ params, ip, headers }) {
4342
const link = await links.getLink(params.code);
4443
const userAgent = headers["user-agent"];
45-
if (!link || isPotentialBot(userAgent)) return;
44+
if (!link || isPotentialBot(userAgent) || !headers["accept-language"]) return;
4645
await insertRedirect({
4746
linkId: link.id,
4847
location: JSON.stringify(await getIPLocation(ip)) ?? null,
49-
language: headers["accept-language"]?.split(",")[0],
48+
language: headers["accept-language"].split(",")[0],
5049
referrer: headers["referer"],
5150
userAgent: JSON.stringify(new UAParser(userAgent).getResult()),
5251
});
53-
await links.incrementRedirects(params.code);
5452
},
5553
},
5654
)

src/db/schema.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ export const linksTable = sqliteTable("links", {
55
id: integer("id").primaryKey({ autoIncrement: true }),
66
code: text("code").unique().notNull(),
77
url: text("url").notNull(),
8-
redirects: integer("redirects").default(0),
98
createdAt: integer("created_at", { mode: "timestamp" })
109
.notNull()
1110
.default(sql`(unixepoch())`),

src/errors/index.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/functions/links.ts

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
1-
import { count, desc, eq, sql, sum } from "drizzle-orm";
1+
import { count, desc, eq } from "drizzle-orm";
22

33
import { db } from "../db/db";
4-
import { InsertLink, SelectLink, linksTable } from "../db/schema";
4+
import { InsertLink, SelectLink, linksTable, redirectsTable } from "../db/schema";
55
import { generateCode } from "./utils";
66

7-
export async function codeAlreadyUsed(code: SelectLink["code"]): Promise<boolean> {
8-
const link = await db.select().from(linksTable).where(eq(linksTable.code, code));
9-
return link.length > 0;
7+
async function codeAlreadyUsed(code: SelectLink["code"]): Promise<boolean> {
8+
const result = await db
9+
.select({ id: linksTable.id })
10+
.from(linksTable)
11+
.where(eq(linksTable.code, code))
12+
.get();
13+
return result !== undefined;
1014
}
1115

1216
export async function validateCode(code: string | undefined): Promise<string> {
@@ -34,30 +38,34 @@ export async function editLink(code: SelectLink["code"], data: InsertLink): Prom
3438
return await db.update(linksTable).set(data).where(eq(linksTable.code, code)).returning();
3539
}
3640

37-
export async function incrementRedirects(code: SelectLink["code"]): Promise<void> {
38-
await db
39-
.update(linksTable)
40-
.set({ redirects: sql`${linksTable.redirects} + 1` })
41-
.where(eq(linksTable.code, code));
41+
export async function deleteLink(code: SelectLink["code"]): Promise<boolean> {
42+
const result = await db
43+
.delete(linksTable)
44+
.where(eq(linksTable.code, code))
45+
.returning({ id: linksTable.id });
46+
return result.length > 0;
4247
}
4348

44-
export async function deleteLink(code: SelectLink["code"]): Promise<void> {
45-
await db.delete(linksTable).where(eq(linksTable.code, code));
46-
}
47-
48-
export async function deleteAllLinks(): Promise<void> {
49-
await db.delete(linksTable);
50-
}
5149

52-
export async function getAllLinks(): Promise<SelectLink[]> {
53-
return db.select().from(linksTable).orderBy(desc(linksTable.id));
50+
export async function getAllLinks() {
51+
return db
52+
.select({
53+
id: linksTable.id,
54+
code: linksTable.code,
55+
url: linksTable.url,
56+
redirects: count(redirectsTable.id),
57+
createdAt: linksTable.createdAt,
58+
})
59+
.from(linksTable)
60+
.leftJoin(redirectsTable, eq(redirectsTable.linkId, linksTable.id))
61+
.groupBy(linksTable.id)
62+
.orderBy(desc(linksTable.id));
5463
}
5564

5665
export async function getLinkStats(): Promise<{ totalLinks: number; totalRedirects: number }> {
57-
const totalLinks = await db.select({ count: count() }).from(linksTable);
58-
if (totalLinks[0].count === 0) return { totalLinks: 0, totalRedirects: 0 };
59-
const totalRedirects = await db
60-
.select({ sum: sum(linksTable.redirects).mapWith(Number) })
61-
.from(linksTable);
62-
return { totalLinks: totalLinks[0].count, totalRedirects: totalRedirects[0].sum };
66+
const [[{ totalLinks }], [{ totalRedirects }]] = await Promise.all([
67+
db.select({ totalLinks: count() }).from(linksTable),
68+
db.select({ totalRedirects: count() }).from(redirectsTable),
69+
]);
70+
return { totalLinks, totalRedirects };
6371
}

src/functions/utils.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export function generateCode(): string {
66
}
77

88
export function getIPLocation(
9-
ip: any,
9+
ip: string | undefined,
1010
): Promise<{ city: string; regionName: string; country: string } | null> {
1111
if (!ip) {
1212
return Promise.resolve(null);
@@ -30,16 +30,13 @@ export function getIPLocation(
3030
});
3131
}
3232

33-
const BANNED_USER_AGENTS = ["Snapchat"] as const;
33+
// "Chrome/130" + "Mac OS X 10_15_7" = frozen VM fingerprint used by link health checkers
34+
const BANNED_USER_AGENTS = ["Snapchat", "WOW64", "Chrome/130"] as const;
3435

3536
export function isPotentialBot(userAgent: string | undefined): boolean {
36-
if (!userAgent || userAgent.startsWith("Bun/")) {
37-
return false;
38-
}
39-
40-
if (userAgent === "") {
41-
return true;
42-
}
37+
if (userAgent === undefined) return false;
38+
if (!userAgent) return true;
39+
if (userAgent.startsWith("Bun/")) return false;
4340

4441
if (BANNED_USER_AGENTS.some((banned) => userAgent.includes(banned))) {
4542
return true;

0 commit comments

Comments
 (0)