A full-stack application that automates song generation using Suno.ai API, featuring a FastAPI backend and RemixJS frontend.
- 🎵 Automated song generation via Suno.ai API
- 🚀 FastAPI backend for API management
- ⚛️ RemixJS frontend with React components
- 🔐 User authentication and session management
- 📊 Dashboard for tracking generated songs
- Framework: FastAPI (Python)
- Libraries:
nodriver,selenium_driverlessfor browser automationopenaifor AI integrationsupabasefor databasepandasfor data processing
- Tools:
rufffor fast Python linting and code analysisblackfor consistent code formattinguvicornfor ASGI server
- Testing: pytest for backend unit and integration tests
- Framework: RemixJS (TypeScript)
- UI Libraries:
- React 18
- Tailwind CSS for styling
- State Management: React Context API
- Authentication: Supabase Auth
- Tools:
ESLintwith TypeScript and React configurationsPrettierfor code formattingTypeScriptfor type checking
suno-automation/
├── backend/ # FastAPI backend
│ ├── api/ # API modules
│ │ ├── ai_generation/ # AI song generation endpoints
│ │ ├── ai_review/ # AI review endpoints
│ │ ├── orchestrator/ # Orchestration logic
│ │ └── song/ # Song management endpoints
│ ├── configs/ # Configuration files
│ ├── constants/ # Application constants
│ ├── database_migration/ # Database migration scripts
│ ├── lab/ # Experimental code
│ ├── lib/ # Core libraries
│ │ ├── login.py # Authentication logic
│ │ └── supabase.py # Supabase client
│ ├── logs/ # Application logs
│ ├── middleware/ # FastAPI middleware
│ ├── misc/ # Miscellaneous files
│ ├── multi_tool_agent/ # AI agent tools
│ ├── routes/ # API route handlers
│ │ └── songs.py # Song-related routes
│ ├── services/ # Business logic services
│ │ ├── __init__.py
│ │ └── supabase_service.py # Supabase operations
│ ├── songs/ # Song data storage
│ ├── tests/ # Test suite
│ │ ├── conftest.py # Shared pytest fixtures
│ │ ├── test_api/
│ │ └── test_utils/
│ ├── utils/ # Utility functions
│ │ ├── ai_functions.py # AI helper functions
│ │ ├── ai_review.py # Review utilities
│ │ ├── assign_styles.py # Style assignment
│ │ ├── bible_utils.py # Bible-related utilities
│ │ ├── converter.py # Data converters
│ │ ├── llm_chat_utils.py # LLM chat utilities
│ │ └── suno_functions.py # Suno API functions
│ └── main.py # FastAPI application entry
├── frontend/ # RemixJS frontend
│ ├── app/ # Application code
│ │ ├── components/ # React components
│ │ │ ├── BookCard.tsx
│ │ │ ├── BookDetailsView.tsx
│ │ │ ├── BookFilter.tsx
│ │ │ ├── CardChapter.tsx
│ │ │ ├── CardVerseRange.tsx
│ │ │ ├── DisplayGeneratedSongs.tsx
│ │ │ ├── GenreFilter.tsx
│ │ │ ├── ModalSongs.tsx
│ │ │ ├── SidebarFilters.tsx
│ │ │ ├── TestamentFilter.tsx
│ │ │ └── ui/ # UI components library
│ │ ├── constants/ # Frontend constants
│ │ ├── lib/ # Frontend libraries
│ │ │ ├── api.ts # API client
│ │ │ └── supabase.ts # Supabase client
│ │ ├── routes/ # Page routes
│ │ │ ├── $.tsx # Catch-all route
│ │ │ ├── _index.tsx # Home page
│ │ │ ├── api.list-songs.ts # API route
│ │ │ └── main.tsx # Main dashboard
│ │ ├── entry.client.tsx # Client entry point
│ │ ├── entry.server.tsx # Server entry point
│ │ ├── root.tsx # Root component
│ │ └── tailwind.css # Tailwind styles
│ └── public/ # Static assets
└── README.md # Project documentation
For Windows users, we provide an automated setup script that installs all prerequisites and configures the project:
Requirements:
- Windows 10 or later
- Administrator privileges
- Internet connection
Steps:
- Download the repository or run the script from an existing clone
- Right-click on
setup-windows.batand select "Run as administrator" - Follow the prompts to complete the installation
- Edit the generated
.envfiles with your credentials - Run
start.batto launch the application
The script will automatically:
- Install Git, Node.js (LTS ≥20), and Python 3.11 via Winget
- Clone or update the repository
- Set up the Python virtual environment and install dependencies
- Download the Camoufox browser payload
- Install frontend Node.js dependencies
- Create
.envfiles from templates
- Python 3.11+
- Node.js 20+ (LTS)
- Git
- PostgreSQL database
-
Clone the repository:
git clone https://github.com/vnmw7/suno-automation.git cd suno-automation -
Set up backend:
cd backend python -m venv .venv source .venv/bin/activate # Linux/Mac .venv\Scripts\activate # Windows pip install -r requirements.txt camoufox fetch # Download browser payload
-
Set up frontend:
cd ../frontend npm install -
Create
.envfiles:- Backend:
backend/.env(copy frombackend/.env.example)SUPABASE_URL=your-supabase-url SUPABASE_KEY=your-supabase-key GOOGLE_AI_API_KEY=your-google-ai-api-key - Frontend:
frontend/.env(copy fromfrontend/.env.example)VITE_SUPABASE_URL=your_supabase_url_here VITE_SUPABASE_KEY=your_supabase_key_here VITE_API_URL=http://localhost:8000
- Backend:
After completing the setup, simply run:
start.batThis will start both the backend and frontend services simultaneously.
-
Start backend:
cd backend .venv\Scripts\activate # Windows source .venv/bin/activate # Linux/Mac uvicorn main:app --reload
-
Start frontend (in a new terminal):
cd frontend npm run dev -
Access the application at:
- API Docs:
http://localhost:8000/docs - Frontend:
http://localhost:3000
- API Docs:
The backend uses Ruff for fast Python linting and Black for code formatting.
Create a pyproject.toml file in the backend/ directory:
[tool.ruff]
# Exclude common directories
exclude = [
".git", ".venv", "__pycache__", "build", "dist",
"camoufox_session_data", "node_modules"
]
line-length = 88
target-version = "py310"
[tool.ruff.lint]
select = [
"E4", "E7", "E9", # pycodestyle errors
"F", # Pyflakes
"W", # pycodestyle warnings
"B", # flake8-bugbear
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"C4", # flake8-comprehensions
"PIE", # flake8-pie
"PT", # flake8-pytest-style
"RUF", # Ruff-specific rules
]
ignore = [
"T201", # print statements (useful for debugging)
"PLR0913", # too-many-arguments
"PLR0912", # too-many-branches
]
[tool.ruff.lint.per-file-ignores]
"**/test_*.py" = ["PLR2004", "S101"] # Allow magic values and assertions in tests
"lab/**/*.py" = ["T201", "F401"] # Relaxed rules for lab files
[tool.black]
line-length = 88
target-version = ['py310']cd backend
# Check for linting issues
ruff check .
# Auto-fix issues
ruff check . --fix
# Format code with Black
black .
# Check formatting without changing files
black . --check
# Run both linting and formatting
ruff check . --fix && black .The frontend uses ESLint with TypeScript and React configurations.
The project includes comprehensive ESLint rules in .eslintrc.cjs:
- TypeScript strict rules
- React best practices
- Accessibility (a11y) checks
- Import organization
- Code quality standards
cd frontend
# Check for linting issues
npm run lint
# Auto-fix issues
npm run lint:fix
# Strict linting (fails on warnings)
npm run lint:check
# Format code with Prettier
npm run format
# Check formatting
npm run format:check
# Type checking
npm run typecheckFor the best development experience, run these commands before committing:
Backend:
cd backend
ruff check . --fix
black .
pytest # Run testsFrontend:
cd frontend
npm run lint:fix
npm run format
npm run typecheck
npm test # Run tests when availableThe backend uses pytest for comprehensive Python testing with fixtures, mocking, and coverage reporting.
Add testing dependencies to requirements.txt:
pytest>=7.4.0
pytest-asyncio>=0.21.0
pytest-cov>=4.1.0
pytest-mock>=3.11.1
httpx>=0.24.0 # For testing FastAPI endpoints
Create pytest.ini in the backend/ directory:
[tool:pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
-v
--tb=short
--cov=.
--cov-report=term-missing
--cov-report=html:htmlcov
--cov-exclude=*/tests/*
--cov-exclude=*/venv/*
--cov-exclude=*/camoufox_session_data/*
asyncio_mode = autobackend/tests/
├── conftest.py # Shared pytest fixtures
├── test_main.py # Test main.py endpoints
├── test_api/
│ ├── __init__.py
│ ├── test_auth/
│ │ └── test_routes.py
│ └── test_song/
│ └── test_routes.py
├── test_utils/
│ ├── __init__.py
│ ├── test_ai_functions.py
│ └── test_suno_functions.py
└── test_lib/
├── __init__.py
├── test_login.py
└── test_supabase.py
conftest.py - Shared fixtures:
import pytest
from fastapi.testclient import TestClient
from unittest.mock import AsyncMock, MagicMock
@pytest.fixture
def client():
"""Test client for FastAPI app"""
from main import app
return TestClient(app)
@pytest.fixture
def mock_supabase():
"""Mock Supabase client"""
return MagicMock()
@pytest.fixture
def sample_song_request():
"""Sample song request data"""
return {
"strBookName": "Genesis",
"intBookChapter": 1,
"strVerseRange": "1-5",
"strStyle": "Pop",
"strTitle": "Test Song"
}test_main.py - API endpoint tests:
import pytest
from fastapi.testclient import TestClient
def test_root_endpoint(client):
"""Test root endpoint returns expected message"""
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "server working"}
def test_generate_verse_ranges(client):
"""Test verse ranges generation endpoint"""
response = client.post(
"/generate-verse-ranges?book_name=Genesis&book_chapter=1"
)
assert response.status_code == 200
data = response.json()
assert "success" in data
assert "verse_ranges" in data
@pytest.mark.asyncio
async def test_song_structure_generation(client, sample_song_request):
"""Test song structure generation"""
response = client.post("/generate-song-structure", json=sample_song_request)
assert response.status_code == 200
data = response.json()
assert data["success"] is Truecd backend
# Run all tests
pytest
# Run tests with coverage
pytest --cov=. --cov-report=html
# Run specific test file
pytest tests/test_main.py
# Run tests matching pattern
pytest -k "test_song"
# Run tests with verbose output
pytest -v
# Run tests and stop on first failure
pytest -x
# Run tests in parallel (install pytest-xdist first)
pytest -n autoThe frontend uses Jest with React Testing Library for component and integration testing.
Add testing dependencies to package.json:
{
"devDependencies": {
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.5.1",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"@types/jest": "^29.5.8",
"ts-jest": "^29.1.1"
}
}Create jest.config.js in the frontend/ directory:
/** @type {import('jest').Config} */
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],
moduleNameMapping: {
'^~/(.*)$': '<rootDir>/app/$1',
},
testMatch: [
'**/__tests__/**/*.(ts|tsx|js)',
'**/*.(test|spec).(ts|tsx|js)'
],
collectCoverageFrom: [
'app/**/*.{ts,tsx}',
'!app/**/*.d.ts',
'!app/entry.client.tsx',
'!app/entry.server.tsx',
'!app/root.tsx'
],
coverageDirectory: 'coverage',
coverageReporters: ['text', 'lcov', 'html'],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest'
}
};frontend/tests/
├── setup.ts # Jest setup file
├── __mocks__/ # Mock files
│ └── supabase.ts
├── components/
│ ├── Header.test.tsx
│ └── SongForm.test.tsx
├── routes/
│ ├── _index.test.tsx
│ └── dashboard.test.tsx
└── utils/
└── auth.test.ts
tests/setup.ts - Jest setup:
import '@testing-library/jest-dom';
// Mock Remix modules
jest.mock('@remix-run/react', () => ({
...jest.requireActual('@remix-run/react'),
useLoaderData: jest.fn(),
useActionData: jest.fn(),
useNavigation: jest.fn(() => ({ state: 'idle' })),
}));Component test example:
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { SongForm } from '~/components/SongForm';
describe('SongForm', () => {
test('renders form fields correctly', () => {
render(<SongForm />);
expect(screen.getByLabelText(/book name/i)).toBeInTheDocument();
expect(screen.getByLabelText(/chapter/i)).toBeInTheDocument();
expect(screen.getByLabelText(/verse range/i)).toBeInTheDocument();
});
test('submits form with correct data', async () => {
const mockSubmit = jest.fn();
render(<SongForm onSubmit={mockSubmit} />);
await userEvent.type(screen.getByLabelText(/book name/i), 'Genesis');
await userEvent.type(screen.getByLabelText(/chapter/i), '1');
await userEvent.click(screen.getByRole('button', { name: /submit/i }));
expect(mockSubmit).toHaveBeenCalledWith({
bookName: 'Genesis',
chapter: 1
});
});
});cd frontend
# Run all tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverage
# Run specific test file
npm test SongForm.test.tsx
# Run tests matching pattern
npm test -- --testNamePattern="form"
# Update snapshots
npm test -- --updateSnapshotAdd these scripts to frontend/package.json:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:ci": "jest --coverage --watchAll=false"
}
}- Use fixtures for common test data and mocked dependencies
- Test both success and error scenarios
- Mock external services (Supabase, OpenAI, browser automation)
- Use
pytest-asynciofor testing async functions - Aim for >80% code coverage
- Test user interactions, not implementation details
- Mock external dependencies and API calls
- Use React Testing Library queries (
getByRole,getByLabelText) - Test accessibility features
- Snapshot test for UI components that rarely change
# Backend integration tests
cd backend
pytest tests/integration/ -v
# Frontend E2E tests (if using Playwright/Cypress)
cd frontend
npm run test:e2eWe welcome contributions! Please follow these steps:
- Fork the repository
- Create your feature branch (
git checkout -b feature/your-feature) - Commit your changes with descriptive messages
- Push to the branch (
git push origin feature/your-feature) - Open a pull request
This project is licensed under the MIT License - see the LICENSE file for details.
For support or questions, please open an issue in the GitHub repository.