force rewrited code

This commit is contained in:
Krzysztof Cieślik
2026-04-15 17:38:49 +02:00
parent 6bbb24e633
commit 9a7cf8518d
16 changed files with 3167 additions and 109 deletions

View File

@@ -1,83 +0,0 @@
# 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

141
INSTALL_NODEJS.md Normal file
View File

@@ -0,0 +1,141 @@
# 🔧 Instalacja Node.js na Fedora Atomic
## Problem
Na Fedora Atomic nie ma prostej opcji `dnf install nodejs` ze względu na niezmienność systemu. Trzeba użyć **toolbox** lub alternatywnych sposobów.
## Rozwiązanie 1: Toolbox (Rekomendowane)
Toolbox tworzy kontener z pełnym dostępem do pakietów.
```bash
# Krok 1: Stwórz nowy toolbox kontener
toolbox create
# Krok 2: Wejdź do kontenera
toolbox enter
# Krok 3: Zainstaluj Node.js i npm
sudo dnf install nodejs npm -y
# Krok 4: Wyjdź z kontenera
exit
# Krok 5: Sprawdź instalację (powinno działać z poziomu hosta)
node --version
npm --version
```
## Rozwiązanie 2: Homebrew
Homebrew działa natywnie na Fedora Atomic.
```bash
# Krok 1: Zainstaluj Homebrew (jeśli nie masz)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
# Krok 2: Dodaj do PATH (jeśli nie dodano automatycznie)
echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> ~/.bashrc
source ~/.bashrc
# Krok 3: Zainstaluj Node.js
brew install node
# Krok 4: Sprawdź
node --version
npm --version
```
## Rozwiązanie 3: NVM (Node Version Manager)
NVM pozwala na installowanie wielu wersji Node.js.
```bash
# Krok 1: Zainstaluj NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# Krok 2: Załaduj NVM
source ~/.bashrc
# Krok 3: Zainstaluj Node.js
nvm install node
# Krok 4: Sprawdź
node --version
npm --version
```
## Rozwiązanie 4: Pobrać prebuilt (Szybko)
```bash
# Pobierz Node.js
cd ~/Downloads
wget https://nodejs.org/dist/v20.10.0/node-v20.10.0-linux-x64.tar.xz
# Rozpakuj
tar -xf node-v20.10.0-linux-x64.tar.xz
# Dodaj do PATH
export PATH="$HOME/Downloads/node-v20.10.0-linux-x64/bin:$PATH"
# Dodaj na stałe do .bashrc aby zachowało się między restartami
echo 'export PATH="$HOME/Downloads/node-v20.10.0-linux-x64/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# Sprawdź
node --version
npm --version
```
---
## Po instalacji Node.js
### Uruchom frontend:
```bash
cd frontend
npm install # Zainstaluj zależności (tylko raz)
npm start # Uruchom aplikację
```
### Lub uruchom całą aplikację:
```bash
python main.py
# Wybierz opcję 1 (uruchomić tylko backend) lub q (zainstalować Node.js)
```
---
## Diagnostyka jeśli coś nie działa
```bash
# Sprawdź gdzie jest node
which node
which npm
# Sprawdź wersje
node --version
npm --version
# Uwaga: Jeśli Node.js zainstalowany w toolbox, mogą być dostępne tylko w toolbox
# Użyj wtedy:
toolbox run node --version
toolbox run npm --version
```
## Najszybsze rozwiązanie dla Fedora Atomic:
```bash
# Terminal 1 - Backend
python main.py # wybiez opcja 1
# Terminal 2 - Frontend (jeśli Node zainstalowany)
cd frontend && npm install && npm start
# Lub jeśli Node w toolbox:
# Terminal 2
toolbox run bash -c 'cd /var/home/krzysztof/Documents/inz/git_fix/frontend && npm install && npm start'
```
---
**Osobiście polecam Rozwiązanie 1 (Toolbox)** - najprościej i najczystsze dla Fedora Atomic.

22
backend/app/Dockerfile Normal file
View File

@@ -0,0 +1,22 @@
FROM python:3.11-slim
WORKDIR /app
# Install uv package manager
RUN pip install uv
# Copy project configuration
COPY pyproject.toml .
# Copy uv.lock if it exists, otherwise just the pyproject (using wildcard)
COPY uv.lock* ./
# Install dependencies using uv
RUN uv sync
# Copy application code
COPY . .
EXPOSE 8000
# Run the FastAPI server
CMD ["uv", "run", "python", "main.py"]

View File

@@ -1,3 +1,5 @@
from contextlib import asynccontextmanager
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
@@ -5,10 +7,19 @@ from src.config import ALLOWED_ORIGINS
from src.database import init_db from src.database import init_db
from src.routers import init, login, status from src.routers import init, login, status
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Initialize database on startup."""
init_db()
yield
app = FastAPI( app = FastAPI(
title="Archivium Local Backend", title="Archivium Local Backend",
description="Local archive encryption and authentication system", description="Local archive encryption and authentication system",
version="0.1.0", version="0.1.0",
lifespan=lifespan,
) )
app.add_middleware( app.add_middleware(
@@ -23,13 +34,6 @@ app.include_router(init.router)
app.include_router(login.router) app.include_router(login.router)
app.include_router(status.router) app.include_router(status.router)
@app.on_event("startup")
def startup():
"""Initialize database on startup."""
init_db()
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000) uvicorn.run(app, host="127.0.0.1", port=8000)

6
backend/app/package-lock.json generated Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "app",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

1191
backend/app/uv.lock generated Normal file

File diff suppressed because it is too large Load Diff

38
docker-compose.yml Normal file
View File

@@ -0,0 +1,38 @@
version: '3.8'
services:
backend:
build:
context: ./backend/app
dockerfile: Dockerfile
ports:
- "8000:8000"
volumes:
- ./backend/app:/app
# Cache for uv environments to speed up things inside container
- uv-cache:/root/.cache/uv
# Avoid overwriting container's installed venv with local dev venv
- /app/.venv
environment:
- DATABASE_PATH=archivium.db
- ALLOWED_ORIGINS=http://localhost:3000
restart: unless-stopped
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:3000"
volumes:
- ./frontend:/app
# Hide container's node_modules from local disk
- /app/node_modules
environment:
- REACT_APP_API_URL=http://localhost:8000
depends_on:
- backend
restart: unless-stopped
volumes:
uv-cache:

18
frontend/Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
FROM node:18-alpine
WORKDIR /app
# Copy package management files
# Try to catch package-lock.json if it exists
COPY package.json package-lock.json* ./
# Install dependencies (ignoring optional ones if they fail in alpine)
RUN npm install
# Copy application source code
COPY . .
EXPOSE 3000
# Start development server
CMD ["npm", "start"]

View File

@@ -8,6 +8,7 @@
"name": "text-editor", "name": "text-editor",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.6.0",
"@tiptap/extension-character-count": "^3.20.1", "@tiptap/extension-character-count": "^3.20.1",
"@tiptap/extension-image": "^3.20.1", "@tiptap/extension-image": "^3.20.1",
"@tiptap/extension-link": "^3.20.1", "@tiptap/extension-link": "^3.20.1",
@@ -3378,6 +3379,21 @@
"url": "https://github.com/sponsors/gregberge" "url": "https://github.com/sponsors/gregberge"
} }
}, },
"node_modules/@tauri-apps/api": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-1.6.0.tgz",
"integrity": "sha512-rqI++FWClU5I2UBp4HXFvl+sBWkdigBkxnpJDQUWttNyG7IZP4FwQGhTNL5EOw0vI8i6eSAJ5frLqO7n7jbJdg==",
"license": "Apache-2.0 OR MIT",
"engines": {
"node": ">= 14.6.0",
"npm": ">= 6.6.0",
"yarn": ">= 1.19.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/tauri"
}
},
"node_modules/@tiptap/core": { "node_modules/@tiptap/core": {
"version": "3.20.1", "version": "3.20.1",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.1.tgz", "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.1.tgz",

View File

@@ -3,6 +3,7 @@
"version": "0.0.1", "version": "0.0.1",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.6.0",
"@tiptap/extension-character-count": "^3.20.1", "@tiptap/extension-character-count": "^3.20.1",
"@tiptap/extension-image": "^3.20.1", "@tiptap/extension-image": "^3.20.1",
"@tiptap/extension-link": "^3.20.1", "@tiptap/extension-link": "^3.20.1",

View File

@@ -1,12 +1,162 @@
import React from 'react'; import React, { useState, useEffect } from 'react';
import TextEditor from './TextEditor'; import TextEditor from './TextEditor';
import './App.css'; import './App.css';
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
function App() { function App() {
const [appState, setAppState] = useState({
loading: true,
initialized: false,
authenticated: false,
error: null,
});
const [password, setPassword] = useState('');
const [isRecovery, setIsRecovery] = useState(false);
const [recoveryKey, setRecoveryKey] = useState(null);
useEffect(() => {
checkStatus();
}, []);
const checkStatus = async () => {
try {
const res = await fetch(`${API_URL}/api/status`);
const data = await res.json();
setAppState({
...appState,
loading: false,
initialized: data.is_initialized,
error: null,
});
} catch (err) {
setAppState({
...appState,
loading: false,
error: 'Nie można połączyć z backendem.',
initialized: false,
authenticated: false
});
}
};
const handleInit = async (e) => {
e.preventDefault();
try {
const res = await fetch(`${API_URL}/api/init`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password }),
});
const data = await res.json();
if (res.ok) {
setRecoveryKey(data.recovery_key);
setAppState({ ...appState, initialized: true, error: null });
setPassword('');
} else {
setAppState({ ...appState, error: data.detail || 'Błąd inicjalizacji' });
}
} catch (err) {
setAppState({ ...appState, error: 'Błąd połączenia' });
}
};
const handleLogin = async (e) => {
e.preventDefault();
try {
const res = await fetch(`${API_URL}/api/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ password, is_recovery: isRecovery }),
});
const data = await res.json();
if (res.ok && data.status === 'success') {
setAppState({ ...appState, authenticated: true, error: null });
} else {
// If not initialized (e.g. wiped database), reload state
if (res.status === 404) {
checkStatus();
return;
}
setAppState({ ...appState, error: data.detail || 'Błędne hasło/klucz' });
}
} catch (err) {
setAppState({ ...appState, error: 'Błąd połączenia' });
}
};
if (appState.loading) return <div style={{ padding: '20px', textAlign: 'center' }}>Ładowanie zabezpieczeń...</div>;
// Ekran tworzenia konta
if (!appState.initialized) {
return (
<div style={{ maxWidth: '400px', margin: '100px auto', fontFamily: 'sans-serif', textAlign: 'center', padding: '20px', border: '1px solid #ccc', borderRadius: '8px' }}>
<h2>Witamy w Archivium</h2>
<p>Utwórz pierwsze hasło do swojego sejfu.</p>
<form onSubmit={handleInit} style={{ display: 'flex', flexDirection: 'column', gap: '15px', marginTop: '20px' }}>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Główne hasło (min. 8 znaków)"
required
minLength={8}
style={{ padding: '10px', fontSize: '16px' }}
/>
<button type="submit" style={{ padding: '10px', background: '#282c34', color: '#fff', border: 'none', cursor: 'pointer', fontSize: '16px' }}>Zainicjuj Baze</button>
</form>
{appState.error && <p style={{ color: 'red', marginTop: '15px' }}>{appState.error}</p>}
</div>
);
}
// Ekran po inicjalizacji pokazujący wygenerowany klucz odzyskiwania
if (recoveryKey) {
return (
<div style={{ maxWidth: '500px', margin: '100px auto', fontFamily: 'sans-serif', textAlign: 'center', border: '2px solid red', padding: '30px', borderRadius: '8px' }}>
<h2 style={{ color: 'red', marginTop: 0 }}> ZAPISZ TEN KLUCZ</h2>
<p>To jedyny moment, w którym go widzisz. Użyjesz go do odzyskania dostępu, jeśli zapomnisz hasła.</p>
<code style={{ display: 'block', padding: '15px', background: '#eee', fontSize: '18px', margin: '20px 0', wordBreak: 'break-all' }}>
{recoveryKey}
</code>
<button onClick={() => setRecoveryKey(null)} style={{ padding: '10px 20px', background: '#282c34', color: '#fff', cursor: 'pointer', border: 'none', fontSize: '16px' }}>Zapisany, przejdź do logowania</button>
</div>
);
}
// Ekran logowania
if (!appState.authenticated) {
return (
<div style={{ maxWidth: '400px', margin: '100px auto', fontFamily: 'sans-serif', textAlign: 'center', padding: '20px', border: '1px solid #ccc', borderRadius: '8px' }}>
<h2>Logowanie</h2>
<p style={{ color: '#666', marginBottom: '20px' }}>Wprowadź hasło aby odblokować sejf.</p>
<form onSubmit={handleLogin} style={{ display: 'flex', flexDirection: 'column', gap: '15px' }}>
<input
type={isRecovery ? "text" : "password"}
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder={isRecovery ? "Wklej klucz odzyskiwania" : "Twoje hasło"}
required
style={{ padding: '10px', fontSize: '16px' }}
/>
<label style={{ display: 'flex', alignItems: 'center', gap: '10px', justifyContent: 'center', cursor: 'pointer' }}>
<input type="checkbox" checked={isRecovery} onChange={(e) => setIsRecovery(e.target.checked)} />
Zaloguj kluczem odzyskiwania
</label>
<button type="submit" style={{ padding: '10px', background: '#282c34', color: '#fff', border: 'none', cursor: 'pointer', fontSize: '16px' }}>Odblokuj</button>
</form>
{appState.error && <p style={{ color: 'red', marginTop: '15px' }}>{appState.error}</p>}
</div>
);
}
// Główny widok (Text Editor) - po zalogowaniu
return ( return (
<div className="App"> <div className="App">
<header className="App-header" style={{ minHeight: 'auto', padding: '20px', backgroundColor: '#282c34', color: 'white' }}> <header className="App-header" style={{ minHeight: 'auto', padding: '15px 30px', backgroundColor: '#282c34', color: 'white', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h1>Edytor tekstowy</h1> <h1 style={{ margin: 0, fontSize: '22px' }}>Archivium</h1>
<button onClick={() => { setAppState({...appState, authenticated: false}); setPassword(''); }} style={{ padding: '8px 15px', background: '#dc3545', color: '#fff', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>Wyloguj</button>
</header> </header>
<main style={{ padding: '20px' }}> <main style={{ padding: '20px' }}>
<TextEditor /> <TextEditor />

85
justfile Normal file
View File

@@ -0,0 +1,85 @@
# Configuration
set shell := ["bash", "-c"]
export VIRTUAL_ENV := ""
backend_dir := "backend/app"
frontend_dir := "frontend"
venv_bin := backend_dir + "/.venv/bin"
# ============================================================================
# Default: Show help
# ============================================================================
[doc("Show available recipes")]
default:
@just --list
# ============================================================================
# Setup: Initialize dependencies
# ============================================================================
[doc("Install all dependencies")]
install: install-backend install-frontend
[doc("Install backend dependencies with uv")]
install-backend:
cd {{backend_dir}} && uv sync
[doc("Install portable Node.js and frontend dependencies")]
install-frontend: install-backend
@echo "Ensuring Node.js is available..."
@if ! command -v npm >/dev/null 2>&1 && [ ! -f "{{venv_bin}}/npm" ]; then \
echo "Installing portable Node.js inside Python venv..."; \
cd {{backend_dir}} && uv pip install nodeenv && uv run nodeenv -p; \
fi
@echo "Installing frontend dependencies..."
export PATH="$PWD/{{venv_bin}}:$PATH" && cd {{frontend_dir}} && npm install
# ============================================================================
# Development: Run services
# ============================================================================
[doc("Run backend server")]
run-backend: install-backend
cd {{backend_dir}} && uv run python3 main.py
[doc("Run frontend development server")]
run-frontend: install-frontend
export PATH="$PWD/{{venv_bin}}:$PATH" && cd {{frontend_dir}} && npm start
[doc("Run both services (Backend + GUI)")]
dev: install
@echo "Starting backend and frontend..."
export PATH="$PWD/{{venv_bin}}:$PATH"; \
(cd {{backend_dir}} && uv run python3 main.py) & \
BACKEND_PID=$$!; \
sleep 2; \
(cd {{frontend_dir}} && npm start) & \
FRONTEND_PID=$$!; \
trap "kill $$BACKEND_PID $$FRONTEND_PID 2>/dev/null" EXIT INT TERM; \
wait
# ============================================================================
# Build & Clean
# ============================================================================
[doc("Build frontend")]
build-frontend: install-frontend
export PATH="$PWD/{{venv_bin}}:$PATH" && cd {{frontend_dir}} && npm run build
[doc("Clean build artifacts and all dependencies")]
clean-all:
rm -rf {{backend_dir}}/.pytest_cache
rm -rf {{backend_dir}}/__pycache__
rm -rf {{backend_dir}}/src/__pycache__
rm -rf {{frontend_dir}}/build
rm -rf {{backend_dir}}/.venv
rm -rf {{frontend_dir}}/node_modules
find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true
[doc("Check if tools are available")]
check:
@echo "Checking prerequisites..."
@command -v uv >/dev/null 2>&1 && echo "[OK] uv" || echo "[MISSING] uv"
@if command -v node >/dev/null 2>&1 || [ -f "{{venv_bin}}/node" ]; then echo "[OK] Node.js"; else echo "[MISSING] Node.js"; fi
@if command -v npm >/dev/null 2>&1 || [ -f "{{venv_bin}}/npm" ]; then echo "[OK] npm"; else echo "[MISSING] npm"; fi
@command -v python3 >/dev/null 2>&1 && echo "[OK] python3" || echo "[MISSING] python3"

View File

@@ -4,6 +4,7 @@ import os
import sys import sys
import time import time
import webbrowser import webbrowser
import shutil
def install_missing_packages(): def install_missing_packages():
@@ -17,6 +18,62 @@ def install_missing_packages():
subprocess.check_call([sys.executable, "-m", "pip", "install", package]) subprocess.check_call([sys.executable, "-m", "pip", "install", package])
def check_node_js():
"""Sprawdź czy Node.js jest zainstalowany"""
return shutil.which("node") is not None and shutil.which("npm") is not None
def run_backend_only():
"""Uruchom tylko backend"""
base_dir = os.path.dirname(os.path.abspath(__file__))
backend_dir = os.path.join(base_dir, "backend", "app")
print("\n" + "="*60)
print(" ARCHIVIUM - Backend Only Mode")
print("="*60 + "\n")
print("[*] Startuje backend FastAPI na porcie 8000...")
print("[*] Czekaj 2-3 sekundy na zainicjalizowanie...\n")
backend_process = subprocess.Popen(
[sys.executable, "main.py"],
cwd=backend_dir
)
time.sleep(2)
print("\n" + "="*60)
print("✓ Backend uruchomiony!")
print("="*60)
print("\n📡 API dostępne:")
print(" http://localhost:8000/api/status")
print(" http://localhost:8000/docs (Swagger UI)")
print("\n🔧 Testy API:")
print(" curl http://localhost:8000/api/status")
print(" curl -X POST http://localhost:8000/api/init \\")
print(" -H 'Content-Type: application/json' \\")
print(" -d '{\"password\": \"TestPassword123\"}'")
print("\n❌ Frontend nie dostępny (Node.js nie zainstalowany)")
print("\n📝 Aby zainstalować Node.js na Fedora Atomic:")
print(" 1. Za pomocą toolbox:")
print(" toolbox create")
print(" toolbox run sudo dnf install nodejs npm")
print(" 2. Za pomocą brew:")
print(" brew install node")
print(" 3. Lub pobrać z https://nodejs.org/")
print("\n Po instalacji Node.js, uruchom:")
print(" cd frontend && npm install && npm start")
print("\nAby zatrzymać backend: Ctrl+C\n")
try:
backend_process.wait()
except KeyboardInterrupt:
print("\n[*] Zatrzymywanie backendu...")
backend_process.terminate()
backend_process.wait()
print("[✓] Backend zatrzymany")
def run_archivium(): def run_archivium():
"""Uruchom całą aplikację Archivium""" """Uruchom całą aplikację Archivium"""
install_missing_packages() install_missing_packages()
@@ -25,6 +82,23 @@ def run_archivium():
backend_dir = os.path.join(base_dir, "backend", "app") backend_dir = os.path.join(base_dir, "backend", "app")
frontend_dir = os.path.join(base_dir, "frontend") frontend_dir = os.path.join(base_dir, "frontend")
# Sprawdź czy Node.js jest dostępny
has_node = check_node_js()
if not has_node:
print("\n⚠️ Node.js/npm nie znaleziony!")
print("\nOpcje:")
print("1. Uruchomić tylko backend (Enter)")
print("2. Wyjść i zainstalować Node.js (q)\n")
choice = input("Wybór [1/q]: ").strip().lower()
if choice == 'q':
print("[*] Wyjście. Zainstaluj Node.js i uruchom ponownie.")
sys.exit(1)
else:
run_backend_only()
return
print("\n" + "="*50) print("\n" + "="*50)
print(" ARCHIVIUM - System Zarządzania Archiwum") print(" ARCHIVIUM - System Zarządzania Archiwum")
print("="*50 + "\n") print("="*50 + "\n")
@@ -36,16 +110,11 @@ def run_archivium():
) )
print("[2/3] Startuje frontend (Port 3000)...") print("[2/3] Startuje frontend (Port 3000)...")
try:
frontend_process = subprocess.Popen( frontend_process = subprocess.Popen(
"npm start", "npm start",
shell=True, shell=True,
cwd=frontend_dir, 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...") print("[3/3] Otwieranie przeglądarki...")
time.sleep(3) time.sleep(3)
@@ -66,15 +135,12 @@ def run_archivium():
try: try:
backend_process.wait() backend_process.wait()
if frontend_process:
frontend_process.wait() frontend_process.wait()
except KeyboardInterrupt: except KeyboardInterrupt:
print("\n[*] Zatrzymywanie systemu...") print("\n[*] Zatrzymywanie systemu...")
backend_process.terminate() backend_process.terminate()
if frontend_process:
frontend_process.terminate() frontend_process.terminate()
backend_process.wait() backend_process.wait()
if frontend_process:
frontend_process.wait() frontend_process.wait()
print("[✓] System zatrzymany") print("[✓] System zatrzymany")

109
main.py Normal file
View File

@@ -0,0 +1,109 @@
"""Archivium Launcher"""
import subprocess
import os
import sys
import time
import shutil
def check_dependencies():
"""Check if required packages are installed"""
packages = ["uvicorn", "fastapi", "sqlalchemy", "passlib"]
missing = []
for package in packages:
try:
__import__(package.split("[")[0].replace("-", "_"))
except ImportError:
missing.append(package)
if missing:
print(f"Missing dependencies: {', '.join(missing)}")
print("\nInstall with:")
print(" cd backend/app")
print(" uv sync")
sys.exit(1)
def check_node_js():
"""Check if Node.js is available"""
return shutil.which("node") is not None and shutil.which("npm") is not None
def run_backend():
"""Run backend only"""
base_dir = os.path.dirname(os.path.abspath(__file__))
backend_dir = os.path.join(base_dir, "backend", "app")
print("Starting backend on port 8000...")
backend_process = subprocess.Popen(
["uv", "run", "python", "main.py"],
cwd=backend_dir
)
try:
backend_process.wait()
except KeyboardInterrupt:
backend_process.terminate()
backend_process.wait()
def run_full_stack():
"""Run backend and frontend"""
check_dependencies()
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")
if not check_node_js():
if not sys.stdin.isatty():
run_backend()
return
response = input("Node.js not found. Run backend only? (y/n): ").strip().lower()
if response == 'y':
run_backend()
return
print("Starting backend and frontend...")
backend_process = subprocess.Popen(
["uv", "run", "python", "main.py"],
cwd=backend_dir
)
time.sleep(2)
frontend_process = subprocess.Popen(
"npm start",
shell=True,
cwd=frontend_dir,
)
try:
backend_process.wait()
frontend_process.wait()
except KeyboardInterrupt:
backend_process.terminate()
frontend_process.terminate()
backend_process.wait()
frontend_process.wait()
if __name__ == "__main__":
if len(sys.argv) > 1:
if sys.argv[1] == "--backend-only":
check_dependencies()
run_backend()
elif sys.argv[1] == "--help":
print("Usage: python main.py [option]")
print("\nOptions:")
print(" --backend-only Run backend only")
print(" --help Show this message")
else:
print(f"Unknown option: {sys.argv[1]}")
else:
run_full_stack()

13
pyproject.toml Normal file
View File

@@ -0,0 +1,13 @@
[project]
name = "archivium"
version = "0.1.0"
description = "Archivium - Archive Management System"
requires-python = ">=3.9"
dependencies = [
"fastapi>=0.104.0",
"uvicorn[standard]>=0.24.0",
"pydantic>=2.5.0",
"sqlalchemy>=2.0.0",
"passlib[argon2]>=1.7.4",
"python-dotenv>=1.2.0",
]

1281
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff