93 lines
2.1 KiB
TypeScript
93 lines
2.1 KiB
TypeScript
import { query } from '../db/client.js'
|
|
import type { ShortLinkCreateInput } from '../types/repository/inputs.js'
|
|
import type { ShortLinkRow } from '../types/repository/rows.js'
|
|
|
|
export async function createShortLink(input: ShortLinkCreateInput): Promise<ShortLinkRow | null> {
|
|
const result = await query<ShortLinkRow>(
|
|
`
|
|
INSERT INTO short_links (
|
|
code,
|
|
target_url,
|
|
target_url_hash,
|
|
source,
|
|
task_id,
|
|
expires_at,
|
|
created_at,
|
|
updated_at
|
|
)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
ON CONFLICT (source, task_id, target_url_hash) DO UPDATE
|
|
SET
|
|
target_url = EXCLUDED.target_url,
|
|
expires_at = EXCLUDED.expires_at,
|
|
updated_at = EXCLUDED.updated_at
|
|
RETURNING *
|
|
`,
|
|
[
|
|
input.code,
|
|
input.targetUrl,
|
|
input.targetUrlHash,
|
|
input.source || '',
|
|
normalizeNullableId(input.taskId),
|
|
input.expiresAt || null,
|
|
input.createdAt,
|
|
input.updatedAt,
|
|
],
|
|
)
|
|
|
|
return result.rows[0] || null
|
|
}
|
|
|
|
export async function findShortLinkByCode(code: string): Promise<ShortLinkRow | null> {
|
|
const normalizedCode = String(code || '').trim()
|
|
if (!normalizedCode) {
|
|
return null
|
|
}
|
|
|
|
const result = await query<ShortLinkRow>(
|
|
`
|
|
SELECT *
|
|
FROM short_links
|
|
WHERE code = $1
|
|
LIMIT 1
|
|
`,
|
|
[normalizedCode],
|
|
)
|
|
|
|
return result.rows[0] || null
|
|
}
|
|
|
|
export async function markShortLinkVisited(
|
|
code: string,
|
|
visitedAt: string,
|
|
): Promise<ShortLinkRow | null> {
|
|
const normalizedCode = String(code || '').trim()
|
|
if (!normalizedCode) {
|
|
return null
|
|
}
|
|
|
|
const result = await query<ShortLinkRow>(
|
|
`
|
|
UPDATE short_links
|
|
SET
|
|
visit_count = visit_count + 1,
|
|
last_visited_at = $2,
|
|
updated_at = $2
|
|
WHERE code = $1
|
|
RETURNING *
|
|
`,
|
|
[normalizedCode, visitedAt],
|
|
)
|
|
|
|
return result.rows[0] || null
|
|
}
|
|
|
|
function normalizeNullableId(value: unknown): number | null {
|
|
if (value === null || value === undefined || value === '') {
|
|
return null
|
|
}
|
|
|
|
const parsed = Number(value)
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : null
|
|
}
|