Add project scaffolding and documentation

Add .env.example with configuration for database, Redis, security, SMTP, workers, and plugins. Add .gitignore for Python, Node.js, Next.js, Docker volumes, and IDE files. Add MIT License. Update README.md with feature overview, quick start guide, architecture description, plugin system documentation, security details, backup/restore instructions, and developer setup. Add Alembic configuration files and placeholder directories for API, web, worker, and plugin components
This commit is contained in:
2026-06-21 09:31:47 +02:00
parent cfeeccbf53
commit d694c8b8e3
197 changed files with 8583 additions and 56 deletions
View File
+39
View File
@@ -0,0 +1,39 @@
import pytest
import pytest_asyncio
from fastapi.testclient import TestClient
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker
from apps.api.src.database import Base, get_db
from apps.api.src.main import app
TEST_DATABASE_URL = "postgresql+asyncpg://test:test@localhost:5432/nexadash_test"
engine = create_async_engine(TEST_DATABASE_URL, echo=False, future=True)
TestingSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
@pytest_asyncio.fixture(scope="session", autouse=True)
async def prepare_db():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
@pytest_asyncio.fixture
async def db():
async with TestingSessionLocal() as session:
yield session
await session.rollback()
@pytest.fixture
def client(db):
async def override_get_db():
yield db
app.dependency_overrides[get_db] = override_get_db
yield TestClient(app)
app.dependency_overrides.clear()
+32
View File
@@ -0,0 +1,32 @@
import pytest
@pytest.fixture
def setup_response(client):
return client.post("/api/v1/auth/setup", json={
"email": "owner@nexadash.local",
"password": "StrongPassword123!",
"first_name": "Owner",
"last_name": "User",
})
def test_setup_status(client):
r = client.get("/api/v1/auth/setup-status")
assert r.status_code == 200
assert "setup_required" in r.json()
def test_setup(client, setup_response):
assert setup_response.status_code in (200, 400)
if setup_response.status_code == 200:
assert "access_token" in setup_response.json()
def test_login(client, setup_response):
r = client.post("/api/v1/auth/login", json={
"email": "owner@nexadash.local",
"password": "StrongPassword123!",
})
assert r.status_code == 200
assert "access_token" in r.json()
+21
View File
@@ -0,0 +1,21 @@
import pytest
from apps.api.src.plugins import registry
from apps.api.src.plugins.connectors.proxmox_ve import ProxmoxVEConnector
from apps.api.src.plugins.connectors.adguard_home import AdGuardHomeConnector
from apps.api.src.plugins.connectors.generic_http import GenericHTTPConnector
def test_builtin_connectors_registered():
assert "proxmox-ve" in registry.list_connectors()
assert "adguard-home" in registry.list_connectors()
assert "generic-http" in registry.list_connectors()
def test_connector_classes():
assert registry.get_connector("proxmox-ve") is ProxmoxVEConnector
assert registry.get_connector("adguard-home") is AdGuardHomeConnector
assert registry.get_connector("generic-http") is GenericHTTPConnector
def test_registry_missing():
assert registry.get_connector("unknown") is None
+29
View File
@@ -0,0 +1,29 @@
from apps.api.src.security.password import hash_password, verify_password
from apps.api.src.security.encryption import encrypt_value, decrypt_value, mask_secret
from apps.api.src.security.ssrf import is_safe_url
def test_password_hash():
pw = "super-secret-password"
hashed = hash_password(pw)
assert verify_password(pw, hashed)
assert not verify_password("wrong", hashed)
def test_encryption():
value = "api-token-secret"
encrypted = encrypt_value(value)
decrypted = decrypt_value(encrypted)
assert decrypted == value
def test_mask_secret():
assert mask_secret("1234567890", 4) == "******7890"
def test_ssrf_protection():
assert not is_safe_url("http://localhost:8000")
assert not is_safe_url("http://127.0.0.1/admin")
assert not is_safe_url("http://169.254.169.254")
assert not is_safe_url("ftp://example.com")
assert is_safe_url("https://example.com/api")