feat: remove unused files and dependencies, streamline project structure

This commit is contained in:
2025-06-20 02:33:58 +00:00
committed by GitHub
parent 0d69b10be0
commit 96a29f25dc
8 changed files with 7 additions and 393 deletions

View File

@@ -1,34 +0,0 @@
name: Run Jest Tests
on:
push:
branches:
- main
paths:
- 'apps/data/albums.ts'
- 'apps/data/artists.ts'
pull_request:
branches:
- main
paths:
- 'apps/data/albums.ts'
- 'apps/data/artists.ts'
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install dependencies
run: npm install
- name: Run Jest tests
run: npm test

View File

@@ -1,145 +0,0 @@
# Migration Summary: Firebase → Navidrome/Subsonic
## ✅ Completed Migration Tasks
### 🗑️ Removed Legacy Systems
- [x] **Firebase Dependencies**: Removed firebase, react-firebase-hooks packages
- [x] **Static Data Files**: Moved `app/data/` (albums.ts, artists.ts, playlists.ts) to backup
- [x] **Firebase Config**: Moved `app/firebase/` directory to backup
- [x] **Authentication System**: Removed Firebase Auth integration
- [x] **Database Connections**: Removed Firestore database calls
### 🚀 Implemented Navidrome Integration
- [x] **Navidrome API Client** (`lib/navidrome.ts`)
- Subsonic API authentication with token-based security
- All major endpoints: ping, getArtists, getAlbums, getAlbum, search3, etc.
- Stream URL generation for audio playback
- Cover art URL generation with size parameters
- Star/unstar functionality for favorites
- Scrobbling support for play tracking
- [x] **React Context Provider** (`app/components/NavidromeContext.tsx`)
- Global state management for music library data
- Loading states for UI feedback
- Error handling and connection testing
- Data fetching with automatic refresh
- CRUD operations for playlists
### 🎵 Updated Audio System
- [x] **AudioPlayerContext** - Completely rewritten for Navidrome
- Real audio streaming instead of static file URLs
- Queue management with Navidrome song objects
- Automatic scrobbling when tracks play
- Track conversion from Navidrome Song to playable Track format
- [x] **AudioPlayer Component**
- Updated to handle Navidrome streaming URLs
- Dynamic cover art from Navidrome getCoverArt API
- Proper track metadata display (artist, album, duration)
### 🎨 Updated UI Components
- [x] **AlbumArtwork Component**
- Uses Navidrome Album interface
- Dynamic cover art with getCoverArt API
- Context menu integration with Navidrome playlists
- Proper album metadata display (year, genre, song count)
- [x] **ArtistIcon Component**
- Uses Navidrome Artist interface
- Artist cover art support
- Album count display
- Star/unstar functionality in context menu
### 📄 Updated Pages
- [x] **Main Page** (`app/page.tsx`)
- Uses NavidromeContext for album data
- Loading states with skeleton UI
- Error handling for connection issues
- Recent and newest album sections
- [x] **Album Detail Page** (`app/album/[id]/page.tsx`)
- Fetches album and songs from Navidrome
- Real-time song playback with streaming
- Star/unstar album functionality
- Proper track listing with metadata
- [x] **Artist Page** (`app/artist/[artist]/page.tsx`)
- Artist details from Navidrome API
- Dynamic album grid for artist
- Star/unstar artist functionality
- Modern gradient header design
- [x] **Library Pages**
- `app/library/albums/page.tsx` - Shows all albums in grid layout
- `app/library/artists/page.tsx` - Shows all artists in grid layout
- `app/library/playlists/page.tsx` - Playlist management with CRUD operations
### 🔧 Configuration & Documentation
- [x] **Environment Configuration**
- `.env.example` with Navidrome connection settings
- Removed Firebase environment variables from package.json
- [x] **Documentation**
- `NAVIDROME_MIGRATION.md` - Detailed migration guide
- Updated `README.md` with new setup instructions
- Feature documentation and troubleshooting
- [x] **Type Safety**
- TypeScript interfaces matching Subsonic API responses
- Proper error handling throughout the application
- Type-safe component props and context values
### 🧪 Testing
- [x] **Test Suite** (`__tests__/navidrome.test.ts`)
- API client functionality tests
- TypeScript interface validation
- URL generation testing
- Configuration validation
## 🎯 Key Benefits Achieved
### **Real Music Streaming**
- Replaced static MP3 URLs with dynamic Navidrome streaming
- Support for multiple audio formats and bitrates
- Proper audio metadata from music files
### **Dynamic Library**
- No more manual JSON file management
- Auto-discovery of new music added to Navidrome
- Real-time library updates
### **Enhanced Features**
- Scrobbling for play tracking
- Star/favorite functionality
- Playlist management (create, edit, delete)
- Search across entire music library
- High-quality album artwork
### **Better Architecture**
- Removed Firebase dependency completely
- Self-hosted music solution
- Standards-based Subsonic API integration
- Type-safe development with proper interfaces
## 🔄 Migration Path
1. **Backup**: Old Firebase and static data moved to `-old` directories
2. **Dependencies**: Firebase packages removed, crypto built-in used
3. **Environment**: New `.env.local` needed with Navidrome credentials
4. **Data Flow**: `Static JSON → Firebase → Navidrome API`
5. **Authentication**: `Firebase Auth → Navidrome Server Authentication`
6. **Streaming**: `Static Files → Navidrome Stream API`
## 🚦 Ready for Production
The application is now fully migrated and ready for use with any Navidrome server. All core functionality has been preserved and enhanced:
- ✅ Browse music library (albums, artists, songs)
- ✅ Audio playback with queue management
- ✅ Search functionality
- ✅ Playlist management
- ✅ Favorites/starring
- ✅ Responsive design
- ✅ Error handling and loading states
**Next Steps**: Set up Navidrome server and configure connection in `.env.local`

View File

@@ -5,6 +5,7 @@ import Image from 'next/image';
import { useAudioPlayer } from '@/app/components/AudioPlayerContext';
import { Progress } from '@/components/ui/progress';
import { lrcLibClient } from '@/lib/lrclib';
import Link from 'next/link';
import {
FaPlay,
FaPause,
@@ -333,19 +334,15 @@ export const FullScreenPlayer: React.FC<FullScreenPlayerProps> = ({ isOpen, onCl
<h1 className="text-lg sm:text-xl lg:text-3xl font-bold text-foreground mb-2 line-clamp-2 leading-tight">
{currentTrack.name}
</h1>
<p className="text-base sm:text-lg lg:text-xl text-foreground/80 mb-1 line-clamp-1">{currentTrack.artist}</p>
<Link href={`/album${currentTrack.artistId}`} className="text-base sm:text-lg lg:text-xl text-foreground/80 mb-1 line-clamp-1">
{currentTrack.artist}
</Link>
</div>
{/* Progress */}
<div className="w-full max-w-sm lg:max-w-md mb-4 lg:mb-6 px-4 flex-shrink-0">
<div
className="h-2 bg-white/20 rounded-full cursor-pointer relative overflow-hidden"
onClick={handleSeek}
>
<div
className="h-full bg-foreground transition-all duration-150"
style={{ width: `${progress}%` }}
/>
<div className="w-full" onClick={handleSeek}>
<Progress value={progress} className="h-2 cursor-pointer" />
</div>
<div className="flex justify-between text-sm text-foreground/60 mt-2">
<span>{formatTime(currentTime)}</span>

View File

@@ -1,6 +1,4 @@
import { SpeedInsights } from "@vercel/speed-insights/next";
import React from 'react';
import { Analytics } from "@vercel/analytics/react";
import localFont from "next/font/local";
import "./globals.css";
import { AudioPlayerProvider } from "./components/AudioPlayerContext";
@@ -81,8 +79,6 @@ export default function Layout({ children }: LayoutProps) {
<NavidromeConfigProvider>
<NavidromeProvider>
<AudioPlayerProvider>
<SpeedInsights />
<Analytics />
<Ihateserverside>
{children}
</Ihateserverside>

View File

@@ -1,7 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
},
};

View File

@@ -6,8 +6,7 @@
"dev": "next dev -p 40625",
"build": "next build",
"start": "next start -p 40625",
"lint": "next lint",
"test": "jest"
"lint": "next lint"
},
"dependencies": {
"@hookform/resolvers": "^3.9.1",
@@ -25,8 +24,6 @@
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-toast": "^1.2.4",
"@vercel/analytics": "^1.4.1",
"@vercel/speed-insights": "^1.1.0",
"axios": "^1.7.7",
"chalk": "^5.3.0",
"class-variance-authority": "^0.7.0",
@@ -49,7 +46,6 @@
"@types/react-dom": "^19.0.2",
"eslint": "^9.17",
"eslint-config-next": "15.1.4",
"jest": "^29.7.0",
"postcss": "^8",
"tailwindcss": "^3.4.15",
"ts-jest": "^29.2.5",

68
pnpm-lock.yaml generated
View File

@@ -53,12 +53,6 @@ importers:
'@radix-ui/react-toast':
specifier: ^1.2.4
version: 1.2.4(@types/react-dom@19.0.2(@types/react@19.0.4))(@types/react@19.0.4)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@vercel/analytics':
specifier: ^1.4.1
version: 1.4.1(next@15.1.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
'@vercel/speed-insights':
specifier: ^1.1.0
version: 1.1.0(next@15.1.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
axios:
specifier: ^1.7.7
version: 1.7.9
@@ -120,9 +114,6 @@ importers:
eslint-config-next:
specifier: 15.1.4
version: 15.1.4(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.3)
jest:
specifier: ^29.7.0
version: 29.7.0(@types/node@22.10.5)
postcss:
specifier: ^8
version: 8.4.49
@@ -1212,55 +1203,6 @@ packages:
resolution: {integrity: sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@vercel/analytics@1.4.1':
resolution: {integrity: sha512-ekpL4ReX2TH3LnrRZTUKjHHNpNy9S1I7QmS+g/RQXoSUQ8ienzosuX7T9djZ/s8zPhBx1mpHP/Rw5875N+zQIQ==}
peerDependencies:
'@remix-run/react': ^2
'@sveltejs/kit': ^1 || ^2
next: '>= 13'
react: ^18 || ^19 || ^19.0.0-rc
svelte: '>= 4'
vue: ^3
vue-router: ^4
peerDependenciesMeta:
'@remix-run/react':
optional: true
'@sveltejs/kit':
optional: true
next:
optional: true
react:
optional: true
svelte:
optional: true
vue:
optional: true
vue-router:
optional: true
'@vercel/speed-insights@1.1.0':
resolution: {integrity: sha512-rAXxuhhO4mlRGC9noa5F7HLMtGg8YF1zAN6Pjd1Ny4pII4cerhtwSG4vympbCl+pWkH7nBS9kVXRD4FAn54dlg==}
peerDependencies:
'@sveltejs/kit': ^1 || ^2
next: '>= 13'
react: ^18 || ^19 || ^19.0.0-rc
svelte: '>= 4'
vue: ^3
vue-router: ^4
peerDependenciesMeta:
'@sveltejs/kit':
optional: true
next:
optional: true
react:
optional: true
svelte:
optional: true
vue:
optional: true
vue-router:
optional: true
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -4532,16 +4474,6 @@ snapshots:
'@typescript-eslint/types': 8.19.1
eslint-visitor-keys: 4.2.0
'@vercel/analytics@1.4.1(next@15.1.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)':
optionalDependencies:
next: 15.1.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react: 19.0.0
'@vercel/speed-insights@1.1.0(next@15.1.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)':
optionalDependencies:
next: 15.1.4(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
react: 19.0.0
acorn-jsx@5.3.2(acorn@8.14.0):
dependencies:
acorn: 8.14.0

121
theme.md
View File

@@ -1,121 +0,0 @@
# Violet
```css
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 224 71.4% 4.1%;
--card: 0 0% 100%;
--card-foreground: 224 71.4% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 224 71.4% 4.1%;
--primary: 262.1 83.3% 57.8%;
--primary-foreground: 210 20% 98%;
--secondary: 220 14.3% 95.9%;
--secondary-foreground: 220.9 39.3% 11%;
--muted: 220 14.3% 95.9%;
--muted-foreground: 220 8.9% 46.1%;
--accent: 220 14.3% 95.9%;
--accent-foreground: 220.9 39.3% 11%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 20% 98%;
--border: 220 13% 91%;
--input: 220 13% 91%;
--ring: 262.1 83.3% 57.8%;
--radius: 0.65rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--background: 224 71.4% 4.1%;
--foreground: 210 20% 98%;
--card: 224 71.4% 4.1%;
--card-foreground: 210 20% 98%;
--popover: 224 71.4% 4.1%;
--popover-foreground: 210 20% 98%;
--primary: 263.4 70% 50.4%;
--primary-foreground: 210 20% 98%;
--secondary: 215 27.9% 16.9%;
--secondary-foreground: 210 20% 98%;
--muted: 215 27.9% 16.9%;
--muted-foreground: 217.9 10.6% 64.9%;
--accent: 215 27.9% 16.9%;
--accent-foreground: 210 20% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 20% 98%;
--border: 215 27.9% 16.9%;
--input: 215 27.9% 16.9%;
--ring: 263.4 70% 50.4%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
```
# Blue
```css@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 5.9% 10%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 0 0% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 48%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--hover: 240 27% 11%;
}
}
```