From 6033461be118ce78277ec568e1ef1ceeff7311c8 Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Sat, 3 Jan 2026 23:32:17 +0100 Subject: [PATCH] feat: add Apple Liquid Glass templates, image security settings, and automated releases ## New Features - Apple Liquid Glass CSS template with iOS 26-inspired design - Liquid Glass Dark theme with neon accents - Image Security settings tab with per-event protection levels - Release Please automation for versioning and changelog ## Improvements - Update CSS template migration with final working templates - Add search placeholder visibility fix for glass themes - Update README roadmap (Download Protection, Gallery Templates, Filtering & Export now implemented) ## Infrastructure - Add release-please.yml workflow for automated releases - Add release-please-config.json and manifest - Update docker-build.yml with Release Please integration comments - Add comprehensive CHANGELOG.md ## Cleanup - Add working/planning docs to .gitignore (CLAUDE.md, test-*.md, feature-*.md, etc.) - Remove internal planning documents from git tracking (kept locally) ## Files Added - .github/workflows/release-please.yml - .release-please-manifest.json - release-please-config.json - CHANGELOG.md - frontend/src/features/settings/tabs/ImageSecurityTab.tsx --- .github/workflows/docker-build.yml | 10 +- .github/workflows/release-please.yml | 71 + .gitignore | 7 + .release-please-manifest.json | 3 + BUGS_AND_FEATURES.md | 1309 ------------- CHANGELOG.md | 63 + README.md | 6 +- .../core/053_add_liquid_glass_templates.js | 474 ++--- backend/package-lock.json | 1452 +++++++------- backend/scripts/check-storage.js | 0 backend/scripts/cleanup-thumbnails.js | 0 backend/scripts/create-admin.js | 0 backend/scripts/migrate-upload-assets.js | 0 backend/scripts/regenerate-thumbnails.js | 0 backend/scripts/reset-admin-password.js | 0 backend/src/routes/adminEvents.js | 9 +- backend/src/routes/adminImageSecurity.js | 8 +- backend/src/routes/gallery.js | 63 +- docs/REFACTORING_PLAN.md | 1744 ----------------- docs/feature-custom-css-templates.md | 1263 ------------ docs/feature-event-rename.md | 492 ----- docs/feature-optional-event-fields.md | 564 ------ docs/feature-photo-filtering-export.md | 798 -------- docs/test-custom-css-templates.md | 338 ---- docs/test-event-rename.md | 342 ---- docs/test-optional-event-fields.md | 245 --- docs/test-photo-filtering-export.md | 327 ---- frontend/TEST_PLAN.md | 1022 ---------- .../components/common/AuthenticatedImage.tsx | 89 +- .../src/components/gallery/GalleryView.tsx | 35 +- frontend/src/components/gallery/PhotoGrid.tsx | 25 +- .../gallery/PhotoGridWithLayouts.tsx | 16 +- .../src/components/gallery/PhotoLightbox.tsx | 33 +- .../gallery/layouts/BaseGalleryLayout.tsx | 1 + .../gallery/layouts/GridGalleryLayout.tsx | 6 +- .../gallery/layouts/HeroGalleryLayout.tsx | 17 +- frontend/src/features/settings/index.ts | 1 + .../settings/tabs/ImageSecurityTab.tsx | 381 ++++ frontend/src/i18n/locales/de.json | 6 + frontend/src/i18n/locales/en.json | 6 + frontend/src/pages/admin/EventDetailsPage.tsx | 149 +- frontend/src/pages/admin/SettingsPage.tsx | 6 +- frontend/src/types/index.ts | 12 +- release-please-config.json | 28 + 44 files changed, 1978 insertions(+), 9443 deletions(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json delete mode 100644 BUGS_AND_FEATURES.md create mode 100644 CHANGELOG.md mode change 100755 => 100644 backend/scripts/check-storage.js mode change 100755 => 100644 backend/scripts/cleanup-thumbnails.js mode change 100755 => 100644 backend/scripts/create-admin.js mode change 100755 => 100644 backend/scripts/migrate-upload-assets.js mode change 100755 => 100644 backend/scripts/regenerate-thumbnails.js mode change 100755 => 100644 backend/scripts/reset-admin-password.js delete mode 100644 docs/REFACTORING_PLAN.md delete mode 100644 docs/feature-custom-css-templates.md delete mode 100644 docs/feature-event-rename.md delete mode 100644 docs/feature-optional-event-fields.md delete mode 100644 docs/feature-photo-filtering-export.md delete mode 100644 docs/test-custom-css-templates.md delete mode 100644 docs/test-event-rename.md delete mode 100644 docs/test-optional-event-fields.md delete mode 100644 docs/test-photo-filtering-export.md delete mode 100644 frontend/TEST_PLAN.md create mode 100644 frontend/src/features/settings/tabs/ImageSecurityTab.tsx create mode 100644 release-please-config.json diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 6a1c4550..482f3af1 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -1,13 +1,19 @@ name: Build and Push Docker Images +# This workflow is triggered by: +# - Push to main/develop branches (builds 'latest' or branch-tagged images) +# - Version tags from Release Please (e.g., v1.2.0 -> builds versioned images) +# - GitHub Releases (created by Release Please) +# - Manual workflow dispatch + on: push: branches: [ main, develop ] - tags: [ 'v*.*.*' ] + tags: [ 'v*.*.*' ] # Triggered by Release Please tags pull_request: branches: [ main ] release: - types: [ published ] + types: [ published ] # Triggered when Release Please creates a release workflow_dispatch: inputs: push: diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 00000000..b5973685 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,71 @@ +name: Release Please + +on: + push: + branches: [main] + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + version: ${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }} + steps: + - name: Run Release Please + uses: googleapis/release-please-action@v4 + id: release + with: + token: ${{ secrets.GITHUB_TOKEN }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + - name: Output Release Info + if: ${{ steps.release.outputs.release_created }} + run: | + echo "## Release Created! " >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Tag:** ${{ steps.release.outputs.tag_name }}" >> $GITHUB_STEP_SUMMARY + echo "**Version:** ${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Docker images will be built and tagged with this version." >> $GITHUB_STEP_SUMMARY + + # Sync version to package.json files after release + sync-versions: + needs: release-please + if: ${{ needs.release-please.outputs.release_created }} + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: main + + - name: Update package.json versions + run: | + VERSION="${{ needs.release-please.outputs.version }}" + echo "Updating package.json files to version $VERSION" + + # Update backend package.json + cd backend + npm version $VERSION --no-git-tag-version --allow-same-version + cd .. + + # Update frontend package.json + cd frontend + npm version $VERSION --no-git-tag-version --allow-same-version + cd .. + + - name: Commit version updates + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add backend/package.json frontend/package.json + git diff --staged --quiet || git commit -m "chore: sync package.json versions to ${{ needs.release-please.outputs.version }}" + git push diff --git a/.gitignore b/.gitignore index 86d5dea6..fd3844d9 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,13 @@ certbot/ AGENTS.md CLAUDE.md +# Working/planning documents (not for release) +BUGS_AND_FEATURES.md +frontend/TEST_PLAN.md +docs/REFACTORING_PLAN.md +docs/test-*.md +docs/feature-*.md + # Local artifacts from browser tooling .playwright-mcp/ diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..7e437931 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.1.15" +} diff --git a/BUGS_AND_FEATURES.md b/BUGS_AND_FEATURES.md deleted file mode 100644 index 76696a34..00000000 --- a/BUGS_AND_FEATURES.md +++ /dev/null @@ -1,1309 +0,0 @@ -# PicPeak Bug Report & Feature Requests - -> **Generated:** January 2, 2026 -> **Version:** Frontend v1.1.15 / Backend v1.1.15 -> **Priority Levels:** P0 (Critical), P1 (High), P2 (Medium), P3 (Low) - ---- - -## Table of Contents - -1. [BUG-001: Logo Customization Section Formatting Issues](#bug-001-logo-customization-section-formatting-issues) -2. [BUG-002: Favicon Upload Section Missing Preview](#bug-002-favicon-upload-section-missing-preview) -3. [FEATURE-003: Custom CSS Instructions Panel](#feature-003-custom-css-instructions-panel) -4. [FEATURE-004: Custom CSS Integration with Theme Presets & Gallery Layouts](#feature-004-custom-css-integration-with-theme-presets--gallery-layouts) -5. [BUG-005: Custom Layouts Not Showing in Event Creation](#bug-005-custom-layouts-not-showing-in-event-creation) -6. [FEATURE-006: Apple Liquid Glass Design Custom CSS Templates](#feature-006-apple-liquid-glass-design-custom-css-templates) -7. [BUG-007: Typography & Style Section Row Formatting](#bug-007-typography--style-section-row-formatting) - ---- - -## BUG-001: Logo Customization Section Formatting Issues - -### Priority: P1 (High) - -### Location -- **Page:** `/admin/branding` -- **Section:** Company Information → Logo Customization - -### Current Behavior -1. The "Upload Logo" button appears as a plain text link instead of a proper button -2. No preview thumbnail is displayed when a logo has been uploaded -3. The layout doesn't show the currently uploaded logo image -4. Button styling is inconsistent with other upload buttons (e.g., Favicon) - -### Expected Behavior -1. "Upload Logo" should be a styled button matching the "Upload Favicon" button style -2. When a logo is uploaded, a preview thumbnail should be displayed (similar to how other image uploads work) -3. A "Remove Logo" or "Change Logo" option should appear when a logo exists -4. The section should show: - - Current logo preview (if uploaded) - - Upload/Change button - - Remove button (if logo exists) - - File size/dimension info - -### Technical Analysis - -**Affected Component:** `frontend/src/pages/admin/BrandingPage.tsx` or similar - -**Proposed Solution:** -```tsx -// Logo preview section structure -
- {currentLogo ? ( -
- Current Logo -
- - -
-
- ) : ( - - )} -

Recommended size: 200x60px, PNG or JPEG

-
-``` - -### Screenshots -- See: `.playwright-mcp/branding-logo-section.png` - ---- - -## BUG-002: Favicon Upload Section Missing Preview - -### Priority: P2 (Medium) - -### Location -- **Page:** `/admin/branding` -- **Section:** Company Information → Favicon - -### Current Behavior -1. Only shows "Upload Favicon" button -2. No preview of currently uploaded favicon -3. No way to see or remove existing favicon - -### Expected Behavior -1. Display current favicon preview (32x32px thumbnail) -2. Show "Change Favicon" when one exists -3. Provide "Remove Favicon" option -4. Display file info (name, size) when uploaded - -### Technical Analysis - -**Proposed Solution:** -```tsx -
- -
- {currentFavicon ? ( - <> - Current Favicon - - - - ) : ( - - )} -
-

PNG or ICO format, recommended size: 32x32px

-
-``` - ---- - -## FEATURE-003: Custom CSS Instructions Panel - -### Priority: P2 (Medium) - -### Location -- **Page:** `/admin/settings` → Custom CSS tab - -### Current Behavior -- Simple textarea with minimal guidance -- Only shows: "Advanced: Add custom CSS to further customize the appearance" -- CSS variables are documented in comments within the template - -### Required Enhancement -Add a collapsible instruction panel with comprehensive documentation: - -### Proposed Implementation - -```tsx - - - - CSS Documentation & Guide - - - - {/* Documentation content */} - - -``` - -### Documentation Content Structure - -#### 1. Available CSS Custom Properties -```css -/* Color Variables */ ---gallery-bg: #ffffff; /* Main background color */ ---gallery-bg-secondary: #f5f5f5; /* Secondary/card background */ ---gallery-text: #171717; /* Primary text color */ ---gallery-text-muted: #737373; /* Muted/secondary text */ ---gallery-accent: #22c55e; /* Accent/highlight color */ ---gallery-accent-hover: #16a34a; /* Accent hover state */ ---gallery-border: #e5e5e5; /* Border color */ - -/* Spacing & Layout */ ---gallery-spacing: 16px; /* Base spacing unit */ ---gallery-radius: 8px; /* Border radius */ ---gallery-shadow: 0 1px 3px rgba(0,0,0,0.1); /* Box shadow */ - -/* Typography */ ---gallery-font-body: 'Inter', sans-serif; ---gallery-font-heading: 'Inter', sans-serif; ---gallery-font-size-base: 16px; -``` - -#### 2. Targetable CSS Classes -```css -/* Page Structure */ -.gallery-page { } /* Root gallery container */ -.gallery-header { } /* Header section */ -.gallery-content { } /* Main content area */ -.gallery-footer { } /* Footer section */ - -/* Photo Grid */ -.photo-grid { } /* Grid container */ -.photo-card { } /* Individual photo card */ -.photo-card img { } /* Photo image */ -.photo-card-overlay { } /* Hover overlay */ -.photo-card-info { } /* Photo metadata */ - -/* Lightbox */ -.lightbox-overlay { } /* Lightbox background */ -.lightbox-content { } /* Lightbox container */ -.lightbox-image { } /* Lightbox image */ -.lightbox-controls { } /* Navigation controls */ - -/* Buttons & Interactions */ -.gallery-btn { } /* Primary buttons */ -.gallery-btn-secondary { } /* Secondary buttons */ -.gallery-link { } /* Links */ - -/* Categories */ -.category-filter { } /* Category filter bar */ -.category-pill { } /* Category pill/tag */ - -/* Masonry Layout */ -.masonry-grid { } /* Masonry container */ -.masonry-item { } /* Masonry item */ - -/* Carousel Layout */ -.carousel-container { } /* Carousel wrapper */ -.carousel-slide { } /* Individual slide */ -.carousel-nav { } /* Navigation arrows */ -.carousel-dots { } /* Pagination dots */ - -/* Timeline Layout */ -.timeline-container { } /* Timeline wrapper */ -.timeline-item { } /* Timeline entry */ -.timeline-date { } /* Date marker */ - -/* Hero Layout */ -.hero-section { } /* Hero image area */ -.hero-image { } /* Featured image */ -.hero-content { } /* Hero text content */ -``` - -#### 3. Layout-Specific Customization -```css -/* Grid Layout Customization */ -.gallery-page[data-layout="grid"] .photo-grid { - grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); - gap: var(--gallery-spacing); -} - -/* Masonry Layout Customization */ -.gallery-page[data-layout="masonry"] .masonry-grid { - column-count: 4; - column-gap: var(--gallery-spacing); -} - -/* Carousel Layout Customization */ -.gallery-page[data-layout="carousel"] .carousel-container { - height: 80vh; -} - -/* Timeline Layout Customization */ -.gallery-page[data-layout="timeline"] .timeline-container { - max-width: 1200px; - margin: 0 auto; -} -``` - -#### 4. Responsive Breakpoints -```css -/* Mobile: < 640px */ -@media (max-width: 639px) { } - -/* Tablet: 640px - 1023px */ -@media (min-width: 640px) and (max-width: 1023px) { } - -/* Desktop: >= 1024px */ -@media (min-width: 1024px) { } -``` - -#### 5. Animation Classes -```css -/* Available animations */ -.animate-fade { } /* Fade in */ -.animate-scale { } /* Scale up */ -.animate-slide { } /* Slide in */ -.animate-none { } /* No animation */ -``` - ---- - -## FEATURE-004: Custom CSS Integration with Theme Presets & Gallery Layouts - -### Priority: P0 (Critical) - -### Location -- **Page:** `/admin/settings` → Custom CSS tab -- **Page:** `/admin/branding` → Gallery Theme section -- **Page:** `/admin/events/new` → Theme & Style section - -### Current Behavior -- Custom CSS templates exist but are isolated -- Theme presets don't integrate with custom CSS -- Users cannot define custom theme presets -- Users cannot define custom gallery layouts -- No way to use CSS variables from theme presets - -### Required Features - -#### 4.1 Custom Theme Preset Creation - -Users should be able to create custom theme presets that appear alongside built-in presets: - -```typescript -interface CustomThemePreset { - id: string; - name: string; - description: string; - thumbnail?: string; - // Inherits from base preset or standalone - basePreset?: 'classic-grid' | 'elegant-wedding' | 'modern-masonry' | null; - // CSS template reference - cssTemplateId: 1 | 2 | 3; - // Override variables - variables: { - primaryColor: string; - accentColor: string; - backgroundColor: string; - textColor: string; - fontBody: string; - fontHeading: string; - borderRadius: string; - spacing: string; - }; - // Layout settings - layout: 'grid' | 'masonry' | 'carousel' | 'timeline' | 'hero' | 'mosaic' | 'custom'; - layoutSettings: { - columns: { mobile: number; tablet: number; desktop: number }; - spacing: 'tight' | 'normal' | 'relaxed'; - animation: 'none' | 'fade' | 'scale' | 'slide'; - }; -} -``` - -#### 4.2 Custom Gallery Layout Definition - -Allow users to define custom layouts via CSS: - -```css -/* Custom Layout 1: Magazine Style */ -.gallery-page[data-layout="custom-1"] .photo-grid { - display: grid; - grid-template-columns: 2fr 1fr 1fr; - grid-template-rows: auto; - gap: 8px; -} - -.gallery-page[data-layout="custom-1"] .photo-card:first-child { - grid-row: span 2; -} - -/* Custom Layout 2: Polaroid Style */ -.gallery-page[data-layout="custom-2"] .photo-card { - background: white; - padding: 12px 12px 40px 12px; - box-shadow: 0 4px 6px rgba(0,0,0,0.1); - transform: rotate(var(--rotation, 0deg)); -} - -/* Custom Layout 3: Filmstrip */ -.gallery-page[data-layout="custom-3"] .photo-grid { - display: flex; - overflow-x: auto; - gap: 4px; - padding: 20px; - background: #1a1a1a; -} -``` - -#### 4.3 Variable Inheritance System - -Custom CSS should be able to reference theme preset variables: - -```css -/* Access theme preset variables */ -.gallery-page { - /* These come from the selected theme preset */ - background: var(--theme-bg, var(--gallery-bg)); - color: var(--theme-text, var(--gallery-text)); - - /* Override specific elements */ - --gallery-accent: var(--theme-accent); -} - -/* Conditional styling based on preset */ -.gallery-page[data-preset="elegant-wedding"] { - /* Wedding-specific overrides */ -} - -.gallery-page[data-preset="custom"] { - /* Full custom styling */ -} -``` - -### Database Schema Addition - -```sql --- Custom theme presets table -CREATE TABLE custom_theme_presets ( - id SERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL, - description TEXT, - thumbnail_url VARCHAR(500), - base_preset VARCHAR(50), - css_template_id INTEGER REFERENCES css_templates(id), - variables JSONB NOT NULL DEFAULT '{}', - layout VARCHAR(50) NOT NULL DEFAULT 'grid', - layout_settings JSONB NOT NULL DEFAULT '{}', - is_enabled BOOLEAN DEFAULT true, - sort_order INTEGER DEFAULT 0, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Link events to custom presets -ALTER TABLE events ADD COLUMN custom_preset_id INTEGER REFERENCES custom_theme_presets(id); -``` - -### UI Components Required - -1. **Theme Preset Builder** - Visual editor for creating custom presets -2. **Layout Designer** - CSS grid/flexbox visual editor for custom layouts -3. **Variable Picker** - Color/font/spacing picker that outputs CSS variables -4. **Preview Synchronization** - Real-time preview of custom themes - ---- - -## BUG-005: Custom Layouts Not Showing in Event Creation - -### Priority: P1 (High) - -### Location -- **Page:** `/admin/events/new` → Theme & Style → Gallery Layout - -### Current Behavior -- Only shows 6 default layouts: grid, masonry, carousel, timeline, hero, mosaic -- Custom CSS templates with enabled custom layouts don't appear -- No "Custom 1", "Custom 2", "Custom 3" options visible - -### Expected Behavior -When Custom CSS templates are enabled in Settings: -1. Additional layout options should appear: "Custom 1", "Custom 2", "Custom 3" -2. Only show custom layouts that have content (non-empty CSS) -3. Display the template name as the layout name -4. Show a preview thumbnail if available - -### Technical Analysis - -**Current Gallery Layout Options:** -```typescript -const GALLERY_LAYOUTS = [ - { value: 'grid', label: 'Grid', description: 'Classic grid layout...' }, - { value: 'masonry', label: 'Masonry', description: 'Pinterest-style...' }, - { value: 'carousel', label: 'Carousel', description: 'Full-screen slideshow...' }, - { value: 'timeline', label: 'Timeline', description: 'Photos organized by date' }, - { value: 'hero', label: 'Hero', description: 'Featured image with grid...' }, - { value: 'mosaic', label: 'Mosaic', description: 'Artistic layout...' }, -]; -``` - -**Required Addition:** -```typescript -// Fetch enabled custom templates -const { data: customTemplates } = useQuery({ - queryKey: ['customCssTemplates'], - queryFn: () => settingsService.getCustomCssTemplates(), -}); - -// Filter to only enabled templates with content -const enabledCustomLayouts = customTemplates - ?.filter(t => t.enabled && t.content?.trim()) - .map((t, index) => ({ - value: `custom-${index + 1}`, - label: t.name || `Custom ${index + 1}`, - description: 'Custom CSS layout template', - isCustom: true, - })); - -const allLayouts = [...GALLERY_LAYOUTS, ...(enabledCustomLayouts || [])]; -``` - -### API Endpoint Required - -```typescript -// GET /api/admin/settings/custom-css-templates -interface CustomCssTemplate { - id: number; - name: string; - enabled: boolean; - content: string; - hasLayoutDefinition: boolean; // true if contains custom layout CSS -} -``` - ---- - -## FEATURE-006: Apple Liquid Glass Design Custom CSS Templates - -### Priority: P2 (Medium) - -### Overview -Create 2 premium custom CSS templates implementing Apple's "Liquid Glass" design language introduced at WWDC 2025. This design features translucent surfaces, dynamic light refraction effects, and sophisticated blur treatments. - -### References -- [Apple's Liquid Glass UI design + CSS guide](https://dev.to/gruszdev/apples-liquid-glass-revolution-how-glassmorphism-is-shaping-ui-design-in-2025-with-css-code-1221) -- [Recreating Apple's Liquid Glass Effect with Pure CSS](https://dev.to/kevinbism/recreating-apples-liquid-glass-effect-with-pure-css-3gpl) -- [CSS-Tricks: Getting Clarity on Apple's Liquid Glass](https://css-tricks.com/getting-clarity-on-apples-liquid-glass/) -- [Liquid Glass CSS Generator](https://liquidglassgen.com/) - -### Template 1: "Liquid Glass Light" - -A light-themed glassmorphism design with subtle transparency and soft shadows. - -```css -/* - * PicPeak Custom CSS Template: Liquid Glass Light - * Inspired by Apple's iOS 26 Liquid Glass Design Language - * - * Features: - * - Translucent frosted glass surfaces - * - Dynamic light refraction effects - * - Subtle specular highlights - * - Soft depth shadows - */ - -/* ===== Base Theme Variables ===== */ -.gallery-page { - --glass-bg: rgba(255, 255, 255, 0.7); - --glass-bg-elevated: rgba(255, 255, 255, 0.85); - --glass-border: rgba(255, 255, 255, 0.5); - --glass-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); - --glass-blur: 20px; - --glass-saturation: 180%; - - --gallery-bg: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); - --gallery-text: #1a1a2e; - --gallery-text-muted: rgba(26, 26, 46, 0.7); - --gallery-accent: #667eea; - --gallery-accent-hover: #764ba2; - --gallery-radius: 24px; - --gallery-spacing: 20px; -} - -/* ===== Page Background ===== */ -.gallery-page { - background: var(--gallery-bg); - min-height: 100vh; - position: relative; -} - -/* Animated gradient background */ -.gallery-page::before { - content: ''; - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: - radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.3) 0%, transparent 50%), - radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.2) 0%, transparent 40%); - pointer-events: none; - z-index: 0; -} - -/* ===== Glass Card Base ===== */ -.glass-surface { - background: var(--glass-bg); - backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); - -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); - border: 1px solid var(--glass-border); - border-radius: var(--gallery-radius); - box-shadow: - var(--glass-shadow), - inset 0 1px 1px rgba(255, 255, 255, 0.8), - inset 0 -1px 1px rgba(0, 0, 0, 0.05); -} - -/* Liquid shine effect */ -.glass-surface::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 50%; - background: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.4) 0%, - rgba(255, 255, 255, 0.1) 50%, - transparent 100% - ); - border-radius: var(--gallery-radius) var(--gallery-radius) 0 0; - pointer-events: none; -} - -/* ===== Gallery Header ===== */ -.gallery-header { - background: var(--glass-bg-elevated); - backdrop-filter: blur(30px) saturate(200%); - -webkit-backdrop-filter: blur(30px) saturate(200%); - border-bottom: 1px solid var(--glass-border); - padding: calc(var(--gallery-spacing) * 1.5); - position: sticky; - top: 0; - z-index: 100; -} - -.gallery-title { - color: var(--gallery-text); - font-weight: 700; - font-size: 1.75rem; - letter-spacing: -0.02em; - text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5); -} - -/* ===== Photo Grid ===== */ -.photo-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: var(--gallery-spacing); - padding: calc(var(--gallery-spacing) * 2); - position: relative; - z-index: 1; -} - -/* ===== Photo Cards - Glass Style ===== */ -.photo-card { - position: relative; - background: var(--glass-bg); - backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); - -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); - border: 1px solid var(--glass-border); - border-radius: var(--gallery-radius); - overflow: hidden; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: - 0 4px 16px rgba(0, 0, 0, 0.1), - inset 0 1px 1px rgba(255, 255, 255, 0.6); -} - -.photo-card::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 40%; - background: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.3) 0%, - transparent 100% - ); - pointer-events: none; - z-index: 1; - border-radius: var(--gallery-radius) var(--gallery-radius) 0 0; -} - -.photo-card:hover { - transform: translateY(-8px) scale(1.02); - box-shadow: - 0 20px 40px rgba(102, 126, 234, 0.3), - 0 8px 16px rgba(0, 0, 0, 0.1), - inset 0 1px 1px rgba(255, 255, 255, 0.8); -} - -.photo-card img { - width: 100%; - height: 240px; - object-fit: cover; - transition: transform 0.4s ease; -} - -.photo-card:hover img { - transform: scale(1.05); -} - -.photo-card-info { - padding: var(--gallery-spacing); - background: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.1) 0%, - rgba(255, 255, 255, 0.3) 100% - ); -} - -/* ===== Buttons - Glass Style ===== */ -.gallery-btn { - background: var(--glass-bg); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border: 1px solid var(--glass-border); - border-radius: calc(var(--gallery-radius) / 2); - padding: 12px 24px; - color: var(--gallery-text); - font-weight: 600; - cursor: pointer; - transition: all 0.3s ease; - position: relative; - overflow: hidden; -} - -.gallery-btn::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 50%; - background: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.4) 0%, - transparent 100% - ); -} - -.gallery-btn:hover { - background: var(--glass-bg-elevated); - transform: translateY(-2px); - box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3); -} - -.gallery-btn-primary { - background: linear-gradient(135deg, var(--gallery-accent) 0%, var(--gallery-accent-hover) 100%); - color: white; - border: none; -} - -/* ===== Lightbox - Glass Style ===== */ -.lightbox-overlay { - background: rgba(26, 26, 46, 0.8); - backdrop-filter: blur(40px); - -webkit-backdrop-filter: blur(40px); -} - -.lightbox-content { - background: var(--glass-bg); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border: 1px solid var(--glass-border); - border-radius: var(--gallery-radius); - box-shadow: 0 24px 48px rgba(0, 0, 0, 0.2); -} - -/* ===== Category Pills ===== */ -.category-pill { - background: var(--glass-bg); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border: 1px solid var(--glass-border); - border-radius: 9999px; - padding: 8px 20px; - font-size: 0.875rem; - font-weight: 500; - color: var(--gallery-text); - transition: all 0.3s ease; -} - -.category-pill:hover, -.category-pill.active { - background: var(--gallery-accent); - color: white; - border-color: var(--gallery-accent); -} - -/* ===== Responsive ===== */ -@media (max-width: 768px) { - .gallery-page { - --gallery-radius: 16px; - --gallery-spacing: 12px; - --glass-blur: 16px; - } - - .photo-grid { - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - } - - .photo-card img { - height: 180px; - } -} - -/* ===== Accessibility: Reduce Motion ===== */ -@media (prefers-reduced-motion: reduce) { - .photo-card, - .gallery-btn { - transition: none; - } - - .photo-card:hover { - transform: none; - } -} - -/* ===== Accessibility: Reduce Transparency ===== */ -@media (prefers-reduced-transparency: reduce) { - .glass-surface, - .photo-card, - .gallery-btn { - backdrop-filter: none; - -webkit-backdrop-filter: none; - background: rgba(255, 255, 255, 0.95); - } -} -``` - -### Template 2: "Liquid Glass Dark" - -A dark-themed glassmorphism design with deep translucency and neon accents. - -```css -/* - * PicPeak Custom CSS Template: Liquid Glass Dark - * Inspired by Apple's iOS 26 Liquid Glass Design Language - * - * Features: - * - Deep translucent dark surfaces - * - Neon accent highlights - * - Dramatic glass reflections - * - Subtle animated gradients - */ - -/* ===== Base Theme Variables ===== */ -.gallery-page { - --glass-bg: rgba(15, 15, 35, 0.7); - --glass-bg-elevated: rgba(25, 25, 55, 0.85); - --glass-border: rgba(255, 255, 255, 0.1); - --glass-border-highlight: rgba(255, 255, 255, 0.2); - --glass-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); - --glass-blur: 24px; - --glass-saturation: 150%; - - --gallery-bg: #0a0a1a; - --gallery-text: #f0f0f5; - --gallery-text-muted: rgba(240, 240, 245, 0.6); - --gallery-accent: #00d4ff; - --gallery-accent-secondary: #ff00e5; - --gallery-accent-hover: #00ffea; - --gallery-radius: 20px; - --gallery-spacing: 20px; - - /* Neon glow variables */ - --neon-glow: 0 0 20px rgba(0, 212, 255, 0.5), 0 0 40px rgba(0, 212, 255, 0.2); - --neon-glow-secondary: 0 0 20px rgba(255, 0, 229, 0.5), 0 0 40px rgba(255, 0, 229, 0.2); -} - -/* ===== Page Background ===== */ -.gallery-page { - background: var(--gallery-bg); - min-height: 100vh; - position: relative; - overflow-x: hidden; -} - -/* Animated mesh gradient background */ -.gallery-page::before { - content: ''; - position: fixed; - top: -50%; - left: -50%; - right: -50%; - bottom: -50%; - background: - radial-gradient(circle at 30% 20%, rgba(0, 212, 255, 0.15) 0%, transparent 40%), - radial-gradient(circle at 70% 80%, rgba(255, 0, 229, 0.1) 0%, transparent 40%), - radial-gradient(circle at 50% 50%, rgba(100, 100, 255, 0.05) 0%, transparent 60%); - animation: gradientShift 20s ease-in-out infinite; - pointer-events: none; - z-index: 0; -} - -@keyframes gradientShift { - 0%, 100% { transform: translate(0, 0) rotate(0deg); } - 25% { transform: translate(2%, 2%) rotate(1deg); } - 50% { transform: translate(-1%, 3%) rotate(-1deg); } - 75% { transform: translate(3%, -2%) rotate(2deg); } -} - -/* ===== Gallery Header ===== */ -.gallery-header { - background: var(--glass-bg-elevated); - backdrop-filter: blur(30px) saturate(var(--glass-saturation)); - -webkit-backdrop-filter: blur(30px) saturate(var(--glass-saturation)); - border-bottom: 1px solid var(--glass-border-highlight); - padding: calc(var(--gallery-spacing) * 1.5); - position: sticky; - top: 0; - z-index: 100; - box-shadow: - 0 4px 24px rgba(0, 0, 0, 0.3), - inset 0 1px 0 rgba(255, 255, 255, 0.1); -} - -.gallery-title { - color: var(--gallery-text); - font-weight: 700; - font-size: 1.75rem; - letter-spacing: -0.02em; - background: linear-gradient(135deg, var(--gallery-text) 0%, var(--gallery-accent) 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -/* ===== Photo Grid ===== */ -.photo-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: var(--gallery-spacing); - padding: calc(var(--gallery-spacing) * 2); - position: relative; - z-index: 1; -} - -/* ===== Photo Cards - Dark Glass Style ===== */ -.photo-card { - position: relative; - background: var(--glass-bg); - backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); - -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); - border: 1px solid var(--glass-border); - border-radius: var(--gallery-radius); - overflow: hidden; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: - 0 4px 24px rgba(0, 0, 0, 0.3), - inset 0 1px 0 rgba(255, 255, 255, 0.05); -} - -/* Top highlight reflection */ -.photo-card::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 1px; - background: linear-gradient( - 90deg, - transparent 0%, - rgba(255, 255, 255, 0.3) 50%, - transparent 100% - ); - z-index: 2; -} - -/* Inner glow effect */ -.photo-card::after { - content: ''; - position: absolute; - inset: 0; - border-radius: var(--gallery-radius); - padding: 1px; - background: linear-gradient( - 135deg, - rgba(0, 212, 255, 0) 0%, - rgba(0, 212, 255, 0) 40%, - rgba(0, 212, 255, 0.1) 100% - ); - -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0); - -webkit-mask-composite: xor; - mask-composite: exclude; - pointer-events: none; - opacity: 0; - transition: opacity 0.4s ease; -} - -.photo-card:hover { - transform: translateY(-8px) scale(1.02); - border-color: var(--glass-border-highlight); - box-shadow: - 0 24px 48px rgba(0, 0, 0, 0.4), - 0 0 0 1px rgba(0, 212, 255, 0.2), - var(--neon-glow); -} - -.photo-card:hover::after { - opacity: 1; -} - -.photo-card img { - width: 100%; - height: 240px; - object-fit: cover; - transition: transform 0.4s ease, filter 0.4s ease; - filter: brightness(0.9); -} - -.photo-card:hover img { - transform: scale(1.05); - filter: brightness(1); -} - -.photo-card-info { - padding: var(--gallery-spacing); - background: linear-gradient( - 180deg, - rgba(0, 0, 0, 0.2) 0%, - rgba(0, 0, 0, 0.4) 100% - ); - color: var(--gallery-text); -} - -.photo-card-info p { - color: var(--gallery-text-muted); - font-size: 0.875rem; -} - -/* ===== Buttons - Neon Glass Style ===== */ -.gallery-btn { - background: var(--glass-bg); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border: 1px solid var(--glass-border); - border-radius: calc(var(--gallery-radius) / 2); - padding: 12px 24px; - color: var(--gallery-text); - font-weight: 600; - cursor: pointer; - transition: all 0.3s ease; - position: relative; -} - -.gallery-btn:hover { - border-color: var(--gallery-accent); - box-shadow: var(--neon-glow); - color: var(--gallery-accent); -} - -.gallery-btn-primary { - background: linear-gradient(135deg, var(--gallery-accent) 0%, var(--gallery-accent-secondary) 100%); - color: white; - border: none; - box-shadow: var(--neon-glow); -} - -.gallery-btn-primary:hover { - box-shadow: - 0 0 30px rgba(0, 212, 255, 0.6), - 0 0 60px rgba(0, 212, 255, 0.3), - 0 0 90px rgba(255, 0, 229, 0.2); - transform: translateY(-2px); -} - -/* ===== Lightbox - Dark Glass ===== */ -.lightbox-overlay { - background: rgba(5, 5, 15, 0.9); - backdrop-filter: blur(40px); - -webkit-backdrop-filter: blur(40px); -} - -.lightbox-content { - background: var(--glass-bg-elevated); - backdrop-filter: blur(24px); - -webkit-backdrop-filter: blur(24px); - border: 1px solid var(--glass-border-highlight); - border-radius: var(--gallery-radius); - box-shadow: - 0 24px 80px rgba(0, 0, 0, 0.5), - var(--neon-glow); -} - -/* ===== Category Pills ===== */ -.category-pill { - background: var(--glass-bg); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border: 1px solid var(--glass-border); - border-radius: 9999px; - padding: 8px 20px; - font-size: 0.875rem; - font-weight: 500; - color: var(--gallery-text-muted); - transition: all 0.3s ease; -} - -.category-pill:hover { - border-color: var(--gallery-accent); - color: var(--gallery-accent); - box-shadow: var(--neon-glow); -} - -.category-pill.active { - background: linear-gradient(135deg, var(--gallery-accent) 0%, var(--gallery-accent-secondary) 100%); - color: white; - border-color: transparent; - box-shadow: var(--neon-glow); -} - -/* ===== Scrollbar Styling ===== */ -.gallery-page ::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -.gallery-page ::-webkit-scrollbar-track { - background: var(--glass-bg); - border-radius: 4px; -} - -.gallery-page ::-webkit-scrollbar-thumb { - background: linear-gradient(180deg, var(--gallery-accent) 0%, var(--gallery-accent-secondary) 100%); - border-radius: 4px; -} - -/* ===== Responsive ===== */ -@media (max-width: 768px) { - .gallery-page { - --gallery-radius: 16px; - --gallery-spacing: 12px; - --glass-blur: 16px; - } - - .photo-grid { - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); - } - - .photo-card img { - height: 180px; - } - - /* Reduce animation complexity on mobile */ - .gallery-page::before { - animation: none; - } -} - -/* ===== Accessibility: Reduce Motion ===== */ -@media (prefers-reduced-motion: reduce) { - .gallery-page::before { - animation: none; - } - - .photo-card, - .gallery-btn { - transition: none; - } - - .photo-card:hover { - transform: none; - } -} - -/* ===== Accessibility: Reduce Transparency ===== */ -@media (prefers-reduced-transparency: reduce) { - .photo-card, - .gallery-btn, - .gallery-header { - backdrop-filter: none; - -webkit-backdrop-filter: none; - } - - .gallery-page { - --glass-bg: rgba(20, 20, 40, 0.98); - --glass-bg-elevated: rgba(30, 30, 60, 0.98); - } -} -``` - ---- - -## BUG-007: Typography & Style Section Row Formatting - -### Priority: P2 (Medium) - -### Location -- **Page:** `/admin/events/new` → Theme & Style → Typography & Style section -- **Page:** `/admin/branding` → Gallery Theme → Typography & Style section - -### Current Behavior -The second row of Typography & Style section contains 4 dropdown fields crammed into one row: -- Font Size -- Border Radius -- Shadow Style -- Background - -These fields are truncated and show abbreviated text (e.g., "Nor", "Lar", "Sub") because they don't have enough horizontal space. - -### Expected Behavior -Split the 4 fields into 2 rows of 2 fields each: -- **Row 1:** Font Size, Border Radius -- **Row 2:** Shadow Style, Background - -### Screenshots -- See: `.playwright-mcp/typography-style-visible.png` - -### Technical Analysis - -**Current Code Structure (likely):** -```tsx -
- - - - -
-``` - -**Proposed Fix:** -```tsx -{/* Row 1: Font Size & Border Radius */} -
- - - - - - -
- -{/* Row 2: Shadow Style & Background */} -
- - - - - - -
-``` - -### Files to Modify -1. `frontend/src/pages/admin/EventCreatePage.tsx` (or similar) -2. `frontend/src/pages/admin/BrandingPage.tsx` -3. `frontend/src/components/admin/ThemeCustomizer.tsx` (if shared component) - ---- - -## Implementation Priority Order - -| Priority | Issue | Effort | Impact | -|----------|-------|--------|--------| -| P0 | FEATURE-004: Custom CSS + Theme Integration | High | Critical for customization | -| P1 | BUG-001: Logo Upload Formatting | Medium | User experience | -| P1 | BUG-005: Custom Layouts in Events | Medium | Feature completeness | -| P2 | BUG-002: Favicon Preview | Low | User experience | -| P2 | BUG-007: Typography Row Layout | Low | UI polish | -| P2 | FEATURE-003: CSS Instructions | Medium | User guidance | -| P2 | FEATURE-006: Liquid Glass Templates | Medium | Premium feature | - ---- - -## Testing Checklist - -### BUG-001 & BUG-002 -- [ ] Logo upload displays preview after upload -- [ ] Logo can be changed/removed -- [ ] Favicon upload displays preview -- [ ] Favicon can be changed/removed -- [ ] Button styling matches design system - -### FEATURE-003 -- [ ] Collapsible instructions panel works -- [ ] All CSS variables documented -- [ ] All CSS classes documented -- [ ] Code examples copy correctly - -### FEATURE-004 -- [ ] Custom theme presets can be created -- [ ] Custom presets appear in event creation -- [ ] CSS variables from presets work in custom CSS -- [ ] Custom layouts render correctly - -### BUG-005 -- [ ] Enabled custom templates show in layout selector -- [ ] Only templates with content appear -- [ ] Custom layouts apply correctly to galleries - -### FEATURE-006 -- [ ] Liquid Glass Light template renders correctly -- [ ] Liquid Glass Dark template renders correctly -- [ ] Animations respect prefers-reduced-motion -- [ ] Transparency respects prefers-reduced-transparency -- [ ] Mobile responsiveness works - -### BUG-007 -- [ ] Typography section shows 2 items per row -- [ ] Dropdown labels fully visible -- [ ] Responsive behavior maintained - ---- - -*Document generated for PicPeak development team* diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..8fe849c1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,63 @@ +# Changelog + +All notable changes to PicPeak will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.2.0](https://github.com/the-luap/picpeak/compare/v1.1.15...v1.2.0) (2026-01-03) + +### Features + +* **Event Rename**: Safe event renaming with automatic slug updates, old URL redirects via `slug_redirects` table, and optional email notifications to clients +* **Optional Event Fields**: Make customer name, email, and admin email fields optional via admin settings with "(optional)" labels in forms +* **Photo Filtering**: Filter photos by rating, likes, favorites, and comments with a new PhotoFilterPanel component +* **Photo Export**: Export filtered photo selections as ZIP, generate Capture One/Lightroom-compatible XMP sidecar files, or export metadata lists +* **Custom CSS Templates**: 3 customizable CSS template slots with live preview, XSS-safe sanitization, and per-event template assignment +* **Apple Liquid Glass Theme**: Starter CSS template inspired by iOS 26 / macOS Tahoe Liquid Glass design with glass morphism effects, Apple SF Pro fonts, and responsive layout +* **Liquid Glass Dark Theme**: Neon-accented dark glass theme with animated gradient backgrounds +* **Image Security Settings**: Per-event download protection with configurable protection levels (basic, standard, enhanced, maximum), canvas rendering, DevTools detection, and right-click prevention +* **Automated Releases**: Release Please integration for automatic versioning, changelog generation, and GitHub releases that trigger Docker image builds + +### Bug Fixes + +* **Date Parsing**: Fix event date formatting in slugs (now uses YYYY-MM-DD format correctly) +* **Search Placeholder**: Fix search field placeholder visibility in glass-styled sidebar +* **Vite Proxy**: Fix Vite dev server proxy port configuration +* **Photo Export Button**: Fix export button staying disabled when photos are selected +* **Boolean Parsing**: Fix boolean parsing in publicSettings.js for optional fields +* **Translation Keys**: Add missing `common.optional` translation key in locales + +### Security + +* Fix critical vulnerabilities and harden application security +* Add CSS sanitizer utility blocking XSS vectors in custom templates +* Implement secure gallery CSS endpoint for template delivery + +### Code Refactoring + +* Add Photo and Settings service layers for better code organization +* Phase 1 code consolidation with service layer architecture +* Modular settings page with feature-based tab components +* Create photoFilterBuilder utility for query construction +* Add eventRenameService for safe event operations + +### Documentation + +* Add comprehensive REFACTORING_PLAN.md for codebase improvement roadmap +* Update README roadmap with implemented features (Download Protection, Gallery Templates, Filtering & Export) +* Add test specification documents for all new features + +### Database Migrations + +* `049_add_slug_redirects.js` - Store old slugs for URL redirects after rename +* `050_add_optional_event_fields_settings.js` - Settings for optional form fields +* `051_add_photo_filter_indexes.js` - Performance indexes for photo filtering +* `052_add_css_templates.js` - CSS template storage with 3 slots +* `053_add_liquid_glass_templates.js` - Apple Liquid Glass and Dark theme starter templates + +--- + +## [1.1.15] - Previous Release + +Initial stable release with core functionality. diff --git a/README.md b/README.md index 15875af4..4e802ae9 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,6 @@ These features are currently in beta testing and may have limited functionality | Feature | Description | Status | |---------|-------------|--------| -| **Download Protection** | Advanced image protection system with canvas rendering, invisible watermarking, and right-click prevention to protect your photos from unauthorized downloads | 🧪 Beta | | **Simple Deployment Script** | One-click deployment script for quick server setup with automated configuration and dependency installation | 🧪 Beta | ### 📋 Future Enhancements @@ -244,12 +243,13 @@ These features are currently in beta testing and may have limited functionality |---------|-------------|----------|---------| | **Backup & Restore** | Comprehensive backup system with S3/MinIO support, automated scheduling, and safe restore functionality | High | ✅ Implemented | | **External Media Library (Reference Mode)** | Use an external folder library as a read‑only source with import and on‑demand thumbnail generation | High | ✅ Implemented | -| **Gallery Templates** | Additional gallery layouts and themes (masonry, slideshow, story-style) for different event types | Medium | 🔄 Open | +| **Download Protection** | Advanced image protection system with canvas rendering, invisible watermarking, right-click prevention, and DevTools detection to protect photos from unauthorized downloads | High | ✅ Implemented | +| **Gallery Templates** | Multiple gallery layouts (grid, masonry, carousel, timeline, hero, mosaic) with custom CSS styling support. Includes starter templates like Apple Liquid Glass for complete visual customization | Medium | ✅ Implemented | | **Face Recognition** | AI-powered face detection to help guests find their photos and create automatic person-based albums | Low | 🔄 Open | | **Gallery Feedback** | Allow guests to like, rate, and comment on photos with admin notifications and moderation | Medium | ✅ Implemented | | **Video Support** | Upload and display videos alongside photos in galleries with streaming support | Low | ✅ Implemented | | **Multiple Administrators** | Support for multiple admin accounts with role-based permissions and activity tracking | Low | 📋 Planned | -| **Filtering & Export Options** | Add filters to show only rated, liked, or marked photos and export filtered selections for Capture One or Lightroom workflows | Low | 🔄 Open | +| **Filtering & Export Options** | Filter photos by likes, ratings, comments, or favorites. Search by filename. Sort by date, name, size, or rating. Export filtered selections as ZIP or generate Capture One/Lightroom-compatible file lists for professional workflows | Medium | ✅ Implemented | **Status Legend:** ✅ Implemented | 🚧 In Progress | 🔄 Open | 📋 Planned diff --git a/backend/migrations/core/053_add_liquid_glass_templates.js b/backend/migrations/core/053_add_liquid_glass_templates.js index 2900eb0f..af19bdd0 100644 --- a/backend/migrations/core/053_add_liquid_glass_templates.js +++ b/backend/migrations/core/053_add_liquid_glass_templates.js @@ -1,296 +1,334 @@ /** * Migration: Add Liquid Glass CSS Templates * Updates template slots 2 and 3 with Apple-inspired Liquid Glass designs - */ - -const LIQUID_GLASS_LIGHT = `/* - * PicPeak Custom CSS Template: Liquid Glass Light - * Inspired by Apple's iOS 26 Liquid Glass Design Language * - * Features: - * - Translucent frosted glass surfaces - * - Dynamic light refraction effects - * - Subtle specular highlights - * - Soft depth shadows + * These are starter example templates for new installations. + * Users can edit or replace them as needed. */ -/* ===== Base Theme Variables ===== */ -.gallery-page { - --glass-bg: rgba(255, 255, 255, 0.7); - --glass-bg-elevated: rgba(255, 255, 255, 0.85); - --glass-border: rgba(255, 255, 255, 0.5); - --glass-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); - --glass-blur: 20px; - --glass-saturation: 180%; +const APPLE_LIQUID_GLASS = `/* + * PicPeak Custom CSS Template: Apple Liquid Glass + * Authentic iOS 26 / macOS Tahoe Liquid Glass Design + */ - --gallery-bg: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); +/* ===== Apple System Fonts ===== */ +.gallery-page, +.gallery-page *, +.gallery-sidebar, +.gallery-sidebar * { + font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", + "Helvetica Neue", Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* ===== CSS Variables ===== */ +:root { + --glass-blur: 20px; + --glass-blur-heavy: 40px; + --glass-saturation: 180%; + --glass-bg: rgba(255, 255, 255, 0.08); + --glass-bg-medium: rgba(255, 255, 255, 0.18); + --glass-bg-solid: rgba(255, 255, 255, 0.25); + --glass-border: rgba(255, 255, 255, 0.2); + --glass-border-light: rgba(255, 255, 255, 0.4); + --glass-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); + --glass-inset: inset 0 1px 1px rgba(255, 255, 255, 0.4), + inset 0 -1px 1px rgba(0, 0, 0, 0.05); + --gallery-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%); --gallery-text: #1a1a2e; - --gallery-text-muted: rgba(26, 26, 46, 0.7); + --gallery-text-light: #ffffff; --gallery-accent: #667eea; - --gallery-accent-hover: #764ba2; - --gallery-radius: 24px; - --gallery-spacing: 20px; + --gallery-radius: 20px; + --gallery-radius-sm: 12px; } /* ===== Page Background ===== */ .gallery-page { - background: var(--gallery-bg); + background: var(--gallery-gradient) !important; + background-attachment: fixed !important; min-height: 100vh; - position: relative; } -/* Animated gradient background */ .gallery-page::before { content: ''; position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; + inset: 0; background: - radial-gradient(circle at 20% 80%, rgba(255, 255, 255, 0.3) 0%, transparent 50%), - radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.2) 0%, transparent 40%); + radial-gradient(ellipse 600px 400px at 15% 85%, rgba(255, 255, 255, 0.2) 0%, transparent 50%), + radial-gradient(ellipse 500px 350px at 85% 15%, rgba(255, 255, 255, 0.15) 0%, transparent 45%); pointer-events: none; z-index: 0; } -/* ===== Glass Card Base ===== */ -.glass-surface { - background: var(--glass-bg); - backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); - -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); - border: 1px solid var(--glass-border); - border-radius: var(--gallery-radius); - box-shadow: - var(--glass-shadow), - inset 0 1px 1px rgba(255, 255, 255, 0.8), - inset 0 -1px 1px rgba(0, 0, 0, 0.05); +/* ===== TOP BAR / HEADER - Liquid Glass ===== */ +.gallery-page .gallery-header, +.gallery-page header { + background: var(--glass-bg-medium) !important; + backdrop-filter: blur(var(--glass-blur-heavy)) saturate(var(--glass-saturation)) !important; + -webkit-backdrop-filter: blur(var(--glass-blur-heavy)) saturate(var(--glass-saturation)) !important; + border-bottom: 1px solid var(--glass-border) !important; + box-shadow: var(--glass-shadow), var(--glass-inset) !important; } -/* Liquid shine effect */ -.glass-surface::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 50%; - background: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.4) 0%, - rgba(255, 255, 255, 0.1) 50%, - transparent 100% - ); - border-radius: var(--gallery-radius) var(--gallery-radius) 0 0; - pointer-events: none; +.gallery-page .gallery-header > div { + background: transparent !important; + border: none !important; } -/* ===== Gallery Header ===== */ -.gallery-header { - background: var(--glass-bg-elevated); - backdrop-filter: blur(30px) saturate(200%); - -webkit-backdrop-filter: blur(30px) saturate(200%); - border-bottom: 1px solid var(--glass-border); - padding: calc(var(--gallery-spacing) * 1.5); - position: sticky; - top: 0; - z-index: 100; +/* ===== SIDEBAR - Liquid Glass ===== */ +.gallery-sidebar { + background: var(--glass-bg-medium) !important; + backdrop-filter: blur(var(--glass-blur-heavy)) saturate(var(--glass-saturation)) !important; + -webkit-backdrop-filter: blur(var(--glass-blur-heavy)) saturate(var(--glass-saturation)) !important; + border-right: 1px solid var(--glass-border) !important; + box-shadow: 4px 0 32px rgba(31, 38, 135, 0.1), var(--glass-inset) !important; } -.gallery-title { - color: var(--gallery-text); - font-weight: 700; - font-size: 1.75rem; - letter-spacing: -0.02em; - text-shadow: 0 1px 2px rgba(255, 255, 255, 0.5); +.gallery-sidebar h2, +.gallery-sidebar h3 { + color: var(--gallery-text) !important; + font-weight: 600 !important; } -/* ===== Photo Grid ===== */ -.photo-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); - gap: var(--gallery-spacing); - padding: calc(var(--gallery-spacing) * 2); - position: relative; - z-index: 1; +/* ===== HERO LAYOUT - Transform to Glass Title Box ===== */ +/* Target the hero wrapper */ +.gallery-page .relative.-mt-6 { + margin-top: 0 !important; } -/* ===== Photo Cards - Glass Style ===== */ -.photo-card { - position: relative; - background: var(--glass-bg); - backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); - -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)); - border: 1px solid var(--glass-border); - border-radius: var(--gallery-radius); - overflow: hidden; - transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); - box-shadow: - 0 4px 16px rgba(0, 0, 0, 0.1), - inset 0 1px 1px rgba(255, 255, 255, 0.6); +/* Target the hero section (first child with h-[60vh]) */ +.gallery-page .relative.-mt-6 > .relative:first-child { + height: auto !important; + min-height: auto !important; + margin: 0 !important; + padding: 2rem !important; + display: flex !important; + justify-content: center !important; + align-items: center !important; + background: transparent !important; } -.photo-card::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 40%; - background: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.3) 0%, - transparent 100% - ); - pointer-events: none; - z-index: 1; - border-radius: var(--gallery-radius) var(--gallery-radius) 0 0; +/* Hide the hero background image */ +.gallery-page .relative.-mt-6 > .relative:first-child > img, +.gallery-page .relative.-mt-6 > .relative:first-child > canvas { + display: none !important; } -.photo-card:hover { - transform: translateY(-8px) scale(1.02); - box-shadow: - 0 20px 40px rgba(102, 126, 234, 0.3), - 0 8px 16px rgba(0, 0, 0, 0.1), - inset 0 1px 1px rgba(255, 255, 255, 0.8); +/* Hide the dark overlay */ +.gallery-page .relative.-mt-6 > .relative:first-child > .absolute.inset-0.bg-black { + display: none !important; } -.photo-card img { - width: 100%; - height: 240px; - object-fit: cover; - transition: transform 0.4s ease; +/* Style the content area as glass title box */ +.gallery-page .relative.-mt-6 > .relative:first-child > .absolute.inset-0.flex { + position: relative !important; + inset: auto !important; + background: var(--glass-bg-medium) !important; + backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)) !important; + -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)) !important; + border: 1px solid var(--glass-border-light) !important; + border-radius: var(--gallery-radius) !important; + padding: 2rem 3rem !important; + box-shadow: var(--glass-shadow), var(--glass-inset) !important; + max-width: 600px !important; + width: auto !important; } -.photo-card:hover img { - transform: scale(1.05); +/* Hide logo in glass title box */ +.gallery-page .relative.-mt-6 > .relative:first-child .mb-6 { + display: none !important; } -.photo-card-info { - padding: var(--gallery-spacing); - background: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.1) 0%, - rgba(255, 255, 255, 0.3) 100% - ); +/* Style title text */ +.gallery-page .relative.-mt-6 > .relative:first-child h1 { + color: var(--gallery-text) !important; + text-shadow: none !important; + font-weight: 700 !important; + font-size: 2.25rem !important; + margin-bottom: 0.75rem !important; } -/* ===== Buttons - Glass Style ===== */ -.gallery-btn { - background: var(--glass-bg); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border: 1px solid var(--glass-border); - border-radius: calc(var(--gallery-radius) / 2); - padding: 12px 24px; - color: var(--gallery-text); - font-weight: 600; - cursor: pointer; - transition: all 0.3s ease; - position: relative; - overflow: hidden; +/* Style date text */ +.gallery-page .relative.-mt-6 > .relative:first-child .text-white\\/90 { + color: var(--gallery-text) !important; + opacity: 0.8; } -.gallery-btn::before { - content: ''; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 50%; - background: linear-gradient( - 180deg, - rgba(255, 255, 255, 0.4) 0%, - transparent 100% - ); +/* Hide scroll down button */ +.gallery-page .relative.-mt-6 > .relative:first-child > .absolute.bottom-8, +.gallery-page .relative.-mt-6 > .relative:first-child > button.absolute { + display: none !important; } -.gallery-btn:hover { - background: var(--glass-bg-elevated); - transform: translateY(-2px); - box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3); +/* ===== PHOTO GRID ===== */ +.gallery-page .photo-grid { + display: grid !important; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)) !important; + gap: 1.25rem !important; + padding: 1rem !important; } -.gallery-btn-primary { - background: linear-gradient(135deg, var(--gallery-accent) 0%, var(--gallery-accent-hover) 100%); - color: white; - border: none; +/* ===== PHOTO CARDS - Liquid Glass ===== */ +.gallery-page .photo-card { + background: var(--glass-bg) !important; + backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)) !important; + -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)) !important; + border: 1px solid var(--glass-border) !important; + border-radius: var(--gallery-radius) !important; + overflow: hidden !important; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1), var(--glass-inset) !important; } -/* ===== Lightbox - Glass Style ===== */ -.lightbox-overlay { - background: rgba(26, 26, 46, 0.8); - backdrop-filter: blur(40px); - -webkit-backdrop-filter: blur(40px); +.gallery-page .photo-card:hover { + transform: translateY(-6px) scale(1.02) !important; + box-shadow: 0 20px 40px rgba(102, 126, 234, 0.25), + 0 8px 16px rgba(0, 0, 0, 0.1), + var(--glass-inset) !important; + border-color: var(--glass-border-light) !important; } -.lightbox-content { - background: var(--glass-bg); - backdrop-filter: blur(20px); - -webkit-backdrop-filter: blur(20px); - border: 1px solid var(--glass-border); - border-radius: var(--gallery-radius); - box-shadow: 0 24px 48px rgba(0, 0, 0, 0.2); +.gallery-page .photo-card img { + transition: transform 0.4s ease !important; } -/* ===== Category Pills ===== */ -.category-pill { - background: var(--glass-bg); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); - border: 1px solid var(--glass-border); - border-radius: 9999px; - padding: 8px 20px; - font-size: 0.875rem; - font-weight: 500; - color: var(--gallery-text); - transition: all 0.3s ease; +.gallery-page .photo-card:hover img { + transform: scale(1.05) !important; } -.category-pill:hover, -.category-pill.active { - background: var(--gallery-accent); - color: white; - border-color: var(--gallery-accent); +/* ===== BUTTONS - Glass Pill Style ===== */ +.gallery-page button, +.gallery-page [role="button"], +.gallery-sidebar button { + background: var(--glass-bg) !important; + backdrop-filter: blur(12px) saturate(150%) !important; + -webkit-backdrop-filter: blur(12px) saturate(150%) !important; + border: 1px solid var(--glass-border) !important; + border-radius: 9999px !important; + color: var(--gallery-text) !important; + font-weight: 500 !important; + transition: all 0.3s ease !important; } -/* ===== Responsive ===== */ +.gallery-page button:hover, +.gallery-page [role="button"]:hover, +.gallery-sidebar button:hover { + background: var(--glass-bg-medium) !important; + transform: translateY(-1px); + box-shadow: 0 4px 16px rgba(102, 126, 234, 0.2) !important; +} + +.gallery-page button[class*="bg-primary"], +.gallery-page .gallery-btn-download { + background: linear-gradient(135deg, var(--gallery-accent) 0%, #764ba2 100%) !important; + color: white !important; + border: none !important; +} + +/* ===== INPUT FIELDS ===== */ +.gallery-page input, +.gallery-page select, +.gallery-sidebar input, +.gallery-sidebar select { + background: rgba(255, 255, 255, 0.25) !important; + backdrop-filter: blur(8px) !important; + -webkit-backdrop-filter: blur(8px) !important; + border: 1px solid var(--glass-border) !important; + border-radius: var(--gallery-radius-sm) !important; + color: var(--gallery-text) !important; +} + +/* Input placeholder text - make it visible */ +.gallery-page input::placeholder, +.gallery-sidebar input::placeholder { + color: rgba(26, 26, 46, 0.6) !important; + opacity: 1 !important; +} + +/* Input focus state */ +.gallery-page input:focus, +.gallery-sidebar input:focus { + background: rgba(255, 255, 255, 0.35) !important; + border-color: var(--glass-border-light) !important; + outline: none !important; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2) !important; +} + +/* ===== FOOTER ===== */ +.gallery-page .gallery-footer, +.gallery-page footer { + background: var(--glass-bg) !important; + backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)) !important; + -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturation)) !important; + border-top: 1px solid var(--glass-border) !important; +} + +/* ===== LIGHTBOX ===== */ +.gallery-page [class*="fixed"][class*="inset-0"][class*="z-50"] { + background: rgba(0, 0, 0, 0.7) !important; + backdrop-filter: blur(30px) !important; + -webkit-backdrop-filter: blur(30px) !important; +} + +/* ===== SCROLLBAR ===== */ +.gallery-page ::-webkit-scrollbar, +.gallery-sidebar ::-webkit-scrollbar { + width: 8px; +} + +.gallery-page ::-webkit-scrollbar-track, +.gallery-sidebar ::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.1); +} + +.gallery-page ::-webkit-scrollbar-thumb, +.gallery-sidebar ::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); + border-radius: 4px; +} + +/* ===== RESPONSIVE ===== */ @media (max-width: 768px) { - .gallery-page { + :root { --gallery-radius: 16px; - --gallery-spacing: 12px; --glass-blur: 16px; } - .photo-grid { - grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); + .gallery-page .relative.-mt-6 > .relative:first-child > .absolute.inset-0.flex { + padding: 1.5rem 2rem !important; + max-width: 90% !important; } - .photo-card img { - height: 180px; + .gallery-page .relative.-mt-6 > .relative:first-child h1 { + font-size: 1.5rem !important; + } + + .gallery-page .photo-grid { + grid-template-columns: repeat(2, 1fr) !important; + gap: 0.75rem !important; } } -/* ===== Accessibility: Reduce Motion ===== */ +/* ===== ACCESSIBILITY ===== */ @media (prefers-reduced-motion: reduce) { - .photo-card, - .gallery-btn { - transition: none; + .gallery-page .photo-card, + .gallery-page button { + transition: none !important; } - .photo-card:hover { - transform: none; + .gallery-page .photo-card:hover { + transform: none !important; } } -/* ===== Accessibility: Reduce Transparency ===== */ @media (prefers-reduced-transparency: reduce) { - .glass-surface, - .photo-card, - .gallery-btn { - backdrop-filter: none; - -webkit-backdrop-filter: none; - background: rgba(255, 255, 255, 0.95); + .gallery-page .photo-card, + .gallery-page button, + .gallery-sidebar { + backdrop-filter: none !important; + -webkit-backdrop-filter: none !important; + background: rgba(255, 255, 255, 0.95) !important; } }`; @@ -642,12 +680,12 @@ const LIQUID_GLASS_DARK = `/* }`; exports.up = async function(knex) { - // Update template slot 2 with Liquid Glass Light + // Update template slot 2 with Apple Liquid Glass (Light) await knex('css_templates') .where({ slot_number: 2 }) .update({ - name: 'Liquid Glass Light', - css_content: LIQUID_GLASS_LIGHT, + name: 'Apple Liquid Glass', + css_content: APPLE_LIQUID_GLASS, is_enabled: true, is_default: false, updated_at: knex.fn.now() @@ -689,5 +727,5 @@ exports.down = async function(knex) { }; // Export templates for use elsewhere -module.exports.LIQUID_GLASS_LIGHT = LIQUID_GLASS_LIGHT; +module.exports.APPLE_LIQUID_GLASS = APPLE_LIQUID_GLASS; module.exports.LIQUID_GLASS_DARK = LIQUID_GLASS_DARK; diff --git a/backend/package-lock.json b/backend/package-lock.json index 5b858263..89d39dd4 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -258,66 +258,66 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.940.0.tgz", - "integrity": "sha512-Wi4qnBT6shRRMXuuTgjMFTU5mu2KFWisgcigEMPptjPGUtJvBVi4PTGgS64qsLoUk/obqDAyOBOfEtRZ2ddC2w==", + "version": "3.962.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.962.0.tgz", + "integrity": "sha512-I2/1McBZCcM3PfM4ck8D6gnZR3K7+yl1fGkwTq/3ThEn9tdLjNwcdgTbPfxfX6LoecLrH9Ekoo+D9nmQ0T261w==", "license": "Apache-2.0", "peer": true, "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.940.0", - "@aws-sdk/credential-provider-node": "3.940.0", - "@aws-sdk/middleware-bucket-endpoint": "3.936.0", - "@aws-sdk/middleware-expect-continue": "3.936.0", - "@aws-sdk/middleware-flexible-checksums": "3.940.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-location-constraint": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-sdk-s3": "3.940.0", - "@aws-sdk/middleware-ssec": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.940.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/signature-v4-multi-region": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.940.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/eventstream-serde-browser": "^4.2.5", - "@smithy/eventstream-serde-config-resolver": "^4.3.5", - "@smithy/eventstream-serde-node": "^4.2.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-blob-browser": "^4.2.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/hash-stream-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/md5-js": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-node": "3.962.0", + "@aws-sdk/middleware-bucket-endpoint": "3.957.0", + "@aws-sdk/middleware-expect-continue": "3.957.0", + "@aws-sdk/middleware-flexible-checksums": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-location-constraint": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-sdk-s3": "3.957.0", + "@aws-sdk/middleware-ssec": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/signature-v4-multi-region": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/eventstream-serde-browser": "^4.2.7", + "@smithy/eventstream-serde-config-resolver": "^4.3.7", + "@smithy/eventstream-serde-node": "^4.2.7", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-blob-browser": "^4.2.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/hash-stream-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/md5-js": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", - "@smithy/util-stream": "^4.5.6", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", + "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", - "@smithy/util-waiter": "^4.2.5", + "@smithy/util-waiter": "^4.2.7", "tslib": "^2.6.2" }, "engines": { @@ -325,47 +325,47 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.940.0.tgz", - "integrity": "sha512-SdqJGWVhmIURvCSgkDditHRO+ozubwZk9aCX9MK8qxyOndhobCndW1ozl3hX9psvMAo9Q4bppjuqy/GHWpjB+A==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.958.0.tgz", + "integrity": "sha512-6qNCIeaMzKzfqasy2nNRuYnMuaMebCcCPP4J2CVGkA8QYMbIVKPlkn9bpB20Vxe6H/r3jtCCLQaOJjVTx/6dXg==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.940.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.940.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.940.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -374,22 +374,22 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.940.0.tgz", - "integrity": "sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.957.0.tgz", + "integrity": "sha512-DrZgDnF1lQZv75a52nFWs6MExihJF2GZB6ETZRqr6jMwhrk2kbJPUtvgbifwcL7AYmVqHQDJBrR/MqkwwFCpiw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws-sdk/xml-builder": "3.930.0", - "@smithy/core": "^3.18.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/xml-builder": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.5", + "@smithy/util-middleware": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -397,16 +397,29 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.940.0.tgz", - "integrity": "sha512-/G3l5/wbZYP2XEQiOoIkRJmlv15f1P3MSd1a0gz27lHEMrOJOGq66rF1Ca4OJLzapWt3Fy9BPrZAepoAX11kMw==", + "node_modules/@aws-sdk/crc64-nvme": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/crc64-nvme/-/crc64-nvme-3.957.0.tgz", + "integrity": "sha512-qSwSfI+qBU9HDsd6/4fM9faCxYJx2yDuHtj+NVOQ6XYDWQzFab/hUdwuKZ77Pi6goLF1pBZhJ2azaC2w7LbnTA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.957.0.tgz", + "integrity": "sha512-475mkhGaWCr+Z52fOOVb/q2VHuNvqEDixlYIkeaO6xJ6t9qR0wpLt4hOQaR6zR1wfZV0SlE7d8RErdYq/PByog==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -414,20 +427,20 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.940.0.tgz", - "integrity": "sha512-dOrc03DHElNBD6N9Okt4U0zhrG4Wix5QUBSZPr5VN8SvmjD9dkrrxOkkJaMCl/bzrW7kbQEp7LuBdbxArMmOZQ==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.957.0.tgz", + "integrity": "sha512-8dS55QHRxXgJlHkEYaCGZIhieCs9NU1HU1BcqQ4RfUdSsfRdxxktqUKgCnBnOOn0oD3PPA8cQOCAVgIyRb3Rfw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", "tslib": "^2.6.2" }, "engines": { @@ -435,24 +448,24 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.940.0.tgz", - "integrity": "sha512-gn7PJQEzb/cnInNFTOaDoCN/hOKqMejNmLof1W5VW95Qk0TPO52lH8R4RmJPnRrwFMswOWswTOpR1roKNLIrcw==", + "version": "3.962.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.962.0.tgz", + "integrity": "sha512-h0kVnXLW2d3nxbcrR/Pfg3W/+YoCguasWz7/3nYzVqmdKarGrpJzaFdoZtLgvDSZ8VgWUC4lWOTcsDMV0UNqUQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.940.0", - "@aws-sdk/credential-provider-env": "3.940.0", - "@aws-sdk/credential-provider-http": "3.940.0", - "@aws-sdk/credential-provider-login": "3.940.0", - "@aws-sdk/credential-provider-process": "3.940.0", - "@aws-sdk/credential-provider-sso": "3.940.0", - "@aws-sdk/credential-provider-web-identity": "3.940.0", - "@aws-sdk/nested-clients": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-login": "3.962.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.958.0", + "@aws-sdk/credential-provider-web-identity": "3.958.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -460,18 +473,18 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.940.0.tgz", - "integrity": "sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg==", + "version": "3.962.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.962.0.tgz", + "integrity": "sha512-kHYH6Av2UifG3mPkpPUNRh/PuX6adaAcpmsclJdHdxlixMCRdh8GNeEihq480DC0GmfqdpoSf1w2CLmLLPIS6w==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.940.0", - "@aws-sdk/nested-clients": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -479,22 +492,22 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.940.0.tgz", - "integrity": "sha512-M8NFAvgvO6xZjiti5kztFiAYmSmSlG3eUfr4ZHSfXYZUA/KUdZU/D6xJyaLnU8cYRWBludb6K9XPKKVwKfqm4g==", + "version": "3.962.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.962.0.tgz", + "integrity": "sha512-CS78NsWRxLa+nWqeWBEYMZTLacMFIXs1C5WJuM9kD05LLiWL32ksljoPsvNN24Bc7rCSQIIMx/U3KGvkDVZMVg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "3.940.0", - "@aws-sdk/credential-provider-http": "3.940.0", - "@aws-sdk/credential-provider-ini": "3.940.0", - "@aws-sdk/credential-provider-process": "3.940.0", - "@aws-sdk/credential-provider-sso": "3.940.0", - "@aws-sdk/credential-provider-web-identity": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/credential-provider-env": "3.957.0", + "@aws-sdk/credential-provider-http": "3.957.0", + "@aws-sdk/credential-provider-ini": "3.962.0", + "@aws-sdk/credential-provider-process": "3.957.0", + "@aws-sdk/credential-provider-sso": "3.958.0", + "@aws-sdk/credential-provider-web-identity": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -502,16 +515,16 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.940.0.tgz", - "integrity": "sha512-pILBzt5/TYCqRsJb7vZlxmRIe0/T+FZPeml417EK75060ajDGnVJjHcuVdLVIeKoTKm9gmJc9l45gon6PbHyUQ==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.957.0.tgz", + "integrity": "sha512-/KIz9kadwbeLy6SKvT79W81Y+hb/8LMDyeloA2zhouE28hmne+hLn0wNCQXAAupFFlYOAtZR2NTBs7HBAReJlg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -519,18 +532,18 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.940.0.tgz", - "integrity": "sha512-q6JMHIkBlDCOMnA3RAzf8cGfup+8ukhhb50fNpghMs1SNBGhanmaMbZSgLigBRsPQW7fOk2l8jnzdVLS+BB9Uw==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.958.0.tgz", + "integrity": "sha512-CBYHJ5ufp8HC4q+o7IJejCUctJXWaksgpmoFpXerbjAso7/Fg7LLUu9inXVOxlHKLlvYekDXjIUBXDJS2WYdgg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/client-sso": "3.940.0", - "@aws-sdk/core": "3.940.0", - "@aws-sdk/token-providers": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/client-sso": "3.958.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/token-providers": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -538,17 +551,17 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.940.0.tgz", - "integrity": "sha512-9QLTIkDJHHaYL0nyymO41H8g3ui1yz6Y3GmAN1gYQa6plXisuFBnGAbmKVj7zNvjWaOKdF0dV3dd3AFKEDoJ/w==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.958.0.tgz", + "integrity": "sha512-dgnvwjMq5Y66WozzUzxNkCFap+umHUtqMMKlr8z/vl9NYMLem/WUbWNpFFOVFWquXikc+ewtpBMR4KEDXfZ+KA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.940.0", - "@aws-sdk/nested-clients": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -556,14 +569,14 @@ } }, "node_modules/@aws-sdk/lib-storage": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.940.0.tgz", - "integrity": "sha512-4pHgz9tuFJNSy/qoTbW5FqXPjoR4B18jB656UsE+TP5GWd7EPx7m4F0EUwIsD3OF5+KPiiyICi8zkxOs7erfQw==", + "version": "3.962.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-storage/-/lib-storage-3.962.0.tgz", + "integrity": "sha512-Ai5gWRQkzsUMQ6NPoZZoiLXoQ6/yPRcR4oracIVjyWcu48TfBpsRgbqY/5zNOM55ag1wPX9TtJJGOhK3TNk45g==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/smithy-client": "^4.9.8", + "@smithy/abort-controller": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/smithy-client": "^4.10.2", "buffer": "5.6.0", "events": "3.3.0", "stream-browserify": "3.0.0", @@ -573,20 +586,20 @@ "node": ">=18.0.0" }, "peerDependencies": { - "@aws-sdk/client-s3": "^3.940.0" + "@aws-sdk/client-s3": "^3.962.0" } }, "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.936.0.tgz", - "integrity": "sha512-XLSVVfAorUxZh6dzF+HTOp4R1B5EQcdpGcPliWr0KUj2jukgjZEcqbBmjyMF/p9bmyQsONX80iURF1HLAlW0qg==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.957.0.tgz", + "integrity": "sha512-iczcn/QRIBSpvsdAS/rbzmoBpleX1JBjXvCynMbDceVLBIcVrwT1hXECrhtIC2cjh4HaLo9ClAbiOiWuqt+6MA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-arn-parser": "3.893.0", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-arn-parser": "3.957.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" }, @@ -595,14 +608,14 @@ } }, "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.936.0.tgz", - "integrity": "sha512-Eb4ELAC23bEQLJmUMYnPWcjD3FZIsmz2svDiXEcxRkQU9r7NRID7pM7C5NPH94wOfiCk0b2Y8rVyFXW0lGQwbA==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.957.0.tgz", + "integrity": "sha512-AlbK3OeVNwZZil0wlClgeI/ISlOt/SPUxBsIns876IFaVu/Pj3DgImnYhpcJuFRek4r4XM51xzIaGQXM6GDHGg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.957.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -610,22 +623,23 @@ } }, "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.940.0.tgz", - "integrity": "sha512-WdsxDAVj5qaa5ApAP+JbpCOMHFGSmzjs2Y2OBSbWPeR9Ew7t/Okj+kUub94QJPsgzhvU1/cqNejhsw5VxeFKSQ==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.957.0.tgz", + "integrity": "sha512-iJpeVR5V8se1hl2pt+k8bF/e9JO4KWgPCMjg8BtRspNtKIUGy7j6msYvbDixaKZaF2Veg9+HoYcOhwnZumjXSA==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", - "@aws-sdk/core": "3.940.0", - "@aws-sdk/types": "3.936.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/crc64-nvme": "3.957.0", + "@aws-sdk/types": "3.957.0", "@smithy/is-array-buffer": "^4.2.0", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-stream": "^4.5.6", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -634,14 +648,14 @@ } }, "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", - "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.957.0.tgz", + "integrity": "sha512-BBgKawVyfQZglEkNTuBBdC3azlyqNXsvvN4jPkWAiNYcY0x1BasaJFl+7u/HisfULstryweJq/dAvIZIxzlZaA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.957.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -649,13 +663,13 @@ } }, "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.936.0.tgz", - "integrity": "sha512-SCMPenDtQMd9o5da9JzkHz838w3327iqXk3cbNnXWqnNRx6unyW8FL0DZ84gIY12kAyVHz5WEqlWuekc15ehfw==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.957.0.tgz", + "integrity": "sha512-y8/W7TOQpmDJg/fPYlqAhwA4+I15LrS7TwgUEoxogtkD8gfur9wFMRLT8LCyc9o4NMEcAnK50hSb4+wB0qv6tQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -663,13 +677,13 @@ } }, "node_modules/@aws-sdk/middleware-logger": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", - "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.957.0.tgz", + "integrity": "sha512-w1qfKrSKHf9b5a8O76yQ1t69u6NWuBjr5kBX+jRWFx/5mu6RLpqERXRpVJxfosbep7k3B+DSB5tZMZ82GKcJtQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -677,15 +691,15 @@ } }, "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", - "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.957.0.tgz", + "integrity": "sha512-D2H/WoxhAZNYX+IjkKTdOhOkWQaK0jjJrDBj56hKjU5c9ltQiaX/1PqJ4dfjHntEshJfu0w+E6XJ+/6A6ILBBA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@aws/lambda-invoke-store": "^0.2.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.957.0", + "@aws/lambda-invoke-store": "^0.2.2", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -693,23 +707,23 @@ } }, "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.940.0.tgz", - "integrity": "sha512-JYkLjgS1wLoKHJ40G63+afM1ehmsPsjcmrHirKh8+kSCx4ip7+nL1e/twV4Zicxr8RJi9Y0Ahq5mDvneilDDKQ==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.957.0.tgz", + "integrity": "sha512-5B2qY2nR2LYpxoQP0xUum5A1UNvH2JQpLHDH1nWFNF/XetV7ipFHksMxPNhtJJ6ARaWhQIDXfOUj0jcnkJxXUg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-arn-parser": "3.893.0", - "@smithy/core": "^3.18.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-arn-parser": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-stream": "^4.5.6", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -718,13 +732,13 @@ } }, "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.936.0.tgz", - "integrity": "sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.957.0.tgz", + "integrity": "sha512-qwkmrK0lizdjNt5qxl4tHYfASh8DFpHXM1iDVo+qHe+zuslfMqQEGRkzxS8tJq/I+8F0c6v3IKOveKJAfIvfqQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -732,17 +746,17 @@ } }, "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.940.0.tgz", - "integrity": "sha512-nJbLrUj6fY+l2W2rIB9P4Qvpiy0tnTdg/dmixRxrU1z3e8wBdspJlyE+AZN4fuVbeL6rrRrO/zxQC1bB3cw5IA==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.957.0.tgz", + "integrity": "sha512-50vcHu96XakQnIvlKJ1UoltrFODjsq2KvtTgHiPFteUS884lQnK5VC/8xd1Msz/1ONpLMzdCVproCQqhDTtMPQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@smithy/core": "^3.18.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@smithy/core": "^3.20.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -750,47 +764,47 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.940.0.tgz", - "integrity": "sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.958.0.tgz", + "integrity": "sha512-/KuCcS8b5TpQXkYOrPLYytrgxBhv81+5pChkOlhegbeHttjM69pyUpQVJqyfDM/A7wPLnDrzCAnk4zaAOkY0Nw==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.940.0", - "@aws-sdk/middleware-host-header": "3.936.0", - "@aws-sdk/middleware-logger": "3.936.0", - "@aws-sdk/middleware-recursion-detection": "3.936.0", - "@aws-sdk/middleware-user-agent": "3.940.0", - "@aws-sdk/region-config-resolver": "3.936.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-endpoints": "3.936.0", - "@aws-sdk/util-user-agent-browser": "3.936.0", - "@aws-sdk/util-user-agent-node": "3.940.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/core": "^3.18.5", - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/hash-node": "^4.2.5", - "@smithy/invalid-dependency": "^4.2.5", - "@smithy/middleware-content-length": "^4.2.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-retry": "^4.4.12", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/middleware-host-header": "3.957.0", + "@aws-sdk/middleware-logger": "3.957.0", + "@aws-sdk/middleware-recursion-detection": "3.957.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/region-config-resolver": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-endpoints": "3.957.0", + "@aws-sdk/util-user-agent-browser": "3.957.0", + "@aws-sdk/util-user-agent-node": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/core": "^3.20.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/hash-node": "^4.2.7", + "@smithy/invalid-dependency": "^4.2.7", + "@smithy/middleware-content-length": "^4.2.7", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-retry": "^4.4.17", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.11", - "@smithy/util-defaults-mode-node": "^4.2.14", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", + "@smithy/util-defaults-mode-browser": "^4.3.16", + "@smithy/util-defaults-mode-node": "^4.2.19", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -799,15 +813,15 @@ } }, "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", - "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.957.0.tgz", + "integrity": "sha512-V8iY3blh8l2iaOqXWW88HbkY5jDoWjH56jonprG/cpyqqCnprvpMUZWPWYJoI8rHRf2bqzZeql1slxG6EnKI7A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/config-resolver": "^4.4.3", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.957.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -815,18 +829,18 @@ } }, "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.940.0.tgz", - "integrity": "sha512-TgTUDM2H7revReDfkVwVtIqxV3K0cJLdyuLDIkefVHRUNKwU1Vd5FB2TaFrs6STO0kx5pTckDCOLh0iy7nW5WQ==", + "version": "3.962.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.962.0.tgz", + "integrity": "sha512-tyxsGfLY4NSohLrJsFGXbE3j8jguWK+hdGaUQSD1gJPvmC0B82qOyJ7WBIJLWgTabU3fiF/I9EGXjzR2rKr8jQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/signature-v4-multi-region": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@aws-sdk/util-format-url": "3.936.0", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/protocol-http": "^5.3.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", + "@aws-sdk/signature-v4-multi-region": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@aws-sdk/util-format-url": "3.957.0", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/protocol-http": "^5.3.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -834,16 +848,16 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.940.0.tgz", - "integrity": "sha512-ugHZEoktD/bG6mdgmhzLDjMP2VrYRAUPRPF1DpCyiZexkH7DCU7XrSJyXMvkcf0DHV+URk0q2sLf/oqn1D2uYw==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.957.0.tgz", + "integrity": "sha512-t6UfP1xMUigMMzHcb7vaZcjv7dA2DQkk9C/OAP1dKyrE0vb4lFGDaTApi17GN6Km9zFxJthEMUbBc7DL0hq1Bg==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/signature-v4": "^5.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/middleware-sdk-s3": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/signature-v4": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -851,17 +865,17 @@ } }, "node_modules/@aws-sdk/token-providers": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.940.0.tgz", - "integrity": "sha512-k5qbRe/ZFjW9oWEdzLIa2twRVIEx7p/9rutofyrRysrtEnYh3HAWCngAnwbgKMoiwa806UzcTRx0TjyEpnKcCg==", + "version": "3.958.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.958.0.tgz", + "integrity": "sha512-UCj7lQXODduD1myNJQkV+LYcGYJ9iiMggR8ow8Hva1g3A/Na5imNXzz6O67k7DAee0TYpy+gkNw+SizC6min8Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "3.940.0", - "@aws-sdk/nested-clients": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/core": "3.957.0", + "@aws-sdk/nested-clients": "3.958.0", + "@aws-sdk/types": "3.957.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -869,12 +883,12 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", - "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.957.0.tgz", + "integrity": "sha512-wzWC2Nrt859ABk6UCAVY/WYEbAd7FjkdrQL6m24+tfmWYDNRByTJ9uOgU/kw9zqLCAwb//CPvrJdhqjTznWXAg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -882,9 +896,9 @@ } }, "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", - "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.957.0.tgz", + "integrity": "sha512-Aj6m+AyrhWyg8YQ4LDPg2/gIfGHCEcoQdBt5DeSFogN5k9mmJPOJ+IAmNSWmWRjpOxEy6eY813RNDI6qS97M0g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -894,15 +908,15 @@ } }, "node_modules/@aws-sdk/util-endpoints": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", - "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.957.0.tgz", + "integrity": "sha512-xwF9K24mZSxcxKS3UKQFeX/dPYkEps9wF1b+MGON7EvnbcucrJGyQyK1v1xFPn1aqXkBTFi+SZaMRx5E5YCVFw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-endpoints": "^3.2.5", + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-endpoints": "^3.2.7", "tslib": "^2.6.2" }, "engines": { @@ -910,14 +924,14 @@ } }, "node_modules/@aws-sdk/util-format-url": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.936.0.tgz", - "integrity": "sha512-MS5eSEtDUFIAMHrJaMERiHAvDPdfxc/T869ZjDNFAIiZhyc037REw0aoTNeimNXDNy2txRNZJaAUn/kE4RwN+g==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.957.0.tgz", + "integrity": "sha512-Yyo/tlc0iGFGTPPkuxub1uRAv6XrnVnvSNjslZh5jIYA8GZoeEFPgJa3Qdu0GUS/YwoK8GOLnnaL9h/eH5LDJQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.957.0", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -925,9 +939,9 @@ } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.893.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.893.0.tgz", - "integrity": "sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.957.0.tgz", + "integrity": "sha512-nhmgKHnNV9K+i9daumaIz8JTLsIIML9PE/HUks5liyrjUzenjW/aHoc7WJ9/Td/gPZtayxFnXQSJRb/fDlBuJw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -937,27 +951,27 @@ } }, "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.936.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", - "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.957.0.tgz", + "integrity": "sha512-exueuwxef0lUJRnGaVkNSC674eAiWU07ORhxBnevFFZEKisln+09Qrtw823iyv5I1N8T+wKfh95xvtWQrNKNQw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "3.936.0", - "@smithy/types": "^4.9.0", + "@aws-sdk/types": "3.957.0", + "@smithy/types": "^4.11.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.940.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.940.0.tgz", - "integrity": "sha512-dlD/F+L/jN26I8Zg5x0oDGJiA+/WEQmnSE27fi5ydvYnpfQLwThtQo9SsNS47XSR/SOULaaoC9qx929rZuo74A==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.957.0.tgz", + "integrity": "sha512-ycbYCwqXk4gJGp0Oxkzf2KBeeGBdTxz559D41NJP8FlzSej1Gh7Rk40Zo6AyTfsNWkrl/kVi1t937OIzC5t+9Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/middleware-user-agent": "3.940.0", - "@aws-sdk/types": "3.936.0", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@aws-sdk/middleware-user-agent": "3.957.0", + "@aws-sdk/types": "3.957.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -973,12 +987,12 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.930.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", - "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", + "version": "3.957.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.957.0.tgz", + "integrity": "sha512-Ai5iiQqS8kJ5PjzMhWcLKN0G2yasAkvpnPlq2EnqlIMdB48HsizElt62qcktdxp4neRMyGkFq4NzgmDbXnhRiA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" }, @@ -987,9 +1001,9 @@ } }, "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.1.tgz", - "integrity": "sha512-sIyFcoPZkTtNu9xFeEoynMef3bPJIAbOfUh+ueYcfhVl6xm2VRtMcMclSxmZCMnHHd4hlYKJeq/aggmBEWynww==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.2.tgz", + "integrity": "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -1532,9 +1546,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2860,12 +2874,12 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", - "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.7.tgz", + "integrity": "sha512-rzMY6CaKx2qxrbYbqjXWS0plqEy7LOdKHS0bg4ixJ6aoGDPNUcLWk/FRNuCILh7GKLG9TFUXYYeQQldMBBwuyw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2898,16 +2912,16 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", - "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.5.tgz", + "integrity": "sha512-HAGoUAFYsUkoSckuKbCPayECeMim8pOu+yLy1zOxt1sifzEbrsRpYa+mKcMdiHKMeiqOibyPG0sFJnmaV/OGEg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.5", - "@smithy/util-middleware": "^4.2.5", + "@smithy/util-endpoints": "^3.2.7", + "@smithy/util-middleware": "^4.2.7", "tslib": "^2.6.2" }, "engines": { @@ -2915,18 +2929,18 @@ } }, "node_modules/@smithy/core": { - "version": "3.18.5", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.5.tgz", - "integrity": "sha512-6gnIz3h+PEPQGDj8MnRSjDvKBah042jEoPgjFGJ4iJLBE78L4lY/n98x14XyPF4u3lN179Ub/ZKFY5za9GeLQw==", + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.20.0.tgz", + "integrity": "sha512-WsSHCPq/neD5G/MkK4csLI5Y5Pkd9c1NMfpYEKeghSGaD4Ja1qLIohRQf2D5c1Uy5aXp76DeKHkzWZ9KAlHroQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.2.6", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-stream": "^4.5.6", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-stream": "^4.5.8", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" @@ -2936,15 +2950,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", - "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.7.tgz", + "integrity": "sha512-CmduWdCiILCRNbQWFR0OcZlUPVtyE49Sr8yYL0rZQ4D/wKxiNzBNS/YHemvnbkIWj623fplgkexUd/c9CAKdoA==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", "tslib": "^2.6.2" }, "engines": { @@ -2952,13 +2966,13 @@ } }, "node_modules/@smithy/eventstream-codec": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.5.tgz", - "integrity": "sha512-Ogt4Zi9hEbIP17oQMd68qYOHUzmH47UkK7q7Gl55iIm9oKt27MUGrC5JfpMroeHjdkOliOA4Qt3NQ1xMq/nrlA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.7.tgz", + "integrity": "sha512-DrpkEoM3j9cBBWhufqBwnbbn+3nf1N9FP6xuVJ+e220jbactKuQgaZwjwP5CP1t+O94brm2JgVMD2atMGX3xIQ==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", "tslib": "^2.6.2" }, @@ -2967,13 +2981,13 @@ } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.5.tgz", - "integrity": "sha512-HohfmCQZjppVnKX2PnXlf47CW3j92Ki6T/vkAT2DhBR47e89pen3s4fIa7otGTtrVxmj7q+IhH0RnC5kpR8wtw==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.7.tgz", + "integrity": "sha512-ujzPk8seYoDBmABDE5YqlhQZAXLOrtxtJLrbhHMKjBoG5b4dK4i6/mEU+6/7yXIAkqOO8sJ6YxZl+h0QQ1IJ7g==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/eventstream-serde-universal": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2981,12 +2995,12 @@ } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.5.tgz", - "integrity": "sha512-ibjQjM7wEXtECiT6my1xfiMH9IcEczMOS6xiCQXoUIYSj5b1CpBbJ3VYbdwDy8Vcg5JHN7eFpOCGk8nyZAltNQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.7.tgz", + "integrity": "sha512-x7BtAiIPSaNaWuzm24Q/mtSkv+BrISO/fmheiJ39PKRNH3RmH2Hph/bUKSOBOBC9unqfIYDhKTHwpyZycLGPVQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -2994,13 +3008,13 @@ } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.5.tgz", - "integrity": "sha512-+elOuaYx6F2H6x1/5BQP5ugv12nfJl66GhxON8+dWVUEDJ9jah/A0tayVdkLRP0AeSac0inYkDz5qBFKfVp2Gg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.7.tgz", + "integrity": "sha512-roySCtHC5+pQq5lK4be1fZ/WR6s/AxnPaLfCODIPArtN2du8s5Ot4mKVK3pPtijL/L654ws592JHJ1PbZFF6+A==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/eventstream-serde-universal": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3008,13 +3022,13 @@ } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.5.tgz", - "integrity": "sha512-G9WSqbST45bmIFaeNuP/EnC19Rhp54CcVdX9PDL1zyEB514WsDVXhlyihKlGXnRycmHNmVv88Bvvt4EYxWef/Q==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.7.tgz", + "integrity": "sha512-QVD+g3+icFkThoy4r8wVFZMsIP08taHVKjE6Jpmz8h5CgX/kk6pTODq5cht0OMtcapUx+xrPzUTQdA+TmO0m1g==", "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/eventstream-codec": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3022,14 +3036,14 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.6", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", - "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.8.tgz", + "integrity": "sha512-h/Fi+o7mti4n8wx1SR6UHWLaakwHRx29sizvp8OOm7iqwKGFneT06GCSFhml6Bha5BT6ot5pj3CYZnCHhGC2Rg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, @@ -3038,14 +3052,14 @@ } }, "node_modules/@smithy/hash-blob-browser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.6.tgz", - "integrity": "sha512-8P//tA8DVPk+3XURk2rwcKgYwFvwGwmJH/wJqQiSKwXZtf/LiZK+hbUZmPj/9KzM+OVSwe4o85KTp5x9DUZTjw==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.8.tgz", + "integrity": "sha512-07InZontqsM1ggTCPSRgI7d8DirqRrnpL7nIACT4PW0AWrgDiHhjGZzbAE5UtRSiU0NISGUYe7/rri9ZeWyDpw==", "license": "Apache-2.0", "dependencies": { "@smithy/chunked-blob-reader": "^5.2.0", "@smithy/chunked-blob-reader-native": "^4.2.1", - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3053,12 +3067,12 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", - "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.7.tgz", + "integrity": "sha512-PU/JWLTBCV1c8FtB8tEFnY4eV1tSfBc7bDBADHfn1K+uRbPgSJ9jnJp0hyjiFN2PMdPzxsf1Fdu0eo9fJ760Xw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -3068,12 +3082,12 @@ } }, "node_modules/@smithy/hash-stream-node": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.5.tgz", - "integrity": "sha512-6+do24VnEyvWcGdHXomlpd0m8bfZePpUKBy7m311n+JuRwug8J4dCanJdTymx//8mi0nlkflZBvJe+dEO/O12Q==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.7.tgz", + "integrity": "sha512-ZQVoAwNYnFMIbd4DUc517HuwNelJUY6YOzwqrbcAgCnVn+79/OK7UjwA93SPpdTOpKDVkLIzavWm/Ck7SmnDPQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -3082,12 +3096,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", - "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.7.tgz", + "integrity": "sha512-ncvgCr9a15nPlkhIUx3CU4d7E7WEuVJOV7fS7nnK2hLtPK9tYRBkMHQbhXU1VvvKeBm/O0x26OEoBq+ngFpOEQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3107,12 +3121,12 @@ } }, "node_modules/@smithy/md5-js": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.5.tgz", - "integrity": "sha512-Bt6jpSTMWfjCtC0s79gZ/WZ1w90grfmopVOWqkI2ovhjpD5Q2XRXuecIPB9689L2+cCySMbaXDhBPU56FKNDNg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.7.tgz", + "integrity": "sha512-Wv6JcUxtOLTnxvNjDnAiATUsk8gvA6EeS8zzHig07dotpByYsLot+m0AaQEniUBjx97AC41MQR4hW0baraD1Xw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, @@ -3121,13 +3135,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", - "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.7.tgz", + "integrity": "sha512-GszfBfCcvt7kIbJ41LuNa5f0wvQCHhnGx/aDaZJCCT05Ld6x6U2s0xsc/0mBFONBZjQJp2U/0uSJ178OXOwbhg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3135,18 +3149,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.3.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.12.tgz", - "integrity": "sha512-9pAX/H+VQPzNbouhDhkW723igBMLgrI8OtX+++M7iKJgg/zY/Ig3i1e6seCcx22FWhE6Q/S61BRdi2wXBORT+A==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.1.tgz", + "integrity": "sha512-gpLspUAoe6f1M6H0u4cVuFzxZBrsGZmjx2O9SigurTx4PbntYa4AJ+o0G0oGm1L2oSX6oBhcGHwrfJHup2JnJg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.18.5", - "@smithy/middleware-serde": "^4.2.6", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", - "@smithy/url-parser": "^4.2.5", - "@smithy/util-middleware": "^4.2.5", + "@smithy/core": "^3.20.0", + "@smithy/middleware-serde": "^4.2.8", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", + "@smithy/url-parser": "^4.2.7", + "@smithy/util-middleware": "^4.2.7", "tslib": "^2.6.2" }, "engines": { @@ -3154,18 +3168,18 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.4.12", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.12.tgz", - "integrity": "sha512-S4kWNKFowYd0lID7/DBqWHOQxmxlsf0jBaos9chQZUWTVOjSW1Ogyh8/ib5tM+agFDJ/TCxuCTvrnlc+9cIBcQ==", + "version": "4.4.17", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.17.tgz", + "integrity": "sha512-MqbXK6Y9uq17h+4r0ogu/sBT6V/rdV+5NvYL7ZV444BKfQygYe8wAhDrVXagVebN6w2RE0Fm245l69mOsPGZzg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/service-error-classification": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", - "@smithy/util-middleware": "^4.2.5", - "@smithy/util-retry": "^4.2.5", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/service-error-classification": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", + "@smithy/util-middleware": "^4.2.7", + "@smithy/util-retry": "^4.2.7", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, @@ -3174,13 +3188,13 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", - "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.8.tgz", + "integrity": "sha512-8rDGYen5m5+NV9eHv9ry0sqm2gI6W7mc1VSFMtn6Igo25S507/HaOX9LTHAS2/J32VXD0xSzrY0H5FJtOMS4/w==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3188,12 +3202,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", - "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.7.tgz", + "integrity": "sha512-bsOT0rJ+HHlZd9crHoS37mt8qRRN/h9jRve1SXUhVbkRzu0QaNYZp1i1jha4n098tsvROjcwfLlfvcFuJSXEsw==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3201,14 +3215,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", - "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.7.tgz", + "integrity": "sha512-7r58wq8sdOcrwWe+klL9y3bc4GW1gnlfnFOuL7CXa7UzfhzhxKuzNdtqgzmTV+53lEp9NXh5hY/S4UgjLOzPfw==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/shared-ini-file-loader": "^4.4.0", - "@smithy/types": "^4.9.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/shared-ini-file-loader": "^4.4.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3216,15 +3230,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", - "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", + "version": "4.4.7", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.7.tgz", + "integrity": "sha512-NELpdmBOO6EpZtWgQiHjoShs1kmweaiNuETUpuup+cmm/xJYjT4eUjfhrXRP4jCOaAsS3c3yPsP3B+K+/fyPCQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/querystring-builder": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/abort-controller": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/querystring-builder": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3232,12 +3246,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", - "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.7.tgz", + "integrity": "sha512-jmNYKe9MGGPoSl/D7JDDs1C8b3dC8f/w78LbaVfoTtWy4xAd5dfjaFG9c9PWPihY4ggMQNQSMtzU77CNgAJwmA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3245,12 +3259,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", - "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.7.tgz", + "integrity": "sha512-1r07pb994I20dD/c2seaZhoCuNYm0rWrvBxhCQ70brNh11M5Ml2ew6qJVo0lclB3jMIXirD4s2XRXRe7QEi0xA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3258,12 +3272,12 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", - "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.7.tgz", + "integrity": "sha512-eKONSywHZxK4tBxe2lXEysh8wbBdvDWiA+RIuaxZSgCMmA0zMgoDpGLJhnyj+c0leOQprVnXOmcB4m+W9Rw7sg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, @@ -3272,12 +3286,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", - "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.7.tgz", + "integrity": "sha512-3X5ZvzUHmlSTHAXFlswrS6EGt8fMSIxX/c3Rm1Pni3+wYWB6cjGocmRIoqcQF9nU5OgGmL0u7l9m44tSUpfj9w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3285,24 +3299,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", - "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.7.tgz", + "integrity": "sha512-YB7oCbukqEb2Dlh3340/8g8vNGbs/QsNNRms+gv3N2AtZz9/1vSBx6/6tpwQpZMEJFs7Uq8h4mmOn48ZZ72MkA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0" + "@smithy/types": "^4.11.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", - "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.2.tgz", + "integrity": "sha512-M7iUUff/KwfNunmrgtqBfvZSzh3bmFgv/j/t1Y1dQ+8dNo34br1cqVEqy6v0mYEgi0DkGO7Xig0AnuOaEGVlcg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3310,16 +3324,16 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", - "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.7.tgz", + "integrity": "sha512-9oNUlqBlFZFOSdxgImA6X5GFuzE7V2H7VG/7E70cdLhidFbdtvxxt81EHgykGK5vq5D3FafH//X+Oy31j3CKOg==", "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.5", + "@smithy/util-middleware": "^4.2.7", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" @@ -3329,17 +3343,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.9.8", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.8.tgz", - "integrity": "sha512-8xgq3LgKDEFoIrLWBho/oYKyWByw9/corz7vuh1upv7ZBm0ZMjGYBhbn6v643WoIqA9UTcx5A5htEp/YatUwMA==", + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.2.tgz", + "integrity": "sha512-D5z79xQWpgrGpAHb054Fn2CCTQZpog7JELbVQ6XAvXs5MNKWf28U9gzSBlJkOyMl9LA1TZEjRtwvGXfP0Sl90g==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.18.5", - "@smithy/middleware-endpoint": "^4.3.12", - "@smithy/middleware-stack": "^4.2.5", - "@smithy/protocol-http": "^5.3.5", - "@smithy/types": "^4.9.0", - "@smithy/util-stream": "^4.5.6", + "@smithy/core": "^3.20.0", + "@smithy/middleware-endpoint": "^4.4.1", + "@smithy/middleware-stack": "^4.2.7", + "@smithy/protocol-http": "^5.3.7", + "@smithy/types": "^4.11.0", + "@smithy/util-stream": "^4.5.8", "tslib": "^2.6.2" }, "engines": { @@ -3347,9 +3361,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", - "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.11.0.tgz", + "integrity": "sha512-mlrmL0DRDVe3mNrjTcVcZEgkFmufITfUAPBEA+AHYiIeYyJebso/He1qLbP3PssRe22KUzLRpQSdBPbXdgZ2VA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3359,13 +3373,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", - "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.7.tgz", + "integrity": "sha512-/RLtVsRV4uY3qPWhBDsjwahAtt3x2IsMGnP5W1b2VZIe+qgCqkLxI1UOHDZp1Q1QSOrdOR32MF3Ph2JfWT1VHg==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/querystring-parser": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3436,14 +3450,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.11", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.11.tgz", - "integrity": "sha512-yHv+r6wSQXEXTPVCIQTNmXVWs7ekBTpMVErjqZoWkYN75HIFN5y9+/+sYOejfAuvxWGvgzgxbTHa/oz61YTbKw==", + "version": "4.3.16", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.16.tgz", + "integrity": "sha512-/eiSP3mzY3TsvUOYMeL4EqUX6fgUOj2eUOU4rMMgVbq67TiRLyxT7Xsjxq0bW3OwuzK009qOwF0L2OgJqperAQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3451,17 +3465,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.14.tgz", - "integrity": "sha512-ljZN3iRvaJUgulfvobIuG97q1iUuCMrvXAlkZ4msY+ZuVHQHDIqn7FKZCEj+bx8omz6kF5yQXms/xhzjIO5XiA==", + "version": "4.2.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.19.tgz", + "integrity": "sha512-3a4+4mhf6VycEJyHIQLypRbiwG6aJvbQAeRAVXydMmfweEPnLLabRbdyo/Pjw8Rew9vjsh5WCdhmDaHkQnhhhA==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.3", - "@smithy/credential-provider-imds": "^4.2.5", - "@smithy/node-config-provider": "^4.3.5", - "@smithy/property-provider": "^4.2.5", - "@smithy/smithy-client": "^4.9.8", - "@smithy/types": "^4.9.0", + "@smithy/config-resolver": "^4.4.5", + "@smithy/credential-provider-imds": "^4.2.7", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/property-provider": "^4.2.7", + "@smithy/smithy-client": "^4.10.2", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3469,13 +3483,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", - "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.7.tgz", + "integrity": "sha512-s4ILhyAvVqhMDYREeTS68R43B1V5aenV5q/V1QpRQJkCXib5BPRo4s7uNdzGtIKxaPHCfU/8YkvPAEvTpxgspg==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.5", - "@smithy/types": "^4.9.0", + "@smithy/node-config-provider": "^4.3.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3495,12 +3509,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", - "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.7.tgz", + "integrity": "sha512-i1IkpbOae6NvIKsEeLLM9/2q4X+M90KV3oCFgWQI4q0Qz+yUZvsr+gZPdAEAtFhWQhAHpTsJO8DRJPuwVyln+w==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.9.0", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3508,13 +3522,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", - "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.7.tgz", + "integrity": "sha512-SvDdsQyF5CIASa4EYVT02LukPHVzAgUA4kMAuZ97QJc2BpAqZfA4PINB8/KOoCXEw9tsuv/jQjMeaHFvxdLNGg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/service-error-classification": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3522,14 +3536,14 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.6", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", - "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.8.tgz", + "integrity": "sha512-ZnnBhTapjM0YPGUSmOs0Mcg/Gg87k503qG4zU2v/+Js2Gu+daKOJMeqcQns8ajepY8tgzzfYxl6kQyZKml6O2w==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.6", - "@smithy/node-http-handler": "^4.4.5", - "@smithy/types": "^4.9.0", + "@smithy/fetch-http-handler": "^5.3.8", + "@smithy/node-http-handler": "^4.4.7", + "@smithy/types": "^4.11.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-buffer-from": "^4.2.0", "@smithy/util-hex-encoding": "^4.2.0", @@ -3566,13 +3580,13 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.5.tgz", - "integrity": "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.7.tgz", + "integrity": "sha512-vHJFXi9b7kUEpHWUCY3Twl+9NPOZvQ0SAi+Ewtn48mbiJk4JY9MZmKQjGB4SCvVb9WPiSphZJYY6RIbs+grrzw==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.2.5", - "@smithy/types": "^4.9.0", + "@smithy/abort-controller": "^4.2.7", + "@smithy/types": "^4.11.0", "tslib": "^2.6.2" }, "engines": { @@ -3740,9 +3754,9 @@ } }, "node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "dev": true, "license": "MIT", "dependencies": { @@ -4266,9 +4280,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.31", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz", - "integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==", + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4370,9 +4384,9 @@ } }, "node_modules/bowser": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.0.tgz", - "integrity": "sha512-yHAbSRuT6LTeKi6k2aS40csueHqgAsFEgmrOsfRyFpJnFv5O2hl9FYmWEUZ97gZ/dG17U4IQQcTx4YAFYPuWRQ==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", + "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", "license": "MIT" }, "node_modules/brace-expansion": { @@ -4400,9 +4414,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -4421,11 +4435,11 @@ "license": "MIT", "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -4621,9 +4635,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001757", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz", - "integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==", + "version": "1.0.30001762", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", + "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", "dev": true, "funding": [ { @@ -5115,9 +5129,9 @@ } }, "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5345,9 +5359,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.262", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz", - "integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==", + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", "dev": true, "license": "ISC" }, @@ -5385,6 +5399,30 @@ "node": ">= 0.8" } }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -5621,9 +5659,9 @@ } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5883,9 +5921,9 @@ } }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, "license": "ISC", "dependencies": { @@ -5941,17 +5979,17 @@ } }, "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "statuses": "2.0.1", + "statuses": "~2.0.2", "unpipe": "~1.0.0" }, "engines": { @@ -6578,19 +6616,23 @@ "optional": true }, "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/http-proxy-agent": { @@ -6693,9 +6735,9 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", - "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.1.tgz", + "integrity": "sha512-2Tth85cXwGFHfvRgZWszZSvdo+0Xsqmw8k8ZwxScfcBneNUraK+dxRxRm24nszx80Y0TVio8kKLt5sLE7ZCLlw==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -7768,12 +7810,12 @@ } }, "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", "license": "MIT", "dependencies": { - "jws": "^3.2.2", + "jws": "^4.0.1", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", @@ -7802,9 +7844,9 @@ } }, "node_modules/jwa": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", - "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", @@ -7813,12 +7855,12 @@ } }, "node_modules/jws": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", - "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", "dependencies": { - "jwa": "^1.4.2", + "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, @@ -8810,9 +8852,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.11.tgz", - "integrity": "sha512-gnXhNRE0FNhD7wPSCGhdNh46Hs6nm+uTyg+Kq0cZukNQiYdnCsoQjodNP9BQVG9XrcK/v6/MgpAPBUFyzh9pvw==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.12.tgz", + "integrity": "sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -9227,10 +9269,10 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "license": "ISC", + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } @@ -9472,9 +9514,9 @@ } }, "node_modules/postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -9729,35 +9771,6 @@ "node": ">= 0.10" } }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -9783,9 +9796,9 @@ } }, "node_modules/react": { - "version": "19.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", - "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", "peer": true, "engines": { @@ -10089,24 +10102,24 @@ } }, "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -10127,25 +10140,16 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" @@ -10601,9 +10605,9 @@ } }, "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -10774,9 +10778,9 @@ } }, "node_modules/strnum": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", - "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", + "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", "funding": [ { "type": "github", @@ -11203,9 +11207,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -11287,9 +11291,9 @@ } }, "node_modules/validator": { - "version": "13.15.23", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.23.tgz", - "integrity": "sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==", + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -11387,9 +11391,9 @@ } }, "node_modules/winston": { - "version": "3.18.3", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.18.3.tgz", - "integrity": "sha512-NoBZauFNNWENgsnC9YpgyYwOVrl2m58PpQ8lNHjV3kosGs7KJ7Npk9pCUE+WJlawVSe8mykWDKWFSVfs3QO9ww==", + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz", + "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==", "license": "MIT", "dependencies": { "@colors/colors": "^1.6.0", diff --git a/backend/scripts/check-storage.js b/backend/scripts/check-storage.js old mode 100755 new mode 100644 diff --git a/backend/scripts/cleanup-thumbnails.js b/backend/scripts/cleanup-thumbnails.js old mode 100755 new mode 100644 diff --git a/backend/scripts/create-admin.js b/backend/scripts/create-admin.js old mode 100755 new mode 100644 diff --git a/backend/scripts/migrate-upload-assets.js b/backend/scripts/migrate-upload-assets.js old mode 100755 new mode 100644 diff --git a/backend/scripts/regenerate-thumbnails.js b/backend/scripts/regenerate-thumbnails.js old mode 100755 new mode 100644 diff --git a/backend/scripts/reset-admin-password.js b/backend/scripts/reset-admin-password.js old mode 100755 new mode 100644 diff --git a/backend/src/routes/adminEvents.js b/backend/src/routes/adminEvents.js index 0b136d10..23b55341 100644 --- a/backend/src/routes/adminEvents.js +++ b/backend/src/routes/adminEvents.js @@ -533,7 +533,7 @@ router.put('/:id', adminAuth, [ body('welcome_message').optional({ nullable: true, checkFalsy: true }).trim(), body('color_theme').optional({ nullable: true }), body('allow_user_uploads').optional().isBoolean(), - body('customer_name').optional().trim().notEmpty(), + body('customer_name').optional({ nullable: true, checkFalsy: true }).trim(), body('customer_email').optional().isEmail().normalizeEmail(), body('upload_category_id').optional().custom((value) => { // Accept null, undefined, or integer values @@ -554,6 +554,13 @@ router.put('/:id', adminAuth, [ body('source_mode').optional().isIn(['managed', 'reference']), body('external_path').optional({ nullable: true }).isString().trim(), body('require_password').optional().isBoolean(), + // Download protection settings + body('protection_level').optional().isIn(['basic', 'standard', 'enhanced', 'maximum']), + body('enable_devtools_protection').optional().isBoolean(), + body('use_canvas_rendering').optional().isBoolean(), + body('overlay_protection').optional().isBoolean(), + body('image_quality').optional().isInt({ min: 1, max: 100 }), + body('fragmentation_level').optional().isInt({ min: 1, max: 10 }), body('password').optional().isString().custom((value, { req }) => { if (value === undefined || value === null || value === '') { return true; diff --git a/backend/src/routes/adminImageSecurity.js b/backend/src/routes/adminImageSecurity.js index 75d370f8..b0d58564 100644 --- a/backend/src/routes/adminImageSecurity.js +++ b/backend/src/routes/adminImageSecurity.js @@ -31,13 +31,15 @@ router.get('/settings', adminAuth, async (req, res) => { const config = {}; settings.forEach(setting => { - config[setting.setting_key] = JSON.parse(setting.setting_value); + // PostgreSQL JSON columns are already parsed by the driver + // Just use the value directly - no need to JSON.parse + config[setting.setting_key] = setting.setting_value; }); res.json(config); } catch (error) { - logger.error('Error getting image security settings', { error: error.message }); - res.status(500).json({ error: 'Failed to get security settings' }); + logger.error('Error getting image security settings', { error: error.message, stack: error.stack }); + res.status(500).json({ error: 'Failed to get security settings', details: error.message }); } }); diff --git a/backend/src/routes/gallery.js b/backend/src/routes/gallery.js index 08e98c7e..4cc63c0f 100644 --- a/backend/src/routes/gallery.js +++ b/backend/src/routes/gallery.js @@ -110,7 +110,9 @@ router.get('/:slug/info', async (req, res) => { 'watermark_downloads', 'watermark_text', 'require_password', - 'color_theme' + 'color_theme', + 'enable_devtools_protection', + 'use_canvas_rendering' ) .first(); @@ -154,7 +156,9 @@ router.get('/:slug/info', async (req, res) => { allow_downloads: !(event.allow_downloads === false || event.allow_downloads === 0 || event.allow_downloads === '0'), disable_right_click: event.disable_right_click === true || event.disable_right_click === 1 || event.disable_right_click === '1', watermark_downloads: event.watermark_downloads === true || event.watermark_downloads === 1 || event.watermark_downloads === '1', - watermark_text: event.watermark_text + watermark_text: event.watermark_text, + enable_devtools_protection: event.enable_devtools_protection === true || event.enable_devtools_protection === 1 || event.enable_devtools_protection === '1', + use_canvas_rendering: event.use_canvas_rendering === true || event.use_canvas_rendering === 1 || event.use_canvas_rendering === '1' }); } catch (error) { console.error('Error fetching gallery info:', error); @@ -315,6 +319,8 @@ router.get('/:slug/photos', verifyGalleryAccess, async (req, res) => { disable_right_click: req.event.disable_right_click === true, watermark_downloads: req.event.watermark_downloads === true, watermark_text: req.event.watermark_text, + enable_devtools_protection: req.event.enable_devtools_protection === true, + use_canvas_rendering: req.event.use_canvas_rendering === true, ...protectionSettings }, categories: categories, @@ -397,19 +403,27 @@ router.get('/:slug/download/:photoId', verifyGalleryAccess, async (req, res) => return res.status(404).json({ error: 'Photo file not found' }); } - // Get watermark settings + // Get watermark settings - apply if global setting OR event-level setting is enabled const watermarkSettings = await watermarkService.getWatermarkSettings(); - - if (watermarkSettings && watermarkSettings.enabled) { + const eventWatermarkEnabled = req.event.watermark_downloads === true || req.event.watermark_downloads === 1; + const shouldApplyWatermark = (watermarkSettings && watermarkSettings.enabled) || eventWatermarkEnabled; + + if (shouldApplyWatermark) { // Apply watermark and send - const watermarkedBuffer = await watermarkService.applyWatermark(filePath, watermarkSettings); - + // Use event watermark text if available, otherwise fall back to global settings + const effectiveSettings = { + ...watermarkSettings, + enabled: true, + text: req.event.watermark_text || watermarkSettings?.text || 'Protected' + }; + const watermarkedBuffer = await watermarkService.applyWatermark(filePath, effectiveSettings); + res.set({ 'Content-Type': photo.mime_type || 'image/jpeg', 'Content-Disposition': `attachment; filename="${photo.filename}"`, 'Content-Length': watermarkedBuffer.length }); - + res.send(watermarkedBuffer); } else { // Send original file @@ -468,9 +482,16 @@ router.get('/:slug/download-all', verifyGalleryAccess, async (req, res) => { archive.pipe(res); - // Get watermark settings + // Get watermark settings - apply if global setting OR event-level setting is enabled const watermarkSettings = await watermarkService.getWatermarkSettings(); - + const eventWatermarkEnabled = req.event.watermark_downloads === true || req.event.watermark_downloads === 1; + const shouldApplyWatermark = (watermarkSettings && watermarkSettings.enabled) || eventWatermarkEnabled; + const effectiveSettings = shouldApplyWatermark ? { + ...watermarkSettings, + enabled: true, + text: req.event.watermark_text || watermarkSettings?.text || 'Protected' + } : null; + // Add photos to archive for (const photo of photos) { let filePath; @@ -485,7 +506,7 @@ router.get('/:slug/download-all', verifyGalleryAccess, async (req, res) => { }); continue; } - + // Determine the file name in the archive let archiveName; if (hasMultipleTypes) { @@ -496,10 +517,10 @@ router.get('/:slug/download-all', verifyGalleryAccess, async (req, res) => { // No folders, just the filename archiveName = photo.filename; } - - if (watermarkSettings && watermarkSettings.enabled) { + + if (shouldApplyWatermark && effectiveSettings) { try { - const watermarkedBuffer = await watermarkService.applyWatermark(filePath, watermarkSettings); + const watermarkedBuffer = await watermarkService.applyWatermark(filePath, effectiveSettings); archive.append(watermarkedBuffer, { name: archiveName }); } catch (watermarkError) { logger.warn('Failed to watermark photo for bulk download, skipping original to avoid leak', { @@ -586,15 +607,23 @@ router.post('/:slug/download-selected', verifyGalleryAccess, async (req, res) => }); archive.pipe(res); - // Check watermark settings similar to download-all + // Check watermark settings - apply if global setting OR event-level setting is enabled const watermarkSettings = await watermarkService.getWatermarkSettings(); + const eventWatermarkEnabled = req.event.watermark_downloads === true || req.event.watermark_downloads === 1; + const shouldApplyWatermark = (watermarkSettings && watermarkSettings.enabled) || eventWatermarkEnabled; + const effectiveSettings = shouldApplyWatermark ? { + ...watermarkSettings, + enabled: true, + text: req.event.watermark_text || watermarkSettings?.text || 'Protected' + } : null; + for (const photo of photos) { try { const filePath = resolvePhotoFilePath(req.event, photo); const name = photo.filename || `photo-${photo.id}.jpg`; - if (watermarkSettings && watermarkSettings.enabled) { + if (shouldApplyWatermark && effectiveSettings) { try { - const watermarkedBuffer = await watermarkService.applyWatermark(filePath, watermarkSettings); + const watermarkedBuffer = await watermarkService.applyWatermark(filePath, effectiveSettings); archive.append(watermarkedBuffer, { name }); } catch (watermarkError) { logger.warn('Failed to watermark selected photo, skipping original to avoid leak', { diff --git a/docs/REFACTORING_PLAN.md b/docs/REFACTORING_PLAN.md deleted file mode 100644 index c7f055ff..00000000 --- a/docs/REFACTORING_PLAN.md +++ /dev/null @@ -1,1744 +0,0 @@ -# PicPeak Comprehensive Refactoring Plan - -> **Version**: 1.0 -> **Created**: January 2026 -> **Status**: Approved for Implementation -> **Architecture Goal**: Clean Architecture with Domain-Driven Design principles - ---- - -## Table of Contents - -1. [Executive Summary](#executive-summary) -2. [Current Architecture Analysis](#current-architecture-analysis) -3. [Target Architecture Vision](#target-architecture-vision) -4. [Phase 1: Code Duplication Elimination](#phase-1-code-duplication-elimination) -5. [Phase 2: Backend Route & Service Refactoring](#phase-2-backend-route--service-refactoring) -6. [Phase 3: Frontend Page Decomposition](#phase-3-frontend-page-decomposition) -7. [Phase 4: Cross-Cutting Concerns](#phase-4-cross-cutting-concerns) -8. [Testing Strategy](#testing-strategy) -9. [Migration & Rollback Plan](#migration--rollback-plan) -10. [Implementation Checklist](#implementation-checklist) - ---- - -## Executive Summary - -### Scope -This refactoring addresses **20+ files** totaling over **15,000 lines of code** that have grown beyond maintainable sizes or contain duplicated logic. The goal is to achieve world-class architecture following Clean Architecture and SOLID principles. - -### Key Outcomes -- **50%+ reduction** in average file size for targeted files -- **Zero code duplication** for utility functions -- **Clear separation of concerns** between routes, services, and domain logic -- **Improved testability** with dependency injection and smaller units -- **Better developer experience** with intuitive file organization - -### Risk Mitigation -- All changes are backwards-compatible -- Each phase can be deployed independently -- Comprehensive test coverage required before each merge -- Feature flags for gradual rollout where applicable - ---- - -## Current Architecture Analysis - -### Backend Issues - -``` -┌─────────────────────────────────────────────────────────────┐ -│ CURRENT STATE │ -├─────────────────────────────────────────────────────────────┤ -│ Routes (1000+ lines each) │ -│ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ adminEvents.js │ │ adminSettings.js│ │ -│ │ 1,088 lines │ │ 1,056 lines │ │ -│ │ - HTTP handlers │ │ - HTTP handlers │ │ -│ │ - Business logic│ │ - Business logic│ │ -│ │ - DB queries │ │ - File uploads │ │ -│ │ - Validation │ │ - Validation │ │ -│ │ - Helpers │ │ - Multer config │ │ -│ └─────────────────┘ └─────────────────┘ │ -│ │ -│ Services (1000+ lines each) │ -│ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ backupService │ │ restoreService │ │ -│ │ 1,120 lines │ │ 1,220 lines │ │ -│ │ - Scheduling │ │ - Validation │ │ -│ │ - Execution │ │ - Execution │ │ -│ │ - S3 upload │ │ - Rollback │ │ -│ │ - Notifications │ │ - Logging │ │ -│ └─────────────────┘ └─────────────────┘ │ -│ │ -│ PROBLEMS: │ -│ ✗ Business logic in route handlers │ -│ ✗ No service layer abstraction for events/photos │ -│ ✗ Duplicate utility functions across files │ -│ ✗ God objects with too many responsibilities │ -└─────────────────────────────────────────────────────────────┘ -``` - -### Frontend Issues - -``` -┌─────────────────────────────────────────────────────────────┐ -│ CURRENT STATE │ -├─────────────────────────────────────────────────────────────┤ -│ Pages (1000+ lines each) │ -│ ┌─────────────────┐ ┌─────────────────┐ │ -│ │ SettingsPage │ │EventDetailsPage │ │ -│ │ 1,839 lines │ │ 1,479 lines │ │ -│ │ - 27 hooks │ │ - 33 hooks │ │ -│ │ - 8 tab sections│ │ - Photo grid │ │ -│ │ - All mutations │ │ - Event editing │ │ -│ │ - All queries │ │ - Theme config │ │ -│ └─────────────────┘ └─────────────────┘ │ -│ │ -│ Duplicate Files │ -│ ┌─────────────────┐ ┌─────────────────┐ │ -│ │CreateEventPage │ │ CMSPage.tsx │ │ -│ │CreateEventPage │ │ CMSPageEnhanced │ │ -│ │ Enhanced │ │ │ │ -│ └─────────────────┘ └─────────────────┘ │ -│ │ -│ PROBLEMS: │ -│ ✗ Mega-components with 20+ hooks │ -│ ✗ Multiple duplicate page implementations │ -│ ✗ No custom hooks for shared logic │ -│ ✗ Mixed concerns in single components │ -└─────────────────────────────────────────────────────────────┘ -``` - -### Code Duplication Map - -| Function/Pattern | Locations | Lines Duplicated | -|-----------------|-----------|------------------| -| `parseBooleanInput` | 3 files | ~60 lines | -| Auth routes | 3 versions | ~900 lines | -| CreateEvent pages | 2 versions | ~1,400 lines | -| CMS pages | 2 versions | ~1,100 lines | -| Multer config | 4+ files | ~200 lines | - ---- - -## Target Architecture Vision - -### Backend Target Architecture - -``` -┌─────────────────────────────────────────────────────────────┐ -│ TARGET STATE │ -├─────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ ROUTES LAYER │ │ -│ │ (Thin controllers - HTTP handling only) │ │ -│ │ ~100-200 lines per file │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ SERVICES LAYER │ │ -│ │ (Business logic orchestration) │ │ -│ │ ~200-400 lines per file │ │ -│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ -│ │ │EventService │ │PhotoService │ │SettingsServ │ │ │ -│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ DOMAIN LAYER │ │ -│ │ (Pure business logic, no I/O) │ │ -│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ -│ │ │ Validators │ │ Transformers│ │ Factories │ │ │ -│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ INFRASTRUCTURE LAYER │ │ -│ │ (Database, File System, External APIs) │ │ -│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ -│ │ │Repositories │ │ FileStorage │ │ S3Adapter │ │ │ -│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ SHARED UTILITIES │ │ -│ │ (Pure functions, zero side effects) │ │ -│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ -│ │ │ parsers.js │ │validators.js│ │formatters.js│ │ │ -│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────┘ -``` - -### Frontend Target Architecture - -``` -┌─────────────────────────────────────────────────────────────┐ -│ TARGET STATE │ -├─────────────────────────────────────────────────────────────┤ -│ │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ PAGES │ │ -│ │ (Route components - composition only) │ │ -│ │ ~100-200 lines per file │ │ -│ │ ┌─────────────────────────────────────────────┐ │ │ -│ │ │ SettingsPage.tsx │ │ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ │ │ │ │ │ -│ │ │ ... │ │ │ -│ │ │ │ │ │ -│ │ └─────────────────────────────────────────────┘ │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ FEATURES │ │ -│ │ (Feature-specific components with hooks) │ │ -│ │ ~200-400 lines per file │ │ -│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ -│ │ │ settings/ │ │ events/ │ │ photos/ │ │ │ -│ │ │ ├─ tabs/ │ │ ├─ editor/ │ │ ├─ grid/ │ │ │ -│ │ │ ├─ hooks/ │ │ ├─ viewer/ │ │ ├─ viewer/ │ │ │ -│ │ │ └─ index.ts │ │ └─ hooks/ │ │ └─ export/ │ │ │ -│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ SHARED COMPONENTS │ │ -│ │ (Reusable UI primitives) │ │ -│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ -│ │ │ common/ │ │ forms/ │ │ layout/ │ │ │ -│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ │ -│ ▼ │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ SHARED HOOKS │ │ -│ │ (Reusable stateful logic) │ │ -│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ -│ │ │useSettings │ │ usePhotos │ │ useEvents │ │ │ -│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────┘ -``` - ---- - -## Phase 1: Code Duplication Elimination - -> **Priority**: CRITICAL -> **Estimated Effort**: 2-3 days -> **Risk**: Low -> **Dependencies**: None - -### 1.1 Extract Shared Parsers Utility - -**Problem**: `parseBooleanInput` function duplicated in 3 files - -**Files Affected**: -- `backend/src/routes/adminEvents.js` (lines 61-81) -- `backend/src/routes/events.js` (lines 14-34) -- `frontend/src/pages/admin/SettingsPage.tsx` (lines 33-52, named `toBoolean`) - -**Solution**: Create unified parser utilities - -#### Backend Implementation - -**Create**: `backend/src/utils/parsers.js` - -```javascript -/** - * Shared Parser Utilities - * Pure functions for parsing and transforming input values - */ - -/** - * Parse any input value to boolean with configurable default - * @param {*} value - Input value to parse - * @param {boolean} defaultValue - Default if value is undefined/null - * @returns {boolean} - */ -const parseBooleanInput = (value, defaultValue = true) => { - if (value === undefined || value === null) { - return defaultValue; - } - if (typeof value === 'boolean') { - return value; - } - if (typeof value === 'number') { - if (Number.isNaN(value)) return defaultValue; - return value !== 0; - } - if (typeof value === 'string') { - const normalized = value.trim().toLowerCase(); - if (['false', '0', 'no', 'off', ''].includes(normalized)) { - return false; - } - if (['true', '1', 'yes', 'on'].includes(normalized)) { - return true; - } - } - return defaultValue; -}; - -/** - * Parse numeric input with validation - * @param {*} value - Input value to parse - * @param {number} defaultValue - Default if invalid - * @param {Object} options - Min/max bounds - * @returns {number} - */ -const parseNumberInput = (value, defaultValue, { min, max } = {}) => { - if (value === undefined || value === null || value === '') { - return defaultValue; - } - const parsed = Number(value); - if (!Number.isFinite(parsed)) { - return defaultValue; - } - if (min !== undefined && parsed < min) return min; - if (max !== undefined && parsed > max) return max; - return parsed; -}; - -/** - * Parse string input with trimming and null handling - * @param {*} value - Input value - * @param {string|null} defaultValue - Default if empty - * @returns {string|null} - */ -const parseStringInput = (value, defaultValue = null) => { - if (value === undefined || value === null) { - return defaultValue; - } - if (typeof value === 'string') { - const trimmed = value.trim(); - return trimmed || defaultValue; - } - return String(value); -}; - -/** - * Parse JSON string safely - * @param {*} value - JSON string or already parsed value - * @param {*} defaultValue - Default if parsing fails - * @returns {*} - */ -const parseJsonInput = (value, defaultValue = null) => { - if (value === undefined || value === null) { - return defaultValue; - } - if (typeof value !== 'string') { - return value; // Already parsed - } - try { - return JSON.parse(value); - } catch { - return defaultValue; - } -}; - -module.exports = { - parseBooleanInput, - parseNumberInput, - parseStringInput, - parseJsonInput -}; -``` - -#### Frontend Implementation - -**Create**: `frontend/src/utils/parsers.ts` - -```typescript -/** - * Shared Parser Utilities for Frontend - */ - -/** - * Parse any input value to boolean with configurable default - */ -export const toBoolean = (value: unknown, defaultValue = false): boolean => { - if (value === undefined || value === null) { - return defaultValue; - } - if (typeof value === 'boolean') { - return value; - } - if (typeof value === 'number') { - if (Number.isNaN(value)) return defaultValue; - return value !== 0; - } - if (typeof value === 'string') { - const normalized = value.toLowerCase().trim(); - if (['true', '1', 'yes', 'on'].includes(normalized)) return true; - if (['false', '0', 'no', 'off', ''].includes(normalized)) return false; - } - return defaultValue; -}; - -/** - * Parse numeric input with validation - */ -export const toNumber = ( - value: unknown, - defaultValue: number, - options?: { min?: number; max?: number } -): number => { - if (value === undefined || value === null || value === '') { - return defaultValue; - } - const parsed = Number(value); - if (!Number.isFinite(parsed)) { - return defaultValue; - } - if (options?.min !== undefined && parsed < options.min) return options.min; - if (options?.max !== undefined && parsed > options.max) return options.max; - return parsed; -}; -``` - -#### Migration Steps - -1. Create new utility files -2. Add comprehensive unit tests for all parser functions -3. Update imports in affected files one at a time: - - `adminEvents.js`: Replace inline function with import - - `events.js`: Replace inline function with import - - `SettingsPage.tsx`: Replace inline function with import -4. Remove duplicate function definitions -5. Run full test suite -6. Deploy - ---- - -### 1.2 Consolidate Auth Routes - -**Problem**: Three versions of auth routes exist - -**Files**: -- `backend/src/routes/auth.js` (136 lines) - Basic version -- `backend/src/routes/auth-enhanced.js` (395 lines) - Enhanced v1 -- `backend/src/routes/auth-enhanced-v2.js` (393 lines) - Enhanced v2 - -**Analysis Required**: -```bash -# Determine which version is actively used -grep -r "auth-enhanced" backend/src/ --include="*.js" -grep -r "auth.js" backend/src/ --include="*.js" -``` - -**Solution**: Consolidate into single `auth.js` - -#### Implementation Plan - -1. **Audit Current Usage** - - Identify which auth file is mounted in `server.js` - - Document all endpoints from each version - - Create feature comparison matrix - -2. **Merge Strategy** - ``` - auth.js (NEW - Consolidated) - ├── All endpoints from auth-enhanced-v2.js (active version) - ├── Any unique endpoints from other versions - └── Deprecated endpoints marked for removal - ``` - -3. **Create Feature Flags** (if needed) - ```javascript - // config/features.js - module.exports = { - AUTH_USE_ENHANCED_TOKENS: true, - AUTH_REQUIRE_EMAIL_VERIFICATION: false, - }; - ``` - -4. **Migration Steps** - - Create new consolidated `auth.js` - - Update `server.js` to use new auth routes - - Keep old files temporarily with deprecation notices - - Monitor for errors in production - - Remove deprecated files after 2 weeks - ---- - -### 1.3 Consolidate CreateEvent Pages - -**Problem**: Two nearly identical event creation pages - -**Files**: -- `frontend/src/pages/admin/CreateEventPage.tsx` (709 lines) -- `frontend/src/pages/admin/CreateEventPageEnhanced.tsx` (709 lines) - -**Solution**: Single `CreateEventPage.tsx` with all features - -#### Difference Analysis - -| Feature | Standard | Enhanced | -|---------|----------|----------| -| Basic form fields | ✓ | ✓ | -| Theme customizer | ✓ | ✓ | -| Password generator | ✓ | ✓ | -| Advanced validation | ? | ✓ | -| Auto-save draft | ? | ✓ | - -#### Implementation Plan - -1. **Diff the files** to identify exact differences: - ```bash - diff -u CreateEventPage.tsx CreateEventPageEnhanced.tsx > event_pages_diff.txt - ``` - -2. **Merge enhanced features into standard page** - -3. **Extract shared logic into custom hooks**: - ```typescript - // hooks/useEventForm.ts - export function useEventForm(initialData?: Partial) { - // Form state management - // Validation logic - // Submit handlers - } - - // hooks/useEventDraft.ts - export function useEventDraft(eventData: Partial) { - // Auto-save logic - // Draft recovery - } - ``` - -4. **Update router to use single page** - -5. **Delete duplicate file** - ---- - -### 1.4 Consolidate CMS Pages - -**Problem**: Two CMS page implementations - -**Files**: -- `frontend/src/pages/admin/CMSPage.tsx` (538 lines) -- `frontend/src/pages/admin/CMSPageEnhanced.tsx` (618 lines) - -**Enhanced Features**: -- Auto-save functionality -- Unsaved changes warning -- Enhanced editor toolbar - -**Solution**: Single `CMSPage.tsx` with optional features - -#### Implementation Plan - -1. **Create feature-complete CMSPage.tsx**: - ```typescript - interface CMSPageProps { - enableAutoSave?: boolean; - enableUnsavedWarning?: boolean; - } - - export const CMSPage: React.FC = ({ - enableAutoSave = true, - enableUnsavedWarning = true - }) => { - // Merged implementation - }; - ``` - -2. **Extract reusable hooks**: - ```typescript - // hooks/useAutoSave.ts - export function useAutoSave( - data: T, - saveFn: (data: T) => Promise, - debounceMs = 2000 - ) { ... } - - // hooks/useUnsavedChanges.ts - export function useUnsavedChanges(hasChanges: boolean) { ... } - ``` - -3. **Delete duplicate file** - ---- - -### 1.5 Extract Multer Configuration Factory - -**Problem**: Multer storage configuration duplicated across multiple route files - -**Files with Multer configs**: -- `adminPhotos.js` -- `adminSettings.js` -- `adminEvents.js` -- `protectedImages.js` - -**Solution**: Centralized multer configuration factory - -#### Implementation - -**Create**: `backend/src/config/multerConfig.js` - -```javascript -const multer = require('multer'); -const path = require('path'); -const crypto = require('crypto'); -const fs = require('fs').promises; - -/** - * Create disk storage configuration - * @param {Object} options - * @param {string} options.destination - Upload directory path - * @param {Function} options.filenameGenerator - Custom filename generator - */ -const createDiskStorage = ({ destination, filenameGenerator }) => { - return multer.diskStorage({ - destination: async (req, file, cb) => { - try { - await fs.mkdir(destination, { recursive: true }); - cb(null, destination); - } catch (error) { - cb(error); - } - }, - filename: filenameGenerator || ((req, file, cb) => { - const uniqueSuffix = `${Date.now()}-${crypto.randomBytes(6).toString('hex')}`; - const ext = path.extname(file.originalname).toLowerCase(); - cb(null, `${uniqueSuffix}${ext}`); - }) - }); -}; - -/** - * Create multer upload instance with standard limits - * @param {Object} options - * @param {multer.StorageEngine} options.storage - Multer storage engine - * @param {number} options.maxFileSize - Max file size in bytes (default 50MB) - * @param {string[]} options.allowedMimeTypes - Allowed MIME types - * @param {number} options.maxFiles - Max files per request (default 100) - */ -const createUpload = ({ - storage, - maxFileSize = 50 * 1024 * 1024, - allowedMimeTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'], - maxFiles = 100 -}) => { - return multer({ - storage, - limits: { - fileSize: maxFileSize, - files: maxFiles - }, - fileFilter: (req, file, cb) => { - if (allowedMimeTypes.includes(file.mimetype)) { - cb(null, true); - } else { - cb(new Error(`File type ${file.mimetype} not allowed`)); - } - } - }); -}; - -/** - * Pre-configured uploads for common use cases - */ -const uploads = { - photos: (storagePath) => createUpload({ - storage: createDiskStorage({ destination: storagePath }), - allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp', 'video/mp4', 'video/webm', 'video/quicktime'], - maxFiles: 500 - }), - - logos: (storagePath) => createUpload({ - storage: createDiskStorage({ destination: storagePath }), - allowedMimeTypes: ['image/jpeg', 'image/png', 'image/svg+xml', 'image/webp'], - maxFileSize: 5 * 1024 * 1024, - maxFiles: 1 - }), - - general: (storagePath) => createUpload({ - storage: createDiskStorage({ destination: storagePath }) - }) -}; - -module.exports = { - createDiskStorage, - createUpload, - uploads -}; -``` - ---- - -## Phase 2: Backend Route & Service Refactoring - -> **Priority**: HIGH -> **Estimated Effort**: 5-7 days -> **Risk**: Medium -> **Dependencies**: Phase 1 complete - -### 2.1 Create Event Service Layer - -**Current**: `adminEvents.js` (1,088 lines) with business logic in route handlers - -**Target**: Thin routes + dedicated EventService - -#### New File Structure - -``` -backend/src/ -├── routes/ -│ └── adminEvents.js # ~200 lines (HTTP only) -├── services/ -│ └── events/ -│ ├── index.js # Public exports -│ ├── eventService.js # Main service (~300 lines) -│ ├── eventValidator.js # Validation logic (~100 lines) -│ ├── eventMapper.js # Data transformation (~80 lines) -│ └── eventRepository.js # Database queries (~150 lines) -└── utils/ - └── parsers.js # Shared parsers -``` - -#### EventService Implementation - -**Create**: `backend/src/services/events/eventService.js` - -```javascript -const { db, logActivity } = require('../../database/db'); -const eventRepository = require('./eventRepository'); -const eventValidator = require('./eventValidator'); -const eventMapper = require('./eventMapper'); -const { queueEmail } = require('../emailProcessor'); -const { buildShareLinkVariants } = require('../shareLinkService'); -const logger = require('../../utils/logger'); - -class EventService { - /** - * Create a new event - * @param {Object} eventData - Event creation data - * @param {Object} admin - Admin user creating the event - * @returns {Promise} Created event - */ - async createEvent(eventData, admin) { - // Validate input - const validation = await eventValidator.validateCreate(eventData); - if (!validation.valid) { - throw new ValidationError(validation.errors); - } - - // Transform data - const eventRecord = eventMapper.toDatabase(eventData); - - // Generate slug and share link - const slug = this.generateSlug(eventData); - const shareToken = crypto.randomBytes(16).toString('hex'); - const { shareLinkToStore } = await buildShareLinkVariants({ slug, shareToken }); - - // Create in database - const [event] = await eventRepository.create({ - ...eventRecord, - slug, - share_token: shareToken, - share_link: shareLinkToStore, - created_by: admin.id - }); - - // Create storage folder - await this.createEventFolder(slug); - - // Queue welcome email - if (eventData.customer_email) { - await queueEmail(event.id, eventData.customer_email, 'event_created', { - event_name: event.event_name, - gallery_link: shareLinkToStore - }); - } - - // Log activity - await logActivity('event_created', 'admin', admin.id, admin.username, { - event_id: event.id, - event_name: event.event_name - }); - - return eventMapper.toApi(event); - } - - /** - * Update an existing event - */ - async updateEvent(eventId, updateData, admin) { - const event = await eventRepository.findById(eventId); - if (!event) { - throw new NotFoundError('Event not found'); - } - - const validation = await eventValidator.validateUpdate(updateData, event); - if (!validation.valid) { - throw new ValidationError(validation.errors); - } - - const updatedEvent = await eventRepository.update(eventId, - eventMapper.toDatabase(updateData) - ); - - await logActivity('event_updated', 'admin', admin.id, admin.username, { - event_id: eventId, - changes: updateData - }); - - return eventMapper.toApi(updatedEvent); - } - - /** - * Archive an event - */ - async archiveEvent(eventId, admin) { - // Implementation - } - - /** - * Delete an event - */ - async deleteEvent(eventId, admin) { - // Implementation - } - - /** - * List events with filtering - */ - async listEvents(filters, pagination) { - const events = await eventRepository.findMany(filters, pagination); - return events.map(eventMapper.toApi); - } -} - -module.exports = new EventService(); -``` - -#### Refactored Route Handler - -**Update**: `backend/src/routes/adminEvents.js` - -```javascript -const express = require('express'); -const { body, query, validationResult } = require('express-validator'); -const { adminAuth } = require('../middleware/auth-enhanced-v2'); -const eventService = require('../services/events'); -const { handleAsync } = require('../utils/routeHelpers'); - -const router = express.Router(); - -// Apply admin auth to all routes -router.use(adminAuth); - -/** - * GET /api/admin/events - * List all events with optional filtering - */ -router.get('/', [ - query('status').optional().isIn(['active', 'archived', 'expiring']), - query('search').optional().isString(), - query('page').optional().isInt({ min: 1 }), - query('limit').optional().isInt({ min: 1, max: 100 }) -], handleAsync(async (req, res) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - const events = await eventService.listEvents(req.query, { - page: req.query.page || 1, - limit: req.query.limit || 20 - }); - - res.json(events); -})); - -/** - * POST /api/admin/events - * Create a new event - */ -router.post('/', [ - body('event_name').isString().trim().isLength({ min: 3, max: 200 }), - body('event_type').isIn(['wedding', 'birthday', 'corporate', 'other']), - body('event_date').isISO8601(), - body('customer_email').optional().isEmail(), - body('password').optional().isLength({ min: 6 }) -], handleAsync(async (req, res) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - return res.status(400).json({ errors: errors.array() }); - } - - const event = await eventService.createEvent(req.body, req.admin); - res.status(201).json(event); -})); - -/** - * PUT /api/admin/events/:id - * Update an event - */ -router.put('/:id', handleAsync(async (req, res) => { - const event = await eventService.updateEvent( - parseInt(req.params.id), - req.body, - req.admin - ); - res.json(event); -})); - -/** - * DELETE /api/admin/events/:id - * Delete an event - */ -router.delete('/:id', handleAsync(async (req, res) => { - await eventService.deleteEvent(parseInt(req.params.id), req.admin); - res.status(204).send(); -})); - -/** - * POST /api/admin/events/:id/archive - * Archive an event - */ -router.post('/:id/archive', handleAsync(async (req, res) => { - const result = await eventService.archiveEvent( - parseInt(req.params.id), - req.admin - ); - res.json(result); -})); - -module.exports = router; -``` - ---- - -### 2.2 Create Photo Service Layer - -**Current**: `adminPhotos.js` (1,005 lines) - -**Target**: Similar structure to EventService - -#### New File Structure - -``` -backend/src/services/photos/ -├── index.js -├── photoService.js # Main service -├── photoProcessor.js # Image/video processing -├── photoRepository.js # Database queries -├── photoStorage.js # File system operations -└── photoValidator.js # Validation -``` - ---- - -### 2.3 Create Settings Service Layer - -**Current**: `adminSettings.js` (1,056 lines) - -**Target**: Domain-specific services - -#### New File Structure - -``` -backend/src/services/settings/ -├── index.js -├── settingsService.js # Main orchestrator -├── brandingService.js # Logo, favicon, company info -├── themeService.js # Theme configuration -├── securityService.js # Security settings -└── settingsRepository.js # Database queries -``` - ---- - -### 2.4 Decompose Backup/Restore Services - -**Current**: -- `backupService.js` (1,120 lines) -- `restoreService.js` (1,220 lines) - -**Target**: Smaller, focused classes - -#### New File Structure - -``` -backend/src/services/backup/ -├── index.js -├── BackupOrchestrator.js # Main coordinator (~200 lines) -├── BackupScheduler.js # Cron job management (~150 lines) -├── BackupExecutor.js # Backup creation (~300 lines) -├── BackupUploader.js # S3/remote upload (~200 lines) -├── BackupValidator.js # Validation logic (~100 lines) -└── BackupNotifier.js # Email notifications (~100 lines) - -backend/src/services/restore/ -├── index.js -├── RestoreOrchestrator.js # Main coordinator (~200 lines) -├── RestoreValidator.js # Pre-restore validation (~200 lines) -├── RestoreExecutor.js # Actual restore logic (~300 lines) -├── RestoreRollback.js # Rollback on failure (~150 lines) -└── RestoreLogger.js # Detailed logging (~100 lines) -``` - ---- - -## Phase 3: Frontend Page Decomposition - -> **Priority**: HIGH -> **Estimated Effort**: 5-7 days -> **Risk**: Medium -> **Dependencies**: Phase 1 complete - -### 3.1 Decompose SettingsPage - -**Current**: `SettingsPage.tsx` (1,839 lines) with 8 tabs and 27 hooks - -**Target**: Composition-based page with tab components - -#### New File Structure - -``` -frontend/src/features/settings/ -├── index.ts # Public exports -├── SettingsPage.tsx # Main page (~100 lines) -├── SettingsLayout.tsx # Tab layout component -├── tabs/ -│ ├── index.ts -│ ├── GeneralSettingsTab.tsx # ~200 lines -│ ├── EventSettingsTab.tsx # ~150 lines -│ ├── StatusTab.tsx # ~200 lines -│ ├── SecuritySettingsTab.tsx # ~200 lines -│ ├── CategoriesTab.tsx # ~100 lines -│ ├── AnalyticsSettingsTab.tsx # ~150 lines -│ ├── ModerationTab.tsx # ~100 lines -│ └── StylingTab.tsx # ~100 lines -├── hooks/ -│ ├── index.ts -│ ├── useGeneralSettings.ts # Settings query/mutation -│ ├── useSecuritySettings.ts -│ ├── useAnalyticsSettings.ts -│ ├── useSystemStatus.ts -│ └── useAdminProfile.ts -└── components/ - ├── AccountForm.tsx - ├── FeatureToggles.tsx - ├── StorageIndicator.tsx - └── SystemStatusCard.tsx -``` - -#### Refactored SettingsPage - -```typescript -// features/settings/SettingsPage.tsx -import React, { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { SettingsLayout } from './SettingsLayout'; -import { - GeneralSettingsTab, - EventSettingsTab, - StatusTab, - SecuritySettingsTab, - CategoriesTab, - AnalyticsSettingsTab, - ModerationTab, - StylingTab -} from './tabs'; - -type SettingsTab = - | 'general' - | 'events' - | 'status' - | 'security' - | 'categories' - | 'analytics' - | 'moderation' - | 'styling'; - -export const SettingsPage: React.FC = () => { - const { t } = useTranslation(); - const [activeTab, setActiveTab] = useState('general'); - - const tabs = [ - { id: 'general', label: t('settings.general.title') }, - { id: 'events', label: t('settings.events.title') }, - { id: 'status', label: t('settings.status.title') }, - { id: 'security', label: t('settings.security.title') }, - { id: 'categories', label: t('settings.categories.title') }, - { id: 'analytics', label: t('settings.analytics.title') }, - { id: 'moderation', label: t('settings.moderation.title') }, - { id: 'styling', label: t('settings.styling.title') }, - ] as const; - - const renderTabContent = () => { - switch (activeTab) { - case 'general': - return ; - case 'events': - return ; - case 'status': - return ; - case 'security': - return ; - case 'categories': - return ; - case 'analytics': - return ; - case 'moderation': - return ; - case 'styling': - return ; - } - }; - - return ( - - {renderTabContent()} - - ); -}; -``` - -#### Example Tab Component - -```typescript -// features/settings/tabs/GeneralSettingsTab.tsx -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { Card } from '../../../components/common'; -import { - useGeneralSettings, - useAdminProfile, - useSaveGeneralSettings -} from '../hooks'; -import { AccountForm } from '../components/AccountForm'; -import { FeatureToggles } from '../components/FeatureToggles'; -import { LanguageSelector } from '../components/LanguageSelector'; -import { DateFormatSelector } from '../components/DateFormatSelector'; - -export const GeneralSettingsTab: React.FC = () => { - const { t } = useTranslation(); - const { data: settings, isLoading } = useGeneralSettings(); - const { data: profile } = useAdminProfile(); - const saveMutation = useSaveGeneralSettings(); - - if (isLoading) { - return ; - } - - return ( -
- -

- {t('settings.general.accountSection')} -

- saveMutation.mutate(data)} - isSaving={saveMutation.isPending} - /> -
- - -

- {t('settings.general.features')} -

- -
- - -

- {t('settings.general.localization')} -

- - -
-
- ); -}; -``` - ---- - -### 3.2 Decompose EventDetailsPage - -**Current**: `EventDetailsPage.tsx` (1,479 lines) with 33 hooks - -**Target**: Section-based composition - -#### New File Structure - -``` -frontend/src/features/events/ -├── index.ts -├── EventDetailsPage.tsx # Main page (~150 lines) -├── sections/ -│ ├── index.ts -│ ├── EventInfoSection.tsx # ~200 lines -│ ├── PhotoManagementSection.tsx # ~250 lines -│ ├── ThemeSection.tsx # ~150 lines -│ ├── FeedbackSection.tsx # ~150 lines -│ ├── ShareLinkSection.tsx # ~150 lines -│ └── ActionsSection.tsx # ~100 lines -├── hooks/ -│ ├── index.ts -│ ├── useEventDetails.ts -│ ├── useEventPhotos.ts -│ ├── useEventTheme.ts -│ ├── useEventFeedback.ts -│ └── useEventActions.ts -└── components/ - ├── EventHeader.tsx - ├── EventStats.tsx - ├── PhotoGrid.tsx - └── PhotoViewer.tsx -``` - ---- - -### 3.3 Decompose Large Components - -#### ThemeCustomizerEnhanced (607 lines) - -**Split into**: -``` -frontend/src/components/admin/theme/ -├── index.ts -├── ThemeCustomizer.tsx # Main component (~150 lines) -├── ColorEditor.tsx # Color pickers (~150 lines) -├── TypographyEditor.tsx # Font selection (~100 lines) -├── LayoutSelector.tsx # Gallery layout (~100 lines) -├── PresetSelector.tsx # Theme presets (~80 lines) -└── ThemePreview.tsx # Live preview (~100 lines) -``` - -#### CMSEditor (572 lines) - -**Split into**: -``` -frontend/src/components/admin/cms/ -├── index.ts -├── CMSEditor.tsx # Main editor (~150 lines) -├── EditorToolbar.tsx # Formatting toolbar (~150 lines) -├── EditorPreview.tsx # Preview pane (~80 lines) -├── ViewModeSelector.tsx # Edit/Preview/Split (~60 lines) -├── EditorStats.tsx # Word/char count (~50 lines) -└── EditorHelp.tsx # Help modal (~80 lines) -``` - -#### AdminPhotoViewer (515 lines) - -**Split into**: -``` -frontend/src/components/admin/photos/ -├── index.ts -├── PhotoViewer.tsx # Main viewer (~150 lines) -├── PhotoNavigation.tsx # Prev/next controls (~80 lines) -├── PhotoMetadata.tsx # File info display (~100 lines) -├── PhotoFeedback.tsx # Ratings/comments (~100 lines) -├── PhotoActions.tsx # Download/delete (~80 lines) -└── hooks/ - ├── usePhotoNavigation.ts - └── usePhotoFeedback.ts -``` - ---- - -## Phase 4: Cross-Cutting Concerns - -> **Priority**: MEDIUM -> **Estimated Effort**: 2-3 days -> **Dependencies**: Phases 1-3 complete - -### 4.1 Create Route Helpers Utility - -**Create**: `backend/src/utils/routeHelpers.js` - -```javascript -/** - * Async route handler wrapper - * Catches errors and passes to error middleware - */ -const handleAsync = (fn) => (req, res, next) => { - Promise.resolve(fn(req, res, next)).catch(next); -}; - -/** - * Validate request and return early if invalid - */ -const validateRequest = (req, res) => { - const errors = validationResult(req); - if (!errors.isEmpty()) { - res.status(400).json({ errors: errors.array() }); - return false; - } - return true; -}; - -/** - * Standard success response - */ -const successResponse = (res, data, statusCode = 200) => { - res.status(statusCode).json({ - success: true, - data - }); -}; - -/** - * Standard error response - */ -const errorResponse = (res, message, statusCode = 500, details = null) => { - res.status(statusCode).json({ - success: false, - error: message, - details - }); -}; - -module.exports = { - handleAsync, - validateRequest, - successResponse, - errorResponse -}; -``` - -### 4.2 Create Custom Error Classes - -**Create**: `backend/src/utils/errors.js` - -```javascript -class AppError extends Error { - constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') { - super(message); - this.statusCode = statusCode; - this.code = code; - this.isOperational = true; - Error.captureStackTrace(this, this.constructor); - } -} - -class ValidationError extends AppError { - constructor(errors) { - super('Validation failed', 400, 'VALIDATION_ERROR'); - this.errors = errors; - } -} - -class NotFoundError extends AppError { - constructor(resource = 'Resource') { - super(`${resource} not found`, 404, 'NOT_FOUND'); - } -} - -class UnauthorizedError extends AppError { - constructor(message = 'Unauthorized') { - super(message, 401, 'UNAUTHORIZED'); - } -} - -class ForbiddenError extends AppError { - constructor(message = 'Forbidden') { - super(message, 403, 'FORBIDDEN'); - } -} - -class ConflictError extends AppError { - constructor(message = 'Resource conflict') { - super(message, 409, 'CONFLICT'); - } -} - -module.exports = { - AppError, - ValidationError, - NotFoundError, - UnauthorizedError, - ForbiddenError, - ConflictError -}; -``` - -### 4.3 Create Global Error Handler - -**Update**: `backend/src/middleware/errorHandler.js` - -```javascript -const logger = require('../utils/logger'); -const { AppError } = require('../utils/errors'); - -const errorHandler = (err, req, res, next) => { - // Log error - logger.error('Request error', { - error: err.message, - stack: err.stack, - path: req.path, - method: req.method, - body: req.body, - user: req.admin?.id - }); - - // Operational errors (expected) - if (err instanceof AppError && err.isOperational) { - return res.status(err.statusCode).json({ - success: false, - error: err.message, - code: err.code, - ...(err.errors && { errors: err.errors }) - }); - } - - // Multer errors - if (err.code === 'LIMIT_FILE_SIZE') { - return res.status(400).json({ - success: false, - error: 'File too large', - code: 'FILE_TOO_LARGE' - }); - } - - // Database errors - if (err.code === 'SQLITE_CONSTRAINT' || err.code === '23505') { - return res.status(409).json({ - success: false, - error: 'Resource already exists', - code: 'DUPLICATE_ENTRY' - }); - } - - // Unknown errors - don't leak details - res.status(500).json({ - success: false, - error: 'An unexpected error occurred', - code: 'INTERNAL_ERROR' - }); -}; - -module.exports = errorHandler; -``` - ---- - -## Testing Strategy - -### Unit Tests Required - -#### Backend - -| File | Test File | Coverage Target | -|------|-----------|-----------------| -| `utils/parsers.js` | `parsers.test.js` | 100% | -| `services/events/eventService.js` | `eventService.test.js` | 90% | -| `services/events/eventValidator.js` | `eventValidator.test.js` | 100% | -| `services/photos/photoService.js` | `photoService.test.js` | 90% | -| `services/settings/settingsService.js` | `settingsService.test.js` | 90% | -| `services/backup/BackupOrchestrator.js` | `BackupOrchestrator.test.js` | 85% | -| `services/restore/RestoreOrchestrator.js` | `RestoreOrchestrator.test.js` | 85% | - -#### Frontend - -| Component | Test File | Coverage Target | -|-----------|-----------|-----------------| -| `utils/parsers.ts` | `parsers.test.ts` | 100% | -| `features/settings/hooks/*` | `*.test.ts` | 90% | -| `features/settings/tabs/*` | `*.test.tsx` | 80% | -| `features/events/hooks/*` | `*.test.ts` | 90% | - -### Integration Tests Required - -| Scenario | Test File | -|----------|-----------| -| Event CRUD operations | `events.integration.test.js` | -| Photo upload flow | `photos.integration.test.js` | -| Settings management | `settings.integration.test.js` | -| Backup/Restore cycle | `backup.integration.test.js` | - -### E2E Tests Required - -| Flow | Test File | -|------|-----------| -| Create event end-to-end | `create-event.e2e.ts` | -| Settings page navigation | `settings.e2e.ts` | -| Photo management | `photos.e2e.ts` | - ---- - -## Migration & Rollback Plan - -### Pre-Migration Checklist - -- [ ] All existing tests pass -- [ ] Database backup created -- [ ] Feature flags configured -- [ ] Rollback scripts prepared -- [ ] Monitoring alerts configured - -### Phase Rollout Strategy - -``` -┌─────────────────────────────────────────────────────────────┐ -│ ROLLOUT TIMELINE │ -├─────────────────────────────────────────────────────────────┤ -│ │ -│ Week 1: Phase 1 (Code Duplication) │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ Day 1-2: Create shared utilities │ │ -│ │ Day 3: Update imports, run tests │ │ -│ │ Day 4: Deploy to staging │ │ -│ │ Day 5: Monitor, fix issues, deploy to production │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ -│ Week 2-3: Phase 2 (Backend Services) │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ Day 1-3: EventService extraction │ │ -│ │ Day 4-5: PhotoService extraction │ │ -│ │ Day 6-7: SettingsService extraction │ │ -│ │ Day 8-10: Backup/Restore decomposition │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ -│ Week 4-5: Phase 3 (Frontend Decomposition) │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ Day 1-3: SettingsPage decomposition │ │ -│ │ Day 4-5: EventDetailsPage decomposition │ │ -│ │ Day 6-7: Component decomposition │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ -│ Week 6: Phase 4 (Cross-Cutting) + Cleanup │ -│ ┌─────────────────────────────────────────────────────┐ │ -│ │ Day 1-2: Error handling, route helpers │ │ -│ │ Day 3-4: Delete deprecated files │ │ -│ │ Day 5: Final testing, documentation │ │ -│ └─────────────────────────────────────────────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────┘ -``` - -### Rollback Procedures - -#### Phase 1 Rollback -```bash -# Revert parser utility changes -git revert -# No database changes required -``` - -#### Phase 2 Rollback -```bash -# Service layer changes are additive -# Routes can be reverted without data loss -git revert -# Restart backend services -docker-compose restart backend -``` - -#### Phase 3 Rollback -```bash -# Frontend changes are purely presentational -git revert -# Rebuild frontend -docker-compose build frontend -docker-compose restart frontend -``` - ---- - -## Implementation Checklist - -### Phase 1: Code Duplication (Days 1-3) - -- [ ] **1.1 Shared Parsers** - - [ ] Create `backend/src/utils/parsers.js` - - [ ] Create `frontend/src/utils/parsers.ts` - - [ ] Write unit tests (100% coverage) - - [ ] Update `adminEvents.js` imports - - [ ] Update `events.js` imports - - [ ] Update `SettingsPage.tsx` imports - - [ ] Delete inline function definitions - - [ ] Run full test suite - -- [ ] **1.2 Auth Routes Consolidation** - - [ ] Audit current auth route usage - - [ ] Create consolidated `auth.js` - - [ ] Update `server.js` mount point - - [ ] Add deprecation notices to old files - - [ ] Test auth flows - - [ ] Schedule old file deletion - -- [ ] **1.3 CreateEvent Page Consolidation** - - [ ] Diff files to identify differences - - [ ] Create `hooks/useEventForm.ts` - - [ ] Merge enhanced features into standard - - [ ] Update router - - [ ] Delete duplicate file - -- [ ] **1.4 CMS Page Consolidation** - - [ ] Diff files to identify differences - - [ ] Create `hooks/useAutoSave.ts` - - [ ] Create `hooks/useUnsavedChanges.ts` - - [ ] Merge into single CMSPage - - [ ] Delete duplicate file - -- [ ] **1.5 Multer Config Factory** - - [ ] Create `config/multerConfig.js` - - [ ] Update `adminPhotos.js` - - [ ] Update `adminSettings.js` - - [ ] Update other files using multer - - [ ] Test all upload flows - -### Phase 2: Backend Services (Days 4-10) - -- [ ] **2.1 Event Service** - - [ ] Create `services/events/` directory structure - - [ ] Implement `eventService.js` - - [ ] Implement `eventRepository.js` - - [ ] Implement `eventValidator.js` - - [ ] Implement `eventMapper.js` - - [ ] Refactor `adminEvents.js` to use service - - [ ] Write unit tests (90% coverage) - - [ ] Write integration tests - -- [ ] **2.2 Photo Service** - - [ ] Create `services/photos/` directory structure - - [ ] Implement service layer - - [ ] Refactor `adminPhotos.js` - - [ ] Write tests - -- [ ] **2.3 Settings Service** - - [ ] Create `services/settings/` directory structure - - [ ] Implement service layer - - [ ] Refactor `adminSettings.js` - - [ ] Write tests - -- [ ] **2.4 Backup/Restore Decomposition** - - [ ] Create `services/backup/` directory structure - - [ ] Split `backupService.js` into components - - [ ] Create `services/restore/` directory structure - - [ ] Split `restoreService.js` into components - - [ ] Write tests - -### Phase 3: Frontend Decomposition (Days 11-17) - -- [ ] **3.1 Settings Page** - - [ ] Create `features/settings/` directory structure - - [ ] Extract tab components - - [ ] Create custom hooks - - [ ] Refactor main page - - [ ] Write tests - -- [ ] **3.2 Event Details Page** - - [ ] Create `features/events/` directory structure - - [ ] Extract section components - - [ ] Create custom hooks - - [ ] Refactor main page - - [ ] Write tests - -- [ ] **3.3 Component Decomposition** - - [ ] Split `ThemeCustomizerEnhanced` - - [ ] Split `CMSEditor` - - [ ] Split `AdminPhotoViewer` - - [ ] Write tests - -### Phase 4: Cross-Cutting (Days 18-20) - -- [ ] **4.1 Route Helpers** - - [ ] Create `utils/routeHelpers.js` - - [ ] Update routes to use helpers - - [ ] Write tests - -- [ ] **4.2 Error Classes** - - [ ] Create `utils/errors.js` - - [ ] Create global error handler - - [ ] Update services to throw typed errors - - [ ] Write tests - -- [ ] **4.3 Cleanup** - - [ ] Delete deprecated auth route files - - [ ] Delete duplicate page files - - [ ] Update documentation - - [ ] Final review - -### Post-Implementation - -- [ ] Update README with new architecture -- [ ] Create architecture diagram -- [ ] Conduct code review -- [ ] Performance benchmarking -- [ ] Monitor error rates for 1 week - ---- - -## Success Metrics - -| Metric | Current | Target | -|--------|---------|--------| -| Largest file (lines) | 1,839 | < 400 | -| Average file size | ~500 | < 200 | -| Code duplication | ~3,500 lines | 0 | -| Test coverage | ~60% | > 85% | -| Build time | - | No increase | -| Bundle size | - | No increase | - ---- - -## Appendix: File Impact Summary - -### Files to Create - -| Path | Purpose | Est. Lines | -|------|---------|------------| -| `backend/src/utils/parsers.js` | Shared parsers | 80 | -| `backend/src/utils/routeHelpers.js` | Route utilities | 50 | -| `backend/src/utils/errors.js` | Error classes | 60 | -| `backend/src/config/multerConfig.js` | Upload config | 100 | -| `backend/src/services/events/*` | Event service layer | 600 | -| `backend/src/services/photos/*` | Photo service layer | 500 | -| `backend/src/services/settings/*` | Settings service layer | 400 | -| `backend/src/services/backup/*` | Backup components | 800 | -| `backend/src/services/restore/*` | Restore components | 700 | -| `frontend/src/utils/parsers.ts` | Shared parsers | 50 | -| `frontend/src/features/settings/*` | Settings feature | 1,200 | -| `frontend/src/features/events/*` | Events feature | 1,000 | - -### Files to Modify - -| Path | Change Type | -|------|-------------| -| `backend/src/routes/adminEvents.js` | Reduce to ~200 lines | -| `backend/src/routes/adminPhotos.js` | Reduce to ~200 lines | -| `backend/src/routes/adminSettings.js` | Reduce to ~200 lines | -| `backend/server.js` | Update route imports | -| `frontend/src/pages/admin/SettingsPage.tsx` | Reduce to ~100 lines | -| `frontend/src/pages/admin/EventDetailsPage.tsx` | Reduce to ~150 lines | - -### Files to Delete - -| Path | Reason | -|------|--------| -| `backend/src/routes/auth-enhanced.js` | Consolidated into auth.js | -| `backend/src/routes/auth-enhanced-v2.js` | Consolidated into auth.js | -| `frontend/src/pages/admin/CreateEventPageEnhanced.tsx` | Merged into CreateEventPage | -| `frontend/src/pages/admin/CMSPageEnhanced.tsx` | Merged into CMSPage | - ---- - -*Document maintained by: Development Team* -*Last updated: January 2026* diff --git a/docs/feature-custom-css-templates.md b/docs/feature-custom-css-templates.md deleted file mode 100644 index 0dc5ed3e..00000000 --- a/docs/feature-custom-css-templates.md +++ /dev/null @@ -1,1263 +0,0 @@ -# Feature: Custom CSS Gallery Templates - -## Overview - -This feature allows administrators to create and manage up to 3 custom CSS templates for gallery styling. Each template can be edited via a tabbed interface in the admin panel and selected when creating or editing events. The first template slot includes a working example template. - -**GitHub Issue:** Gallery Templates - Additional gallery layouts and themes - ---- - -## Problem Statement - -Currently, gallery styling is limited to the built-in theme system with predefined color schemes. Photographers and administrators need: -- Full CSS customization capabilities for unique branding -- Ability to create multiple reusable style templates -- Easy switching between templates for different event types -- Professional-grade CSS editing experience - ---- - -## User Story - -As an administrator, I want to create custom CSS templates for my galleries so that I can offer unique visual experiences for different clients and event types while maintaining my brand identity. - ---- - -## Feature Requirements - -### 1. Custom CSS Template System - -#### 1.1 Template Slots -- **3 custom CSS template slots** available system-wide -- Each slot has: - - Name/label (editable, max 50 characters) - - CSS content (unlimited, stored as TEXT) - - Active/enabled toggle - - Last modified timestamp - -#### 1.2 Default Template (Slot 1) -- Pre-populated with a working example template -- Demonstrates available CSS custom properties -- Can be modified but shows "Reset to Default" option - -### 2. Admin UI - Template Editor - -#### 2.1 Location -**Path:** Admin Panel → Settings → Styling → Custom CSS Templates (new tab) - -#### 2.2 Tabbed Interface -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Settings > Styling │ -├─────────────────────────────────────────────────────────────────┤ -│ [Theme] [Colors] [Custom CSS Templates] ← NEW TAB │ -├─────────────────────────────────────────────────────────────────┤ -│ │ -│ ┌──────────────┬──────────────┬──────────────┐ │ -│ │ Template 1 │ Template 2 │ Template 3 │ │ -│ │ (Elegant) │ (Untitled) │ (Untitled) │ │ -│ └──────────────┴──────────────┴──────────────┘ │ -│ │ -│ Template Name: [Elegant Dark_______________] │ -│ │ -│ [✓] Enable this template │ -│ │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ /* Elegant Dark Theme */ │ │ -│ │ :root { │ │ -│ │ --gallery-bg: #1a1a2e; │ │ -│ │ --gallery-text: #eaeaea; │ │ -│ │ --gallery-accent: #e94560; │ │ -│ │ } │ │ -│ │ │ │ -│ │ .gallery-container { │ │ -│ │ background: var(--gallery-bg); │ │ -│ │ } │ │ -│ │ ... │ │ -│ └─────────────────────────────────────────────────────────┘ │ -│ │ -│ CSS Editor (CodeMirror) │ -│ Line: 1 Col: 1 | Valid CSS ✓ │ -│ │ -│ [Preview] [Reset to Default*] [Save Template] │ -│ │ -│ * Only shown for Template 1 │ -└─────────────────────────────────────────────────────────────────┘ -``` - -#### 2.3 CSS Editor Features -- **CodeMirror integration** with CSS mode -- Syntax highlighting -- Auto-completion for CSS properties -- Line numbers -- Real-time CSS validation -- Error highlighting -- Find/replace functionality -- Bracket matching - -### 3. Event Integration - -#### 3.1 Event Creation/Edit Form -Add template selector dropdown: - -``` -┌─────────────────────────────────────────────────────────────────┐ -│ Event Details │ -├─────────────────────────────────────────────────────────────────┤ -│ ... │ -│ │ -│ Gallery Styling │ -│ ┌─────────────────────────────────────────────────────────┐ │ -│ │ Custom CSS Template │ │ -│ │ ┌───────────────────────────────────────────────────┐ │ │ -│ │ │ None (Use default theme) ▼ │ │ │ -│ │ │ ─────────────────────────────────────────────────│ │ │ -│ │ │ ○ None (Use default theme) │ │ │ -│ │ │ ○ Elegant Dark │ │ │ -│ │ │ ○ Template 2 (disabled) │ │ │ -│ │ │ ○ Minimalist White │ │ │ -│ │ └───────────────────────────────────────────────────┘ │ │ -│ │ │ │ -│ │ [Preview Template] │ │ -│ └─────────────────────────────────────────────────────────┘ │ -│ │ -│ ... │ -└─────────────────────────────────────────────────────────────────┘ -``` - -#### 3.2 Template Selection Rules -- Only enabled templates appear in dropdown -- "None" option always available (uses default theme) -- Disabled templates show "(disabled)" and cannot be selected -- Selected template ID stored in events table - -### 4. Gallery Frontend Application - -#### 4.1 CSS Loading -When gallery loads: -1. Check if event has `css_template_id` set -2. If set, fetch template CSS from API -3. Inject CSS into `