feat: photo sync (iCloud + Amazon import), larger gear icon, Luma branding

- Settings: iCloud Photos section with icloudpd-backed sync — Apple ID,
  password, download path, cookie dir, live log output via SSE
- Settings: Amazon Photos one-time import — path to extracted data export,
  Add & Index button wires it directly into Watch Folders
- scripts/amazon_sync.py: stdlib-only downloader using Netscape cookie file
- server: POST /api/sync/icloud, /api/sync/amazon, GET /api/sync/stream
  (SSE log streaming matching ingest stream pattern)
- db: icloud/amazon setting keys; getSettings handles bool + string fields
- Gear icon: fontSize 16→20, button 38×38, color #555→#888
- favicon.svg: 6-blade aperture icon in Luma blue
- Browser tab title: shpm → Luma
- Dockerfile: installs icloudpd via pip3, copies scripts/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 4.6 committed 2026-08-18 09:30:43 -04:00
1 parent 5ff7f38b85
commit 4ff5a6c8ca
9 files changed
+569 -17

No files matched your search

+20 -7
View File
@@ -3,13 +3,26 @@ import path from 'path';
import fs from 'fs';
const SETTING_DEFAULTS = {
thumb_size: '400',
thumb_quality: '80',
preview_size: '1600',
preview_quality: '85',
dedupe_threshold: '10',
thumb_size: '400',
thumb_quality: '80',
preview_size: '1600',
preview_quality: '85',
dedupe_threshold: '10',
// iCloud Photos sync
icloud_enabled: '0',
icloud_apple_id: '',
icloud_password: '',
icloud_dest: '/photos/icloud',
icloud_cookie_dir: '/data/icloud-cookies',
// Amazon Photos sync
amazon_enabled: '0',
amazon_cookie_file: '',
amazon_dest: '/photos/amazon',
};
const NUMERIC_SETTINGS = new Set(['thumb_size', 'thumb_quality', 'preview_size', 'preview_quality', 'dedupe_threshold']);
const BOOL_SETTINGS = new Set(['icloud_enabled', 'amazon_enabled']);
export function openDb(dbPath) {
fs.mkdirSync(path.dirname(path.resolve(dbPath)), { recursive: true });
@@ -218,8 +231,8 @@ export function openDb(dbPath) {
const rows = s.settingsAll.all();
const out = { ...SETTING_DEFAULTS };
for (const { key, value } of rows) out[key] = value;
// Parse numerics
for (const k of Object.keys(SETTING_DEFAULTS)) out[k] = parseInt(out[k], 10);
for (const k of NUMERIC_SETTINGS) out[k] = parseInt(out[k], 10);
for (const k of BOOL_SETTINGS) out[k] = out[k] === '1' || out[k] === true;
return out;
},
setSetting: (key, value) => s.settingsSet.run(key, String(value)),