Skip to content

Commit cd8decb

Browse files
authored
Merge pull request #8 from hienao/dev
Support scrape submission over HTTP
2 parents cc88e94 + edaec69 commit cd8decb

3 files changed

Lines changed: 37 additions & 1 deletion

File tree

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { createIdempotencyKey } from './idempotency-key'
2+
3+
describe('createIdempotencyKey', () => {
4+
it('uses randomUUID when it is available', () => {
5+
const randomUUID = vi.fn(() => 'd9428888-122b-4c26-a04f-efc2b3e153a7')
6+
const getRandomValues = vi.fn((bytes: Uint8Array) => bytes)
7+
8+
expect(createIdempotencyKey({ randomUUID, getRandomValues })).toBe('d9428888-122b-4c26-a04f-efc2b3e153a7')
9+
expect(getRandomValues).not.toHaveBeenCalled()
10+
})
11+
12+
it('creates a version 4 UUID with getRandomValues when randomUUID is unavailable', () => {
13+
const getRandomValues = vi.fn((bytes: Uint8Array) => {
14+
bytes.set(Array.from({ length: 16 }, (_, index) => index))
15+
return bytes
16+
})
17+
18+
expect(createIdempotencyKey({ getRandomValues })).toBe('00010203-0405-4607-8809-0a0b0c0d0e0f')
19+
})
20+
})
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
interface CryptoSource {
2+
randomUUID?: () => string
3+
getRandomValues: (array: Uint8Array) => Uint8Array
4+
}
5+
6+
export function createIdempotencyKey(cryptoSource: CryptoSource = globalThis.crypto) {
7+
if (typeof cryptoSource.randomUUID === 'function') return cryptoSource.randomUUID()
8+
9+
const bytes = cryptoSource.getRandomValues(new Uint8Array(16))
10+
bytes[6] = (bytes[6] & 0x0f) | 0x40
11+
bytes[8] = (bytes[8] & 0x3f) | 0x80
12+
13+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('')
14+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
15+
}

‎frontend/src/pages/targets-page.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { FormField } from '@/components/common/form-field'
1616
import { Message } from '@/components/common/message'
1717
import { Panel } from '@/components/common/panel'
1818
import { errorMessage } from '@/lib/error-message'
19+
import { createIdempotencyKey } from '@/lib/idempotency-key'
1920

2021
const emptyForm: TargetInput = { source_type: 'openlist', connection_id: 0, name: '', root_path: '/', library_type: 'movie', rename_enabled: false, enabled: true }
2122

@@ -95,7 +96,7 @@ export function TargetsPage() {
9596
onError: (error) => setMatchError(errorMessage(error, t('targets.previewError'))),
9697
})
9798
const executeJob = useMutation({
98-
mutationFn: (value: ScrapePreview) => jobApi.submit(value.target_id, { preview_id: value.id, rename_media: value.plan.proposed_directory_creates.length + value.plan.proposed_directory_renames.length + value.plan.proposed_file_renames.length > 0, confirm_directory_fingerprint: value.fingerprint }, crypto.randomUUID()),
99+
mutationFn: (value: ScrapePreview) => jobApi.submit(value.target_id, { preview_id: value.id, rename_media: value.plan.proposed_directory_creates.length + value.plan.proposed_directory_renames.length + value.plan.proposed_file_renames.length > 0, confirm_directory_fingerprint: value.fingerprint }, createIdempotencyKey()),
99100
onSuccess: () => { setMatchCandidate(null); void navigate('/jobs') },
100101
onError: (error) => setMatchError(errorMessage(error, t('targets.executeError'))),
101102
})

0 commit comments

Comments
 (0)