Skip to content

Instantly share code, notes, and snippets.

@ochudi
Last active November 24, 2025 08:39
Show Gist options
  • Select an option

  • Save ochudi/d6d9c2f53b3f499e983afeec96f45c38 to your computer and use it in GitHub Desktop.

Select an option

Save ochudi/d6d9c2f53b3f499e983afeec96f45c38 to your computer and use it in GitHub Desktop.

BizTuneMusic - Documentation

A modern, scalable music discovery platform built with Next.js 15 and Supabase, designed for African artists. The application features comprehensive content management, secure file uploads, and a responsive user interface with dark mode support.

Table of Contents

Features

Content Management

  • Artist Profiles: Complete artist management with biographies, images, and multi-genre support
  • Song Library: Audio file uploads with metadata extraction, album art, and featured artist support
  • Albums: Album management with cover art and track listings
  • Genres: Categorization system supporting multiple genres per item
  • News System: Content publishing with markdown support and multiple image uploads
  • Video Integration: YouTube video embedding linked to artists

User Experience

  • Responsive Design: Mobile-first approach with adaptive layouts
  • Theme System: Light and dark mode with seamless transitions
  • Search Functionality: Real-time search across artists, songs, and genres
  • Audio Player: Built-in HTML5 audio player with streaming support
  • Share Functionality: Social media sharing with Open Graph and Twitter Card metadata
  • SEO Optimization: Dynamic meta tags, slug-based URLs, and sitemap generation

Technical Features

  • Direct File Uploads: Browser-to-Supabase uploads bypassing server limitations
  • Row Level Security: Granular database access control
  • Server-Side Rendering: Optimized performance with Next.js 15 App Router
  • Type Safety: Full TypeScript implementation
  • Real-time Updates: Automatic content synchronization
  • Error Handling: Comprehensive error logging and user feedback

Architecture

Application Structure

The application follows Next.js 15 App Router conventions with a clear separation of concerns:

app/
├── (pages)/              # Public-facing pages
│   ├── artists/[id]/    # Dynamic artist detail pages
│   ├── songs/[id]/      # Dynamic song detail pages
│   ├── news/[id]/       # Dynamic news detail pages
│   ├── genres/[id]/     # Dynamic genre detail pages
│   └── videos/[id]/     # Dynamic video detail pages
├── admin/                # Protected admin dashboard
│   └── dashboard/       # Admin content management
├── api/                  # API route handlers
│   ├── songs/           # Song CRUD operations
│   ├── artists/         # Artist CRUD operations
│   ├── albums/          # Album CRUD operations
│   ├── genres/          # Genre CRUD operations
│   ├── news/            # News CRUD operations
│   ├── videos/          # Video CRUD operations
│   ├── upload/          # File upload endpoint
│   ├── download/        # Secure file download proxy
│   └── admin/           # Admin authentication
└── globals.css          # Global styles and theme variables

Data Flow

  1. Client Requests: User interactions trigger API calls or page navigations
  2. API Routes: Server-side handlers process requests with validation
  3. Supabase Client: Authenticated requests to Supabase backend
  4. Database Operations: PostgreSQL queries with RLS enforcement
  5. Storage Operations: Direct uploads to Supabase Storage buckets
  6. Response Handling: JSON responses with error handling
  7. UI Updates: React state management and re-rendering

Technology Stack

Frontend

  • Framework: Next.js 15.2.4 (React 19)
  • Language: TypeScript 5.x
  • Styling: Tailwind CSS 4.x
  • UI Components: Radix UI primitives
  • Icons: Lucide React
  • Theme: next-themes for dark/light mode switching

Backend

  • Database: Supabase (PostgreSQL 15)
  • Storage: Supabase Storage (S3-compatible)
  • Authentication: Custom password-based admin auth
  • API: Next.js API Routes (Edge Runtime compatible)

Development Tools

  • Package Manager: pnpm
  • Linting: ESLint with Next.js config
  • Type Checking: TypeScript strict mode
  • Version Control: Git

Prerequisites

Before setting up the project, ensure you have:

  • Node.js: Version 18.x or higher
  • pnpm: Version 8.x or higher (npm install -g pnpm)
  • Supabase Account: Free tier available at supabase.com
  • Git: For version control
  • Modern Browser: Chrome, Firefox, Safari, or Edge (latest versions)

Installation & Setup

1. Clone and Install Dependencies

git clone https://github.com/ochudi/biztune.git
cd biztune
pnpm install

2. Environment Configuration

Create a .env.local file in the project root:

cp .env.example .env.local

Configure the following environment variables:

# Supabase Configuration
NEXT_PUBLIC_SUPABASE_URL=https://your-project-id.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key-here
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key-here

# Admin Authentication
ADMIN_PASSWORD=your-secure-password-here

# Application URL (for production)
NEXT_PUBLIC_SITE_URL=https://your-domain.com

# Environment
NODE_ENV=development

Important: Never commit .env.local to version control. The .gitignore file is configured to exclude it.

3. Supabase Project Setup

A. Create Supabase Project

  1. Go to supabase.com/dashboard
  2. Click "New Project"
  3. Choose organization and region
  4. Set a strong database password
  5. Wait for project initialization

B. Configure Database

Navigate to SQL Editor in Supabase dashboard and execute the following scripts in order:

Step 1: Create Tables

-- Execute scripts/create_supabase_tables.sql
-- This creates: artists, songs, albums, genres, news, videos tables

Step 2: Add Slug Support

-- Execute scripts/add_slug_columns.sql
-- Adds SEO-friendly URL slugs to artists, songs, news, videos

Step 3: Add Song Descriptions

-- Execute scripts/add_song_description.sql
-- Adds description field to songs table

Step 4: Enable Row Level Security

-- Execute scripts/enable_rls_policies.sql
-- Configures RLS policies for secure data access

Step 5: Configure Storage

-- Execute scripts/configure_supabase_storage.sql
-- Sets up storage buckets and access policies

C. Create Storage Buckets

  1. Navigate to Storage in Supabase dashboard

  2. Create two buckets:

    • Bucket Name: audio

      • Public: Yes
      • File size limit: 50MB
      • Allowed MIME types: audio/*
    • Bucket Name: images

      • Public: Yes
      • File size limit: 10MB
      • Allowed MIME types: image/*
  3. Verify bucket settings:

    SELECT * FROM storage.buckets;

4. Start Development Server

pnpm dev

Access the application:

5. Initial Admin Access

  1. Navigate to http://localhost:3000/admin
  2. Enter the password configured in ADMIN_PASSWORD
  3. Access the dashboard to begin content management

Database Schema

Core Tables

Artists Table

CREATE TABLE public.artists (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  bio TEXT,
  image TEXT,
  genre_ids TEXT[],
  is_featured BOOLEAN DEFAULT false,
  slug TEXT UNIQUE,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

Songs Table

CREATE TABLE public.songs (
  id TEXT PRIMARY KEY,
  title TEXT NOT NULL,
  artist_id TEXT REFERENCES public.artists(id) ON DELETE SET NULL,
  album_id TEXT REFERENCES public.albums(id) ON DELETE SET NULL,
  genre_ids TEXT[],
  featured_artist_ids TEXT[],
  album_art TEXT,
  download_url TEXT NOT NULL,
  duration TEXT,
  description TEXT,
  slug TEXT UNIQUE,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

Albums Table

CREATE TABLE public.albums (
  id TEXT PRIMARY KEY,
  title TEXT NOT NULL,
  artist_id TEXT REFERENCES public.artists(id) ON DELETE SET NULL,
  cover_art TEXT,
  release_year INTEGER,
  genre_ids TEXT[]
);

Genres Table

CREATE TABLE public.genres (
  id TEXT PRIMARY KEY,
  name TEXT NOT NULL UNIQUE,
  description TEXT
);

News Table

CREATE TABLE public.news (
  id TEXT PRIMARY KEY,
  title TEXT NOT NULL,
  content TEXT,
  image TEXT,
  supporting_images TEXT[],
  slug TEXT UNIQUE,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

Videos Table

CREATE TABLE public.videos (
  id TEXT PRIMARY KEY,
  title TEXT NOT NULL,
  youtube_url TEXT NOT NULL,
  description TEXT,
  artist_id TEXT REFERENCES public.artists(id) ON DELETE SET NULL,
  slug TEXT UNIQUE,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

Indexes

Key indexes for performance optimization:

CREATE INDEX idx_artists_slug ON public.artists(slug);
CREATE INDEX idx_songs_slug ON public.songs(slug);
CREATE INDEX idx_songs_artist_id ON public.songs(artist_id);
CREATE INDEX idx_albums_artist_id ON public.albums(artist_id);
CREATE INDEX idx_news_slug ON public.news(slug);
CREATE INDEX idx_videos_slug ON public.videos(slug);

API Documentation

Public Endpoints

GET /api/artists

Retrieve all artists, sorted by newest first.

Response:

[
  {
    "id": "1234567890",
    "name": "Artist Name",
    "bio": "Artist biography",
    "image": "https://...",
    "genreIds": ["genre1", "genre2"],
    "isFeatured": true,
    "slug": "artist-name"
  }
]

GET /api/songs

Retrieve all songs with metadata.

Response:

[
  {
    "id": "1234567890",
    "title": "Song Title",
    "artistId": "artist123",
    "albumId": "album123",
    "genreIds": ["genre1"],
    "featuredArtistIds": ["artist456"],
    "albumArt": "https://...",
    "downloadUrl": "https://...",
    "duration": "3:45",
    "description": "Song description",
    "slug": "song-title"
  }
]

GET /api/genres

Retrieve all available genres.

GET /api/albums

Retrieve all albums.

GET /api/news

Retrieve all news articles, sorted by creation date.

GET /api/videos

Retrieve all videos, sorted by newest first.

GET /api/download

Secure proxy for file downloads.

Query Parameters:

  • url (required): File URL to download
  • title (optional): Filename for download
  • artist (optional): Artist name for metadata

Protected Endpoints

All write operations require service role authentication via SUPABASE_SERVICE_ROLE_KEY.

POST /api/artists

Create a new artist.

Request Body:

{
  "id": "unique-id",
  "name": "Artist Name",
  "bio": "Biography text",
  "image": "https://...",
  "genreIds": ["genre1", "genre2"],
  "isFeatured": false
}

PUT /api/artists

Update an existing artist.

DELETE /api/artists?id={artistId}

Delete an artist by ID.

Similar CRUD operations exist for:

  • /api/songs
  • /api/albums
  • /api/genres
  • /api/news
  • /api/videos

Admin Endpoints

POST /api/admin/login

Authenticate admin user.

Request Body:

{
  "password": "admin-password"
}

Response:

{
  "success": true
}

POST /api/admin/logout

Clear admin session.

Authentication & Security

Security Measures

  1. Row Level Security (RLS)

    • All tables have RLS enabled
    • Public read access for GET operations
    • Authenticated write access for mutations
    • Service role bypass for admin operations
  2. Input Validation

    • Server-side validation for all inputs
    • Type checking with TypeScript
    • Length limits on text fields
    • File type and size validation
  3. SQL Injection Prevention

    • Parameterized queries via Supabase client
    • No raw SQL from user inputs
    • Prepared statements for all operations
  4. File Upload Security

    • MIME type validation
    • File size limits (50MB audio, 10MB images)
    • Virus scanning (via Supabase)
    • Direct browser-to-storage uploads
  5. Environment Variables

    • Sensitive data in environment variables
    • Never exposed to client bundle
    • Service role key server-side only
  6. HTTPS Only

    • All API calls over HTTPS
    • Secure cookie flags in production
    • HSTS headers enabled

Admin Access Control

Admin routes are protected by middleware:

// middleware.ts
export function middleware(request: NextRequest) {
  const isAdminRoute = request.nextUrl.pathname.startsWith('/admin')
  const isAdminApi = request.nextUrl.pathname.startsWith('/api/admin')
  
  if (isAdminRoute && !isAdminApi) {
    const hasAuth = request.cookies.get('admin-auth')
    if (!hasAuth) {
      return NextResponse.redirect(new URL('/admin', request.url))
    }
  }
}

File Upload System

Direct Upload Architecture

The application uses a direct upload strategy to bypass server limitations:

  1. Client-Side Upload

    • Browser creates Supabase client with anon key
    • File selected via UI component
    • Direct upload to Supabase Storage
    • Progress tracking and error handling
  2. Metadata Extraction

    • Audio metadata extracted in parallel
    • Duration, format, and quality detection
    • Album art extraction from ID3 tags
  3. Database Update

    • Public URL stored in database
    • Metadata saved with file reference
    • Atomic operations for consistency

Upload Components

MusicFileDropzone

Handles audio file uploads with drag-and-drop support.

<MusicFileDropzone
  onFileSelected={(url, metadata) => {
    setMusicUrl(url)
    setDuration(metadata.duration)
  }}
/>

MusicAndArtUpload

Combined component for audio and album art uploads.

<MusicAndArtUpload
  musicUrl={downloadUrl}
  setMusicUrl={setDownloadUrl}
  albumArt={albumArt}
  setAlbumArt={setAlbumArt}
  onMetadataExtracted={(metadata) => {
    setDuration(metadata.duration)
  }}
/>

File Size Limits

  • Audio Files: 50MB maximum
  • Images: 10MB maximum
  • Total Storage: Based on Supabase plan

SEO & Metadata

Dynamic Metadata Generation

Each dynamic route includes server-side metadata generation:

// app/artists/[id]/layout.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
  const artist = await fetchArtist(params.id)
  
  return {
    title: `${artist.name} - Emerging African Artist`,
    description: artist.bio,
    openGraph: {
      title: artist.name,
      description: artist.bio,
      images: [artist.image],
      type: 'profile'
    },
    twitter: {
      card: 'summary_large_image',
      title: artist.name,
      description: artist.bio,
      images: [artist.image]
    }
  }
}

URL Structure

SEO-friendly slug-based URLs:

  • Artists: /artists/artist-name
  • Songs: /songs/song-title
  • News: /news/article-title
  • Videos: /videos/video-title
  • Genres: /genres/genre-name

Sitemap Generation

Automatic sitemap generation at /sitemap.xml:

// app/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const artists = await fetchAllArtists()
  const songs = await fetchAllSongs()
  
  return [
    ...artists.map(artist => ({
      url: `${baseUrl}/artists/${artist.slug || artist.id}`,
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: artist.isFeatured ? 0.9 : 0.8
    })),
    ...songs.map(song => ({
      url: `${baseUrl}/songs/${song.slug || song.id}`,
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 0.7
    }))
  ]
}

Development

Project Structure

biztune/
├── app/                      # Next.js App Router
│   ├── (routes)/            # Page components
│   ├── api/                 # API route handlers
│   └── globals.css          # Global styles
├── components/
│   ├── admin/               # Admin dashboard components
│   ├── ui/                  # Reusable UI components
│   ├── navigation.tsx       # Main navigation
│   └── footer.tsx           # Footer component
├── lib/
│   ├── supabaseClient.ts    # Supabase client factory
│   ├── data.ts              # TypeScript interfaces
│   ├── slug-utils.ts        # URL slug generation
│   ├── validation.ts        # Input validation utilities
│   └── seo-metadata.ts      # SEO helper functions
├── scripts/
│   ├── *.sql                # Database migration scripts
│   └── migrate-urls.mjs     # URL migration utility
├── public/                   # Static assets
├── types/                    # TypeScript type definitions
├── middleware.ts            # Next.js middleware
├── next.config.mjs          # Next.js configuration
├── tailwind.config.ts       # Tailwind configuration
└── tsconfig.json            # TypeScript configuration

Development Commands

# Start development server
pnpm dev

# Build for production
pnpm build

# Start production server
pnpm start

# Run linting
pnpm lint

# Type check
pnpm type-check

# Update dependencies
pnpm update

# Audit dependencies
pnpm audit

Adding New Features

  1. Create TypeScript Interface

    // lib/data.ts
    export interface NewFeature {
      id: string
      name: string
      // ... other fields
    }
  2. Create Database Table

    -- scripts/add_new_feature.sql
    CREATE TABLE public.new_feature (
      id TEXT PRIMARY KEY,
      name TEXT NOT NULL
    );
  3. Create API Route

    // app/api/new-feature/route.ts
    export async function GET() {
      // Implementation
    }
  4. Create UI Component

    // components/admin/admin-new-feature.tsx
    export default function NewFeatureManager() {
      // Implementation
    }
  5. Add to Admin Dashboard

    // app/admin/dashboard/page.tsx
    import NewFeatureManager from '@/components/admin/admin-new-feature'

Code Style Guidelines

  • TypeScript: Use strict mode, avoid any when possible
  • Components: Functional components with hooks
  • Naming: PascalCase for components, camelCase for functions
  • Files: kebab-case for file names
  • CSS: Tailwind utility classes, avoid custom CSS
  • Imports: Absolute imports using @/ alias

Deployment

Vercel Deployment

  1. Push to GitHub

    git push origin main
  2. Import to Vercel

    • Go to vercel.com
    • Click "New Project"
    • Import your GitHub repository
    • Configure project settings
  3. Environment Variables Add all variables from .env.local in Vercel dashboard:

    • Settings → Environment Variables
    • Add each variable with appropriate scope (Production, Preview, Development)
  4. Deploy

    • Vercel automatically deploys on push
    • Monitor deployment in dashboard
    • Check deployment logs for errors

Production Checklist

  • All environment variables configured
  • Supabase RLS policies enabled
  • Storage buckets configured as public
  • Admin password changed from default
  • Database migrations executed
  • SSL certificate active
  • Custom domain configured (if applicable)
  • Error tracking enabled
  • Analytics configured
  • SEO metadata verified
  • Social sharing tested
  • Performance audit completed
  • Security headers configured

Environment Variables

# Production Environment Variables
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
ADMIN_PASSWORD=strong-production-password
NEXT_PUBLIC_SITE_URL=https://your-domain.com
NODE_ENV=production

Database Backups

Configure automatic backups in Supabase:

  1. Navigate to Database → Backups
  2. Enable daily backups
  3. Set retention period (7-30 days)
  4. Test restore procedure

Monitoring

Supabase Dashboard:

  • Monitor database queries
  • Check storage usage
  • Review API logs
  • Track error rates

Vercel Analytics:

  • Page view tracking
  • Performance metrics
  • Error monitoring
  • User analytics

Troubleshooting

Common Issues

Issue: "Failed to upload file"

Cause: Storage bucket not public or incorrect MIME type

Solution:

  1. Verify bucket is public in Supabase Storage settings
  2. Check MIME type restrictions
  3. Verify file size within limits
  4. Check browser console for specific errors

Issue: "Database connection failed"

Cause: Incorrect Supabase credentials or network issue

Solution:

  1. Verify NEXT_PUBLIC_SUPABASE_URL is correct
  2. Check NEXT_PUBLIC_SUPABASE_ANON_KEY matches project
  3. Ensure Supabase project is active
  4. Check network connectivity

Issue: "Admin login fails"

Cause: Incorrect password or missing environment variable

Solution:

  1. Verify ADMIN_PASSWORD in .env.local
  2. Restart development server after changes
  3. Check for typos in password
  4. Clear browser cookies and retry

Issue: "RLS policy prevents operation"

Cause: Row Level Security blocking legitimate requests

Solution:

  1. Run scripts/enable_rls_policies.sql
  2. Verify service role key is set correctly
  3. Check policy definitions in Supabase dashboard
  4. Review policy logs for specific denials

Issue: "Metadata not showing on social media"

Cause: Server-side metadata not generated or cached

Solution:

  1. Verify layout.tsx files exist for dynamic routes
  2. Test with Open Graph Debugger
  3. Clear social media cache
  4. Rebuild and redeploy application

Debug Mode

Enable verbose logging:

# .env.local
DEBUG=true
LOG_LEVEL=verbose

Check logs:

  • Browser console for client errors
  • Vercel deployment logs for server errors
  • Supabase logs for database queries

Contributing

Development Workflow

  1. Fork Repository

    git clone https://github.com/your-username/biztune.git
    cd biztune
    git remote add upstream https://github.com/ochudi/biztune.git
  2. Create Feature Branch

    git checkout -b feature/your-feature-name
  3. Make Changes

    • Follow code style guidelines
    • Add tests if applicable
    • Update documentation
  4. Commit Changes

    git add .
    git commit -m "feat: add new feature description"

    Commit message format:

    • feat: New feature
    • fix: Bug fix
    • docs: Documentation changes
    • style: Code style changes
    • refactor: Code refactoring
    • test: Test additions/changes
    • chore: Build process or auxiliary tool changes
  5. Push and Create PR

    git push origin feature/your-feature-name

    Then create Pull Request on GitHub

Code Review Process

All pull requests require:

  • Passing CI checks
  • Code review approval
  • Updated documentation
  • No merge conflicts

License

MIT License

Copyright (c) 2025 BizTune

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


Documentation Version: 1.0.0
Last Updated: November 2025
Maintainer: Chukwudi Ofoma

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment