Monorepo Integration: Unified Backend, Frontend & Documentation
- Reorganize project into monorepo structure - backend/app/ - New FastAPI backend (modular with src/) - backend/legacy/ - Legacy database modules (relational & vector) - frontend/ - React text editor application - Add launcher.py for easy full-stack startup - Complete documentation in README.md - Quick start guide - API endpoints reference - Development setup - Troubleshooting - Refactor main.py to 35 lines (app configuration only) - Update .gitignore for full-stack project - Add CHANGELOG.md with version history (v0.1.0-v0.1.1) Structure is now clean and ready for team collaboration.
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
ENVIRONMENT=development
|
||||
DATABASE_PATH=archivium.db
|
||||
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
|
||||
67
.gitignore
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
venv/
|
||||
env/
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Environment
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Testing
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Node/Frontend
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
dist/
|
||||
build/
|
||||
|
||||
# Lock files (optional - uncomment if you want to enforce exact versions)
|
||||
# package-lock.json
|
||||
# pnpm-lock.yaml
|
||||
|
||||
8
.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
8
.idea/Editor.iml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.13" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
@@ -1,6 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
7
.idea/misc.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.13" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
8
.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Editor.iml" filepath="$PROJECT_DIR$/.idea/Editor.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
83
CHANGELOG.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# Changelog
|
||||
|
||||
## [0.1.1] - 2026-04-09
|
||||
|
||||
### Changed
|
||||
- Reorganized project structure: moved all modules to `src/` folder
|
||||
- Refactored endpoints into separate router modules:
|
||||
- `src/routers/init.py` - System initialization endpoint
|
||||
- `src/routers/login.py` - Authentication endpoint
|
||||
- `src/routers/status.py` - Status check endpoint
|
||||
- Reduced `main.py` from 100+ lines to 35 lines (only app configuration)
|
||||
- Updated all internal imports to use relative imports within `src/`
|
||||
|
||||
### Project Structure
|
||||
```
|
||||
archivium-backend/
|
||||
├── main.py # Entry point (35 lines)
|
||||
├── src/
|
||||
│ ├── __init__.py
|
||||
│ ├── config.py # Configuration
|
||||
│ ├── models.py # Database models
|
||||
│ ├── schemas.py # Request/response schemas
|
||||
│ ├── database.py # Database setup
|
||||
│ ├── security.py # Password hashing
|
||||
│ └── routers/
|
||||
│ ├── __init__.py
|
||||
│ ├── init.py # POST /api/init
|
||||
│ ├── login.py # POST /api/login
|
||||
│ └── status.py # GET /api/status
|
||||
├── pyproject.toml
|
||||
├── requirements.txt
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## [0.1.0] - 2026-04-09
|
||||
|
||||
### Changed
|
||||
- Removed excessive Polish comments and restructured code for readability
|
||||
- Refactored monolithic `main.py` into modular structure:
|
||||
- `config.py` - Environment configuration and CORS settings
|
||||
- `models.py` - SQLAlchemy ORM models
|
||||
- `schemas.py` - Pydantic request/response schemas with validation
|
||||
- `database.py` - Database initialization and session management
|
||||
- `security.py` - Password hashing and recovery key generation
|
||||
- `main.py` - FastAPI application and endpoint handlers
|
||||
- Added official Python docstrings for public functions and classes only
|
||||
- Improved project metadata with description and version in FastAPI app
|
||||
|
||||
### Security Improvements
|
||||
- Restricted CORS to explicit allowed origins instead of wildcard ("*")
|
||||
- Limited allowed HTTP methods to POST and GET only
|
||||
- Restricted allowed headers to Content-Type only
|
||||
- Added password validation (minimum 8 characters, maximum 128)
|
||||
- Improved error handling with try-except for password verification
|
||||
- Database operations now properly managed with dependency injection
|
||||
|
||||
### Added
|
||||
- `pyproject.toml` for modern Python package management (compatible with uv)
|
||||
- `requirements.txt` for traditional pip/env management
|
||||
- Proper dependency pinning with specific versions
|
||||
- Database initialization on startup event
|
||||
- Dependency injection for database sessions via `Depends(get_db)`
|
||||
- Recovery key generation moved to dedicated security module
|
||||
- Startup lifecycle event to ensure schema creation
|
||||
|
||||
### Dependencies
|
||||
- fastapi>=0.104.0
|
||||
- uvicorn[standard]>=0.24.0
|
||||
- pydantic>=2.5.0
|
||||
- sqlalchemy>=2.0.0
|
||||
- passlib[argon2]>=1.7.4
|
||||
|
||||
### Notes
|
||||
- SQLite remains in use for development (no encryption at rest)
|
||||
- For production deployment, consider:
|
||||
- Using PostgreSQL or equivalent encrypted database
|
||||
- Setting ENVIRONMENT=production env var
|
||||
- Configuring CORS_ORIGINS for specific domains
|
||||
- Enabling HTTPS/SSL
|
||||
- Implementing rate limiting
|
||||
- Adding request logging and monitoring
|
||||
334
README.md
Normal file
@@ -0,0 +1,334 @@
|
||||
# 📚 Archivium - System Zarządzania Archiwum
|
||||
|
||||
**Archivium** to integrowany system do szyfrowania, przechowywania i wyszukiwania archiwów z bezpiecznym uwierzytelnianiem i interfejsem edytora tekstu.
|
||||
|
||||
## 🏗️ Architektura projektu
|
||||
|
||||
```
|
||||
archivium/
|
||||
├── backend/
|
||||
│ ├── app/ # Nowoczesny backend FastAPI
|
||||
│ │ ├── src/
|
||||
│ │ │ ├── config.py # Konfiguracja
|
||||
│ │ │ ├── models.py # Modele bazy
|
||||
│ │ │ ├── schemas.py # Walidacja
|
||||
│ │ │ ├── database.py # Zarządzanie BD
|
||||
│ │ │ ├── security.py # Hashing hasła
|
||||
│ │ │ └── routers/ # Endpointy API
|
||||
│ │ │ ├── init.py
|
||||
│ │ │ ├── login.py
|
||||
│ │ │ └── status.py
|
||||
│ │ ├── main.py # Punkt wejścia (35 linii)
|
||||
│ │ └── pyproject.toml
|
||||
│ │
|
||||
│ └── legacy/ # Stare moduły bazy danych
|
||||
│ ├── relational_database.py
|
||||
│ └── vector_database.py
|
||||
│
|
||||
├── frontend/ # React aplikacja (edytor tekstu)
|
||||
│ ├── public/
|
||||
│ ├── src/
|
||||
│ └── package.json
|
||||
│
|
||||
├── launcher.py # Uruchamiacz całej aplikacji
|
||||
├── requirements.txt # Zależności Python
|
||||
├── README.md # Ta dokumentacja
|
||||
├── CHANGELOG.md # Historia zmian
|
||||
└── .git/ # Repozytorium Git
|
||||
```
|
||||
|
||||
## 🚀 Szybki start
|
||||
|
||||
### Uruchomienie całej aplikacji (jedno polecenie)
|
||||
|
||||
```bash
|
||||
python launcher.py
|
||||
```
|
||||
|
||||
To uruchomi:
|
||||
- ✅ Backend FastAPI (port 8000)
|
||||
- ✅ Frontend React (port 3000)
|
||||
- ✅ Otworzy przeglądarkę na http://localhost:3000
|
||||
|
||||
### Ręczne uruchomienie poszczególnych części
|
||||
|
||||
**Backend:**
|
||||
```bash
|
||||
cd backend/app
|
||||
python main.py
|
||||
```
|
||||
|
||||
**Frontend:**
|
||||
```bash
|
||||
cd frontend
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
## 📡 API Endpoints
|
||||
|
||||
### 1. Inicjalizacja systemu
|
||||
```http
|
||||
POST /api/init
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"password": "twoje_bezpieczne_haslo_8znaków_min"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"recovery_key": "a1b2c3d4e5f6...",
|
||||
"message": "System initialized. Save recovery key in safe place."
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Logowanie - hasłem głównym
|
||||
```http
|
||||
POST /api/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"password": "twoje_haslo",
|
||||
"is_recovery": false
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Successfully authenticated"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Logowanie - kluczem awaryjnym
|
||||
```http
|
||||
POST /api/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"password": "a1b2c3d4e5f6...",
|
||||
"is_recovery": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Authenticated with recovery key. Please change password."
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Sprawdzenie statusu
|
||||
```http
|
||||
GET /api/status
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"is_initialized": true
|
||||
}
|
||||
```
|
||||
|
||||
## 🔐 Bezpieczeństwo
|
||||
|
||||
### Zaimplementowane zabezpieczenia:
|
||||
- ✅ **Hashing haseł** - Argon2 (type="ID")
|
||||
- ✅ **Losowe klucze awaryjne** - 32 znaki hex
|
||||
- ✅ **CORS** - ograniczony do zaufanych domen
|
||||
- ✅ **Walidacja danych** - Pydantic
|
||||
- ✅ **Dwie ścieżki dostępu** - hasło główne + klucz awaryjny
|
||||
|
||||
### Zmienne środowiskowe (.env)
|
||||
```bash
|
||||
ENVIRONMENT=development # development | production
|
||||
DATABASE_PATH=archivium.db # lokalizacja bazy
|
||||
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173
|
||||
```
|
||||
|
||||
### Rekomendacje produkcyjne:
|
||||
1. Użyj **PostgreSQL** zamiast SQLite
|
||||
2. Włącz **HTTPS/SSL**
|
||||
3. Dodaj **rate limiting**
|
||||
4. Włącz **logging i monitoring**
|
||||
5. Regularne **backupy** bazy danych
|
||||
6. Przechowuj sekrety w zmiennych środowiskowych
|
||||
|
||||
## 📚 Szczegółowa dokumentacja struktury
|
||||
|
||||
### Backend - Endpointy (src/routers/)
|
||||
|
||||
**init.py** - `POST /api/init`
|
||||
- Inicjalizuje system
|
||||
- Generuje recovery key (32 znaki)
|
||||
- Hashuje hasło główne z Argon2
|
||||
|
||||
**login.py** - `POST /api/login`
|
||||
- Logowanie hasłem głównym lub kluczem awaryjnym
|
||||
- Weryfikuje hash haseł
|
||||
- Zwraca status autoryzacji
|
||||
|
||||
**status.py** - `GET /api/status`
|
||||
- Sprawdza czy system zainicjalizowany
|
||||
- Zwraca boolean `is_initialized`
|
||||
|
||||
### Backend - Core modules (src/)
|
||||
|
||||
**config.py** - Konfiguracja
|
||||
- CORS configuration
|
||||
- DATABASE_PATH z zmiennych środowiskowych
|
||||
- ALLOWED_ORIGINS
|
||||
|
||||
**models.py** - SQLAlchemy ORM
|
||||
- SecurityConfig model
|
||||
- Tabela `security_config`
|
||||
- Przechowuje hashe haseł i kluczy
|
||||
|
||||
**schemas.py** - Pydantic validation
|
||||
- InitRequest - walidacja do /api/init
|
||||
- LoginRequest - walidacja do /api/login
|
||||
|
||||
**database.py** - Zarządzanie bazą
|
||||
- SQLite setup
|
||||
- Session management
|
||||
- `init_db()` - inicjalizuje schemat
|
||||
- `get_db()` - dependency injection dla sessionów
|
||||
|
||||
**security.py** - Funkcje bezpieczeństwa
|
||||
- `hash_password(password: str) -> str`
|
||||
- `verify_password(password: str, hash_value: str) -> bool`
|
||||
- `generate_recovery_key() -> str`
|
||||
|
||||
**main.py** - FastAPI aplikacja (35 linii!)
|
||||
- Konfiguracja CORS middleware
|
||||
- Include routery
|
||||
- Startup event dla init_db()
|
||||
|
||||
## 🛠️ Development
|
||||
|
||||
### Instalacja dependencies
|
||||
|
||||
```bash
|
||||
# Backend
|
||||
pip install -r requirements.txt
|
||||
|
||||
# Lub z uv:
|
||||
cd backend/app && uv sync
|
||||
|
||||
# Frontend
|
||||
cd frontend && npm install
|
||||
```
|
||||
|
||||
### Testowanie API
|
||||
|
||||
```bash
|
||||
# Curl - Inicjalizacja
|
||||
curl -X POST http://localhost:8000/api/init \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"password": "TestPassword123"}'
|
||||
|
||||
# Curl - Logowanie
|
||||
curl -X POST http://localhost:8000/api/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"password": "TestPassword123", "is_recovery": false}'
|
||||
|
||||
# Status
|
||||
curl http://localhost:8000/api/status
|
||||
```
|
||||
|
||||
### Dokumentacja interaktywna
|
||||
|
||||
Gdy serwer działa, otwórz: **http://localhost:8000/docs**
|
||||
- Swagger UI do testowania API
|
||||
- Automatyczna dokumentacja OpenAPI
|
||||
- Try it out - testuj bezpośrednio z przeglądarki
|
||||
|
||||
## 📝 Historia zmian
|
||||
|
||||
Pełna historia zmian w [CHANGELOG.md](CHANGELOG.md)
|
||||
|
||||
### v0.1.1 - Monorepo Integration
|
||||
- Reorganizacja całego projektu do struktury monorepo
|
||||
- Backend w `backend/app/` (35 linii main.py!)
|
||||
- Launcher do uruchamiania całej aplikacji
|
||||
- Integracja z starym kodem z `Database/` i `TextEditor/`
|
||||
- Kompletna dokumentacja
|
||||
|
||||
### v0.1.0 - Refactorization
|
||||
- Refaktoryzacja kodu, usunięcie komentarzy
|
||||
- Modularyzacja na src/
|
||||
- Bezpieczeństwo (CORS, Argon2, walidacja)
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Problem: Port 8000 już w użyciu
|
||||
```bash
|
||||
# Zmień w backend/app/main.py lub użyj:
|
||||
cd backend/app && uv run uvicorn main:app --port 8001
|
||||
```
|
||||
|
||||
### Problem: Frontend nie łączy się z backendem
|
||||
Sprawdź w `backend/app/src/config.py`:
|
||||
```python
|
||||
ALLOWED_ORIGINS = ["http://localhost:3000"]
|
||||
```
|
||||
|
||||
### Problem: Baza danych uszkodzona
|
||||
```bash
|
||||
rm archivium.db # Usuń stary plik
|
||||
python launcher.py # Przeinicjalizuj
|
||||
```
|
||||
|
||||
### Problem: npm start nie działa
|
||||
```bash
|
||||
cd frontend
|
||||
rm -rf node_modules package-lock.json
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
## 👥 Współpraca (Git)
|
||||
|
||||
```bash
|
||||
# Clone repozytorium
|
||||
git clone http://gitea.archvium.eu:30230/SzymonS/Kod.git
|
||||
|
||||
# Stwórz feature branch
|
||||
git checkout -b feature/my-feature
|
||||
|
||||
# Commit
|
||||
git add .
|
||||
git commit -am "Add new feature"
|
||||
|
||||
# Push
|
||||
git push origin feature/my-feature
|
||||
```
|
||||
|
||||
## 📦 Zależności
|
||||
|
||||
### Backend (Python)
|
||||
- **FastAPI** (0.104.0+) - Framework
|
||||
- **SQLAlchemy** (2.0.0+) - ORM
|
||||
- **Pydantic** (2.5.0+) - Walidacja
|
||||
- **Passlib[argon2]** (1.7.4+) - Hashing
|
||||
- **Uvicorn** (0.24.0+) - ASGI server
|
||||
|
||||
### Frontend (Node.js)
|
||||
- **React** - UI framework
|
||||
- Inne (patrz: `frontend/package.json`)
|
||||
|
||||
Pełna lista: [requirements.txt](requirements.txt)
|
||||
|
||||
## 📞 Support
|
||||
|
||||
Kontakt: SzymonS @ gitea.archvium.eu
|
||||
|
||||
---
|
||||
|
||||
**Ostatnia aktualizacja:** April 9, 2026
|
||||
35
backend/app/main.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from src.config import ALLOWED_ORIGINS
|
||||
from src.database import init_db
|
||||
from src.routers import init, login, status
|
||||
|
||||
app = FastAPI(
|
||||
title="Archivium Local Backend",
|
||||
description="Local archive encryption and authentication system",
|
||||
version="0.1.0",
|
||||
)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=ALLOWED_ORIGINS,
|
||||
allow_credentials=True,
|
||||
allow_methods=["POST", "GET"],
|
||||
allow_headers=["Content-Type"],
|
||||
)
|
||||
|
||||
app.include_router(init.router)
|
||||
app.include_router(login.router)
|
||||
app.include_router(status.router)
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
def startup():
|
||||
"""Initialize database on startup."""
|
||||
init_db()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="127.0.0.1", port=8000)
|
||||
19
backend/app/pyproject.toml
Normal file
@@ -0,0 +1,19 @@
|
||||
[project]
|
||||
name = "archivium-backend"
|
||||
version = "0.1.0"
|
||||
description = "Local archive encryption and authentication system"
|
||||
requires-python = ">=3.9,<3.13"
|
||||
dependencies = [
|
||||
"fastapi>=0.104.0",
|
||||
"uvicorn[standard]>=0.24.0",
|
||||
"pydantic>=2.5.0",
|
||||
"sqlalchemy>=2.0.0",
|
||||
"passlib[argon2]>=1.7.4",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=7.0.0",
|
||||
"pytest-asyncio>=0.21.0",
|
||||
"httpx>=0.25.0",
|
||||
]
|
||||
1
backend/app/src/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Archivium Backend Application."""
|
||||
10
backend/app/src/config.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import os
|
||||
|
||||
DB_PATH = os.getenv("DATABASE_PATH", "archivium.db")
|
||||
|
||||
ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS", "http://localhost:3000,http://localhost:5173").split(",")
|
||||
|
||||
if os.getenv("ENVIRONMENT") == "development":
|
||||
ALLOWED_ORIGINS = ["http://localhost:3000", "http://localhost:5173"]
|
||||
elif os.getenv("ENVIRONMENT") == "production":
|
||||
ALLOWED_ORIGINS = os.getenv("CORS_ORIGINS", "").split(",")
|
||||
28
backend/app/src/database.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker, Session
|
||||
|
||||
from .config import DB_PATH
|
||||
from .models import Base
|
||||
|
||||
DATABASE_URL = f"sqlite:///{DB_PATH}"
|
||||
|
||||
engine = create_engine(
|
||||
DATABASE_URL,
|
||||
connect_args={"check_same_thread": False},
|
||||
)
|
||||
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
|
||||
|
||||
def init_db():
|
||||
"""Initialize database schema."""
|
||||
Base.metadata.create_all(bind=engine)
|
||||
|
||||
|
||||
def get_db():
|
||||
"""Provide database session for dependency injection."""
|
||||
db = SessionLocal()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
13
backend/app/src/models.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
class SecurityConfig(Base):
|
||||
"""Storage for password and recovery key hashes."""
|
||||
__tablename__ = "security_config"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
password_hash = Column(String, nullable=False)
|
||||
recovery_key_hash = Column(String, nullable=False)
|
||||
1
backend/app/src/routers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Routers for Archivium Backend."""
|
||||
39
backend/app/src/routers/init.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import os
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import SecurityConfig
|
||||
from ..schemas import InitRequest
|
||||
from ..security import hash_password, generate_recovery_key
|
||||
from ..config import DB_PATH
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["init"])
|
||||
|
||||
|
||||
@router.post("/init")
|
||||
def initialize_system(request: InitRequest, db: Session = Depends(get_db)):
|
||||
"""Initialize system with master password and generate recovery key."""
|
||||
if os.path.exists(DB_PATH):
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="System already initialized",
|
||||
)
|
||||
|
||||
recovery_key = generate_recovery_key()
|
||||
hashed_password = hash_password(request.password)
|
||||
hashed_recovery = hash_password(recovery_key)
|
||||
|
||||
db.add(
|
||||
SecurityConfig(
|
||||
password_hash=hashed_password,
|
||||
recovery_key_hash=hashed_recovery,
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"recovery_key": recovery_key,
|
||||
"message": "System initialized. Save recovery key in safe place.",
|
||||
}
|
||||
50
backend/app/src/routers/login.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import os
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from ..database import get_db
|
||||
from ..models import SecurityConfig
|
||||
from ..schemas import LoginRequest
|
||||
from ..security import verify_password
|
||||
from ..config import DB_PATH
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["login"])
|
||||
|
||||
|
||||
@router.post("/login")
|
||||
def login(request: LoginRequest, db: Session = Depends(get_db)):
|
||||
"""Authenticate with master password or recovery key."""
|
||||
if not os.path.exists(DB_PATH):
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="System not initialized",
|
||||
)
|
||||
|
||||
config = db.query(SecurityConfig).first()
|
||||
if not config:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="System configuration error",
|
||||
)
|
||||
|
||||
if request.is_recovery:
|
||||
if not verify_password(request.password, config.recovery_key_hash):
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Invalid recovery key",
|
||||
)
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Authenticated with recovery key. Please change password.",
|
||||
}
|
||||
|
||||
if not verify_password(request.password, config.password_hash):
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Invalid password",
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Successfully authenticated",
|
||||
}
|
||||
12
backend/app/src/routers/status.py
Normal file
@@ -0,0 +1,12 @@
|
||||
import os
|
||||
from fastapi import APIRouter
|
||||
|
||||
from ..config import DB_PATH
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["status"])
|
||||
|
||||
|
||||
@router.get("/status")
|
||||
def get_status():
|
||||
"""Check if system is initialized."""
|
||||
return {"is_initialized": os.path.exists(DB_PATH)}
|
||||
10
backend/app/src/schemas.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class InitRequest(BaseModel):
|
||||
password: str = Field(..., min_length=8, max_length=128)
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
password: str = Field(..., min_length=1, max_length=128)
|
||||
is_recovery: bool = False
|
||||
20
backend/app/src/security.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import secrets
|
||||
from passlib.hash import argon2
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
"""Hash password using Argon2."""
|
||||
return argon2.using(type="ID").hash(password)
|
||||
|
||||
|
||||
def verify_password(password: str, hash_value: str) -> bool:
|
||||
"""Verify password against hash."""
|
||||
try:
|
||||
return argon2.using(type="ID").verify(password, hash_value)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def generate_recovery_key() -> str:
|
||||
"""Generate random recovery key (32 hex characters)."""
|
||||
return secrets.token_hex(16)
|
||||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
83
launcher.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""Archivium Launcher - Uruchamia całą aplikację"""
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import webbrowser
|
||||
|
||||
|
||||
def install_missing_packages():
|
||||
"""Instaluj brakujące pakiety"""
|
||||
packages = ["uvicorn", "fastapi", "sqlalchemy", "passlib[argon2]"]
|
||||
for package in packages:
|
||||
try:
|
||||
__import__(package.split("[")[0].replace("-", "_"))
|
||||
except ImportError:
|
||||
print(f"[*] Instalowanie: {package}...")
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
|
||||
|
||||
|
||||
def run_archivium():
|
||||
"""Uruchom całą aplikację Archivium"""
|
||||
install_missing_packages()
|
||||
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
backend_dir = os.path.join(base_dir, "backend", "app")
|
||||
frontend_dir = os.path.join(base_dir, "frontend")
|
||||
|
||||
print("\n" + "="*50)
|
||||
print(" ARCHIVIUM - System Zarządzania Archiwum")
|
||||
print("="*50 + "\n")
|
||||
|
||||
print("[1/3] Startuje backend (Port 8000)...")
|
||||
backend_process = subprocess.Popen(
|
||||
[sys.executable, "main.py"],
|
||||
cwd=backend_dir
|
||||
)
|
||||
|
||||
print("[2/3] Startuje frontend (Port 3000)...")
|
||||
try:
|
||||
frontend_process = subprocess.Popen(
|
||||
"npm start",
|
||||
shell=True,
|
||||
cwd=frontend_dir,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"[!] Uwaga: Nie udało się uruchomić frontendu: {e}")
|
||||
print("[*] Frontend może wymagać: npm install && npm start")
|
||||
frontend_process = None
|
||||
|
||||
print("[3/3] Otwieranie przeglądarki...")
|
||||
time.sleep(3)
|
||||
|
||||
try:
|
||||
webbrowser.open("http://localhost:3000")
|
||||
except Exception as e:
|
||||
print(f"[!] Nie udało się otworzyć przeglądarki: {e}")
|
||||
print("[*] Otwórz ręcznie: http://localhost:3000")
|
||||
|
||||
print("\n" + "="*50)
|
||||
print("✓ Aplikacja uruchomiona!")
|
||||
print(" Backend: http://localhost:8000")
|
||||
print(" Frontend: http://localhost:3000")
|
||||
print(" Docs: http://localhost:8000/docs")
|
||||
print("="*50)
|
||||
print("\nAby zatrzymać: Ctrl+C\n")
|
||||
|
||||
try:
|
||||
backend_process.wait()
|
||||
if frontend_process:
|
||||
frontend_process.wait()
|
||||
except KeyboardInterrupt:
|
||||
print("\n[*] Zatrzymywanie systemu...")
|
||||
backend_process.terminate()
|
||||
if frontend_process:
|
||||
frontend_process.terminate()
|
||||
backend_process.wait()
|
||||
if frontend_process:
|
||||
frontend_process.wait()
|
||||
print("[✓] System zatrzymany")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_archivium()
|
||||
56
main.py
@@ -1,56 +0,0 @@
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import webbrowser
|
||||
|
||||
|
||||
def install_missing_packages():
|
||||
packages = ["uvicorn", "fastapi", "sentence-transformers"]
|
||||
for package in packages:
|
||||
try:
|
||||
__import__(package.replace("-", "_"))
|
||||
except ImportError:
|
||||
print(f"[*] Instalowanie brakującej biblioteki: {package}...")
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
|
||||
|
||||
|
||||
def run_archivium():
|
||||
install_missing_packages()
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
backend_script = os.path.join(base_dir, "Database", "database.py")
|
||||
frontend_dir = os.path.join(base_dir, "TextEditor")
|
||||
|
||||
print("\n--- URUCHAMIANIE SYSTEMU ARCHIVIUM ---")
|
||||
|
||||
print(f"[*] Startuję bazę danych...")
|
||||
backend_process = subprocess.Popen(
|
||||
[sys.executable, backend_script],
|
||||
cwd=os.path.join(base_dir, "Database")
|
||||
)
|
||||
|
||||
print(f"[*] Startuję edytor...")
|
||||
frontend_process = subprocess.Popen(
|
||||
"npm start",
|
||||
shell=True,
|
||||
cwd=frontend_dir,
|
||||
creationflags=subprocess.CREATE_NEW_CONSOLE
|
||||
)
|
||||
|
||||
print("[*] Oczekiwanie na gotowość...")
|
||||
time.sleep(5)
|
||||
|
||||
webbrowser.open("http://localhost:3000")
|
||||
|
||||
try:
|
||||
backend_process.wait()
|
||||
frontend_process.wait()
|
||||
except KeyboardInterrupt:
|
||||
print("\n[*] Zamykanie systemu...")
|
||||
backend_process.terminate()
|
||||
frontend_process.terminate()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_archivium()
|
||||