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()
|
|
||||||