ファイル
btrc-hub/frontend/src/lib/postImportSourceValidation.ts
T
2026-10-20 20:44:14 +09:00

126 行
3.2 KiB
TypeScript

import { translateUIMessage } from '@/lib/i18n'
import type { PostImportSourceIssue } from '@/lib/postImportTypes'
const MAX_ROWS = 100
const MAX_URL_BYTES = 20 * 1024
const truncateUrl = (value: string): string =>
value.length > 120 ? `${ value.slice (0, 117) }…` : value
const bytesize = (value: string): number =>
new TextEncoder ().encode (value).length
const parseImportUrl = (value: string): URL | null => {
const trimmed = value.trim ()
if (!(trimmed))
return null
try
{
return new URL (trimmed)
}
catch
{
return null
}
}
const normaliseImportUrl = (url: URL): string => {
url.hostname = url.hostname.toLowerCase ()
if (url.pathname.endsWith ('/'))
url.pathname = url.pathname.replace (/\/+$/, '')
return url.toString ()
}
export const extractImportSourceUrls = (source: string): string[] =>
source
.split (/\r\n|\n|\r/)
.map (line => line.trim ())
.filter (line => line !== '')
export const countImportSourceLines = (source: string): number =>
extractImportSourceUrls (source).length
export const validateImportSource = (
source: string,
): PostImportSourceIssue[] => {
const lines = source.split (/\r\n|\n|\r/)
const issues: PostImportSourceIssue[] = []
const seen = new Map<string, number> ()
let count = 0
lines.forEach ((rawLine, index) => {
const value = rawLine.trim ()
if (!(value))
return
++count
const sourceRow = index + 1
const displayUrl = truncateUrl (value)
if (count > MAX_ROWS)
{
issues.push ({
sourceRow,
message: translateUIMessage ('validation.tooMany', 'imports', 'ja', {
count: MAX_ROWS }).text,
url: displayUrl })
return
}
if (bytesize (value) > MAX_URL_BYTES)
{
issues.push ({
sourceRow,
message: translateUIMessage ('validation.urlTooLong', 'imports', 'ja').text,
url: displayUrl })
return
}
const parsed = parseImportUrl (value)
if (parsed == null)
{
issues.push ({
sourceRow,
message: translateUIMessage ('validation.invalidUrl', 'imports', 'ja').text,
url: displayUrl })
return
}
if (!(parsed.protocol === 'http:' || parsed.protocol === 'https:'))
{
issues.push ({
sourceRow,
message: translateUIMessage ('validation.protocol', 'imports', 'ja').text,
url: displayUrl })
return
}
if (!(parsed.host))
{
issues.push ({
sourceRow,
message: translateUIMessage ('validation.invalidUrl', 'imports', 'ja').text,
url: displayUrl })
return
}
const normalised = normaliseImportUrl (parsed)
const duplicateRow = seen.get (normalised)
if (duplicateRow != null)
{
issues.push ({
sourceRow,
message: translateUIMessage ('validation.duplicateRow', 'imports', 'ja', {
row: duplicateRow }).text,
url: displayUrl })
return
}
seen.set (normalised, sourceRow)
})
return issues
}