Compare commits
4 Commits
01e39d1fc1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a7cf8518d | ||
|
|
6bbb24e633 | ||
|
|
fddaad962b | ||
|
|
cf4fc4acfc |
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>
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import sqlite3
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
from fastapi import FastAPI, Body
|
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
|
||||||
from sentence_transformers import SentenceTransformer
|
|
||||||
import uvicorn
|
|
||||||
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
app.add_middleware(
|
|
||||||
CORSMiddleware,
|
|
||||||
allow_origins=["*"],
|
|
||||||
allow_methods=["*"],
|
|
||||||
allow_headers=["*"],
|
|
||||||
)
|
|
||||||
|
|
||||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
DB_FILE = os.path.join(BASE_DIR, "archivium.db")
|
|
||||||
MODEL_DIR = os.path.join(BASE_DIR, "local_model_miniLM")
|
|
||||||
|
|
||||||
if not os.path.exists(MODEL_DIR):
|
|
||||||
model = SentenceTransformer('all-MiniLM-L6-v2')
|
|
||||||
model.save(MODEL_DIR)
|
|
||||||
else:
|
|
||||||
model = SentenceTransformer(MODEL_DIR)
|
|
||||||
|
|
||||||
def init_db():
|
|
||||||
conn = sqlite3.connect(DB_FILE)
|
|
||||||
conn.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS documents
|
|
||||||
(
|
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
title TEXT UNIQUE,
|
|
||||||
content TEXT,
|
|
||||||
embedding TEXT
|
|
||||||
)
|
|
||||||
""")
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
init_db()
|
|
||||||
|
|
||||||
@app.post("/save-document")
|
|
||||||
async def save_document(data: dict = Body(...)):
|
|
||||||
title = data.get("title")
|
|
||||||
content = data.get("content")
|
|
||||||
|
|
||||||
text_to_vector = f"{title} {str(content)}"
|
|
||||||
vector = model.encode(text_to_vector).tolist()
|
|
||||||
|
|
||||||
conn = sqlite3.connect(DB_FILE)
|
|
||||||
try:
|
|
||||||
conn.execute("""
|
|
||||||
INSERT INTO documents (title, content, embedding)
|
|
||||||
VALUES (?, ?, ?) ON CONFLICT(title) DO
|
|
||||||
UPDATE SET
|
|
||||||
content=excluded.content,
|
|
||||||
embedding=excluded.embedding
|
|
||||||
""", (title, json.dumps(content), json.dumps(vector)))
|
|
||||||
conn.commit()
|
|
||||||
return {"status": "success"}
|
|
||||||
except Exception as e:
|
|
||||||
return {"status": "error", "message": str(e)}
|
|
||||||
finally:
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
@app.get("/load-document")
|
|
||||||
async def load_document(title: str = None):
|
|
||||||
conn = sqlite3.connect(DB_FILE)
|
|
||||||
if title:
|
|
||||||
row = conn.execute("SELECT title, content FROM documents WHERE title = ?", (title,)).fetchone()
|
|
||||||
else:
|
|
||||||
row = conn.execute("SELECT title, content FROM documents ORDER BY id DESC LIMIT 1").fetchone()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
if row:
|
|
||||||
return {"title": row[0], "content": json.loads(row[1])}
|
|
||||||
return {"error": "Nie znaleziono dokumentu"}
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
uvicorn.run(app, host="127.0.0.1", port=8000)
|
|
||||||
141
INSTALL_NODEJS.md
Normal 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.
|
||||||
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
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import TextEditor from './TextEditor';
|
|
||||||
import './App.css';
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
return (
|
|
||||||
<div className="App">
|
|
||||||
<header className="App-header" style={{ minHeight: 'auto', padding: '20px', backgroundColor: '#282c34', color: 'white' }}>
|
|
||||||
<h1>Edytor tekstowy</h1>
|
|
||||||
</header>
|
|
||||||
<main style={{ padding: '20px' }}>
|
|
||||||
<TextEditor />
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
22
backend/app/Dockerfile
Normal 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"]
|
||||||
39
backend/app/main.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
"""Initialize database on startup."""
|
||||||
|
init_db()
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI(
|
||||||
|
title="Archivium Local Backend",
|
||||||
|
description="Local archive encryption and authentication system",
|
||||||
|
version="0.1.0",
|
||||||
|
lifespan=lifespan,
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="127.0.0.1", port=8000)
|
||||||
6
backend/app/package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "app",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
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)
|
||||||
1191
backend/app/uv.lock
generated
Normal file
101
backend/legacy/relational_database.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import sqlite3
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from fastapi import FastAPI, Body, HTTPException
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
DB_FILE = os.path.join(BASE_DIR, "archivium.db")
|
||||||
|
|
||||||
|
|
||||||
|
def get_db_connection():
|
||||||
|
conn = sqlite3.connect(DB_FILE)
|
||||||
|
conn.execute("PRAGMA journal_mode=WAL;")
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
return conn
|
||||||
|
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
conn.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS archive
|
||||||
|
(
|
||||||
|
id
|
||||||
|
INTEGER
|
||||||
|
PRIMARY
|
||||||
|
KEY
|
||||||
|
AUTOINCREMENT,
|
||||||
|
filename
|
||||||
|
TEXT
|
||||||
|
UNIQUE,
|
||||||
|
ocr_text
|
||||||
|
TEXT,
|
||||||
|
metadata
|
||||||
|
TEXT,
|
||||||
|
created_at
|
||||||
|
TIMESTAMP
|
||||||
|
DEFAULT
|
||||||
|
CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
init_db()
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/save-document")
|
||||||
|
async def save_document(data: dict = Body(...)):
|
||||||
|
title = data.get("title")
|
||||||
|
content = data.get("content")
|
||||||
|
|
||||||
|
if not title or content is None:
|
||||||
|
raise HTTPException(status_code=400, detail="Missing title or content")
|
||||||
|
|
||||||
|
content_str = json.dumps(content)
|
||||||
|
|
||||||
|
try:
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
conn.execute("""
|
||||||
|
INSERT INTO archive (filename, ocr_text)
|
||||||
|
VALUES (?, ?) ON CONFLICT(filename) DO
|
||||||
|
UPDATE SET
|
||||||
|
ocr_text=excluded.ocr_text
|
||||||
|
""", (title, content_str))
|
||||||
|
conn.commit()
|
||||||
|
return {"status": "success"}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/load-document")
|
||||||
|
async def load_document(title: str = None):
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
if title:
|
||||||
|
row = conn.execute("SELECT filename, ocr_text FROM archive WHERE filename = ?", (title,)).fetchone()
|
||||||
|
else:
|
||||||
|
row = conn.execute("SELECT filename, ocr_text FROM archive ORDER BY id DESC LIMIT 1").fetchone()
|
||||||
|
|
||||||
|
if row:
|
||||||
|
try:
|
||||||
|
content_val = json.loads(row['ocr_text'])
|
||||||
|
except:
|
||||||
|
content_val = row['ocr_text']
|
||||||
|
|
||||||
|
return {"title": row['filename'], "content": content_val}
|
||||||
|
|
||||||
|
raise HTTPException(status_code=404, detail="Document not found")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
uvicorn.run(app, host="127.0.0.1", port=8000)
|
||||||
134
backend/legacy/vector_database.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
import numpy as np
|
||||||
|
from fastapi import FastAPI, Body, HTTPException
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from sentence_transformers import SentenceTransformer
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
DB_FILE = os.path.join(BASE_DIR, "assets.db")
|
||||||
|
MODEL_DIR = os.path.join(BASE_DIR, "local_model_miniLM")
|
||||||
|
|
||||||
|
|
||||||
|
if not os.path.exists(MODEL_DIR):
|
||||||
|
model = SentenceTransformer('all-MiniLM-L6-v2')
|
||||||
|
model.save(MODEL_DIR)
|
||||||
|
else:
|
||||||
|
model = SentenceTransformer(MODEL_DIR)
|
||||||
|
|
||||||
|
|
||||||
|
def get_db_connection():
|
||||||
|
conn = sqlite3.connect(DB_FILE)
|
||||||
|
|
||||||
|
conn.execute("PRAGMA journal_mode=WAL;")
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
return conn
|
||||||
|
|
||||||
|
|
||||||
|
def init_db():
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
conn.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS documents
|
||||||
|
(
|
||||||
|
id
|
||||||
|
INTEGER
|
||||||
|
PRIMARY
|
||||||
|
KEY
|
||||||
|
AUTOINCREMENT,
|
||||||
|
title
|
||||||
|
TEXT
|
||||||
|
UNIQUE,
|
||||||
|
content
|
||||||
|
BLOB,
|
||||||
|
content_type
|
||||||
|
TEXT,
|
||||||
|
embedding
|
||||||
|
BLOB
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
|
init_db()
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/save-document")
|
||||||
|
async def save_document(
|
||||||
|
title: str = Body(...),
|
||||||
|
content: str = Body(...),
|
||||||
|
content_type: str = Body("text/plain")
|
||||||
|
):
|
||||||
|
|
||||||
|
vector = model.encode(f"{title} {content}").astype(np.float32).tobytes()
|
||||||
|
|
||||||
|
try:
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
conn.execute("""
|
||||||
|
INSERT INTO documents (title, content, content_type, embedding)
|
||||||
|
VALUES (?, ?, ?, ?) ON CONFLICT(title) DO
|
||||||
|
UPDATE SET
|
||||||
|
content=excluded.content,
|
||||||
|
content_type=excluded.content_type,
|
||||||
|
embedding=excluded.embedding
|
||||||
|
""", (title, content.encode('utf-8'), content_type, vector))
|
||||||
|
conn.commit()
|
||||||
|
return {"status": "success", "message": f"Dokument '{title}' zapisany."}
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/search")
|
||||||
|
async def search_similar(query: str = Body(..., embed=True), top_k: int = 3):
|
||||||
|
"""Wyszukiwanie semantyczne (Vector Search)"""
|
||||||
|
query_vector = model.encode(query).astype(np.float32)
|
||||||
|
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
cursor = conn.execute("SELECT title, content, embedding FROM documents")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for row in rows:
|
||||||
|
db_vector = np.frombuffer(row['embedding'], dtype=np.float32)
|
||||||
|
|
||||||
|
|
||||||
|
score = np.dot(query_vector, db_vector) / (np.linalg.norm(query_vector) * np.linalg.norm(db_vector))
|
||||||
|
|
||||||
|
results.append({
|
||||||
|
"title": row['title'],
|
||||||
|
"content": row['content'].decode('utf-8', errors='ignore'),
|
||||||
|
"score": float(score)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
results = sorted(results, key=lambda x: x['score'], reverse=True)[:top_k]
|
||||||
|
return {"results": results}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/load-document")
|
||||||
|
async def load_document(title: str = None):
|
||||||
|
with get_db_connection() as conn:
|
||||||
|
if title:
|
||||||
|
row = conn.execute("SELECT title, content FROM documents WHERE title = ?", (title,)).fetchone()
|
||||||
|
else:
|
||||||
|
row = conn.execute("SELECT title, content FROM documents ORDER BY id DESC LIMIT 1").fetchone()
|
||||||
|
|
||||||
|
if row:
|
||||||
|
return {
|
||||||
|
"title": row['title'],
|
||||||
|
"content": row['content'].decode('utf-8', errors='ignore')
|
||||||
|
}
|
||||||
|
return {"error": "Nie znaleziono dokumentu"}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
uvicorn.run(app, host="127.0.0.1", port=8000)
|
||||||
38
docker-compose.yml
Normal 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
@@ -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"]
|
||||||
@@ -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",
|
||||||
@@ -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",
|
||||||
|
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 |
168
frontend/src/App.js
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import TextEditor from './TextEditor';
|
||||||
|
import './App.css';
|
||||||
|
|
||||||
|
const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="App">
|
||||||
|
<header className="App-header" style={{ minHeight: 'auto', padding: '15px 30px', backgroundColor: '#282c34', color: 'white', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<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>
|
||||||
|
<main style={{ padding: '20px' }}>
|
||||||
|
<TextEditor />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
85
justfile
Normal 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"
|
||||||
149
launcher.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
"""Archivium Launcher - Uruchamia całą aplikację"""
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import webbrowser
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
|
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 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():
|
||||||
|
"""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")
|
||||||
|
|
||||||
|
# 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(" 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)...")
|
||||||
|
frontend_process = subprocess.Popen(
|
||||||
|
"npm start",
|
||||||
|
shell=True,
|
||||||
|
cwd=frontend_dir,
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
frontend_process.wait()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n[*] Zatrzymywanie systemu...")
|
||||||
|
backend_process.terminate()
|
||||||
|
frontend_process.terminate()
|
||||||
|
backend_process.wait()
|
||||||
|
frontend_process.wait()
|
||||||
|
print("[✓] System zatrzymany")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run_archivium()
|
||||||
101
main.py
@@ -1,56 +1,109 @@
|
|||||||
|
"""Archivium Launcher"""
|
||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import webbrowser
|
import shutil
|
||||||
|
|
||||||
|
|
||||||
def install_missing_packages():
|
def check_dependencies():
|
||||||
packages = ["uvicorn", "fastapi", "sentence-transformers"]
|
"""Check if required packages are installed"""
|
||||||
|
packages = ["uvicorn", "fastapi", "sqlalchemy", "passlib"]
|
||||||
|
missing = []
|
||||||
|
|
||||||
for package in packages:
|
for package in packages:
|
||||||
try:
|
try:
|
||||||
__import__(package.replace("-", "_"))
|
__import__(package.split("[")[0].replace("-", "_"))
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print(f"[*] Instalowanie brakującej biblioteki: {package}...")
|
missing.append(package)
|
||||||
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
|
|
||||||
|
if missing:
|
||||||
|
print(f"Missing dependencies: {', '.join(missing)}")
|
||||||
|
print("\nInstall with:")
|
||||||
|
print(" cd backend/app")
|
||||||
|
print(" uv sync")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def run_archivium():
|
def check_node_js():
|
||||||
install_missing_packages()
|
"""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__))
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
backend_dir = os.path.join(base_dir, "backend", "app")
|
||||||
|
|
||||||
backend_script = os.path.join(base_dir, "Database", "database.py")
|
print("Starting backend on port 8000...")
|
||||||
frontend_dir = os.path.join(base_dir, "TextEditor")
|
|
||||||
|
|
||||||
print("\n--- URUCHAMIANIE SYSTEMU ARCHIVIUM ---")
|
|
||||||
|
|
||||||
print(f"[*] Startuję bazę danych...")
|
|
||||||
backend_process = subprocess.Popen(
|
backend_process = subprocess.Popen(
|
||||||
[sys.executable, backend_script],
|
["uv", "run", "python", "main.py"],
|
||||||
cwd=os.path.join(base_dir, "Database")
|
cwd=backend_dir
|
||||||
)
|
)
|
||||||
|
|
||||||
print(f"[*] Startuję edytor...")
|
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(
|
frontend_process = subprocess.Popen(
|
||||||
"npm start",
|
"npm start",
|
||||||
shell=True,
|
shell=True,
|
||||||
cwd=frontend_dir,
|
cwd=frontend_dir,
|
||||||
creationflags=subprocess.CREATE_NEW_CONSOLE
|
|
||||||
)
|
)
|
||||||
|
|
||||||
print("[*] Oczekiwanie na gotowość...")
|
|
||||||
time.sleep(5)
|
|
||||||
|
|
||||||
webbrowser.open("http://localhost:3000")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
backend_process.wait()
|
backend_process.wait()
|
||||||
frontend_process.wait()
|
frontend_process.wait()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("\n[*] Zamykanie systemu...")
|
|
||||||
backend_process.terminate()
|
backend_process.terminate()
|
||||||
frontend_process.terminate()
|
frontend_process.terminate()
|
||||||
|
backend_process.wait()
|
||||||
|
frontend_process.wait()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
run_archivium()
|
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
@@ -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",
|
||||||
|
]
|
||||||