force rewrited code
This commit is contained in:
83
CHANGELOG.md
83
CHANGELOG.md
@@ -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
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.
|
||||||
22
backend/app/Dockerfile
Normal file
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"]
|
||||||
@@ -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
6
backend/app/package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "app",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
1191
backend/app/uv.lock
generated
Normal file
1191
backend/app/uv.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
38
docker-compose.yml
Normal file
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
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"]
|
||||||
16
frontend/package-lock.json
generated
16
frontend/package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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
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"
|
||||||
98
launcher.py
98
launcher.py
@@ -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,16 +135,13 @@ 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
109
main.py
Normal 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
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",
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user