v2.0.0: Multi-user authentication with JWT
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 16s

Features:
- User registration and login with JWT tokens
- All data is now user-specific (multi-tenancy)
- Profile page with integrations and logout
- Automatic migration of existing data to first user

Backend changes:
- Added users and refresh_tokens tables
- Added user_id to all data tables (projects, entries, nodes, dictionaries, words, progress, configs, telegram_integrations, weekly_goals)
- JWT authentication middleware
- claimOrphanedData() for data migration

Frontend changes:
- AuthContext for state management
- Login/Register forms
- Profile page (replaced Integrations)
- All API calls use authFetch with Bearer token

Migration notes:
- On first deploy, backend automatically adds user_id columns
- First user to login claims all existing data
This commit is contained in:
poignatov
2026-01-01 18:21:18 +03:00
parent 6015b62d29
commit 4a06ceb7f6
23 changed files with 1970 additions and 279 deletions

View File

@@ -1,9 +1,11 @@
import React, { useState, useEffect } from 'react'
import { useAuth } from './auth/AuthContext'
import './WordList.css'
const API_URL = '/api'
function WordList({ onNavigate, dictionaryId, isNewDictionary, refreshTrigger = 0 }) {
const { authFetch } = useAuth()
const [words, setWords] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
@@ -44,7 +46,7 @@ function WordList({ onNavigate, dictionaryId, isNewDictionary, refreshTrigger =
const fetchDictionary = async (dictId) => {
try {
const response = await fetch(`${API_URL}/dictionaries`)
const response = await authFetch(`${API_URL}/dictionaries`)
if (!response.ok) {
throw new Error('Ошибка при загрузке словарей')
}
@@ -74,7 +76,7 @@ function WordList({ onNavigate, dictionaryId, isNewDictionary, refreshTrigger =
try {
setLoading(true)
const url = `${API_URL}/words?dictionary_id=${dictId}`
const response = await fetch(url)
const response = await authFetch(url)
if (!response.ok) {
throw new Error('Ошибка при загрузке слов')
}
@@ -102,7 +104,7 @@ function WordList({ onNavigate, dictionaryId, isNewDictionary, refreshTrigger =
try {
if (!hasValidDictionary(currentDictionaryId)) {
// Create new dictionary
const response = await fetch(`${API_URL}/dictionaries`, {
const response = await authFetch(`${API_URL}/dictionaries`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -131,7 +133,7 @@ function WordList({ onNavigate, dictionaryId, isNewDictionary, refreshTrigger =
onNavigate?.('words', { dictionaryId: newDictionaryId })
} else if (hasValidDictionary(currentDictionaryId)) {
// Update existing dictionary (rename)
const response = await fetch(`${API_URL}/dictionaries/${currentDictionaryId}`, {
const response = await authFetch(`${API_URL}/dictionaries/${currentDictionaryId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',