1024 lines
25 KiB
Markdown
1024 lines
25 KiB
Markdown
# 🤝 Руководство по внесению вклада в Ospabhost 8.1
|
||
|
||
Спасибо за интерес к улучшению проекта! Этот документ описывает процесс внесения изменений.
|
||
|
||
---
|
||
|
||
## 📋 Содержание
|
||
|
||
- [Кодекс поведения](#кодекс-поведения)
|
||
- [С чего начать](#с-чего-начать)
|
||
- [Процесс разработки](#процесс-разработки)
|
||
- [Стандарты кода](#стандарты-кода)
|
||
- [Коммиты](#коммиты)
|
||
- [Pull Requests](#pull-requests)
|
||
- [Архитектурные решения](#архитектурные-решения)
|
||
- [Тестирование](#тестирование)
|
||
- [Документация](#документация)
|
||
|
||
---
|
||
|
||
## 📜 Кодекс поведения
|
||
|
||
### Наши обязательства
|
||
|
||
- Уважительное отношение ко всем участникам
|
||
- Конструктивная критика
|
||
- Фокус на улучшении проекта
|
||
- Помощь новичкам
|
||
|
||
### Недопустимое поведение
|
||
|
||
- Оскорбления и агрессия
|
||
- Троллинг и спам
|
||
- Дискриминация любого рода
|
||
- Публикация личной информации без разрешения
|
||
|
||
---
|
||
|
||
## 🚀 С чего начать
|
||
|
||
### Для новичков
|
||
|
||
Ищите issues с метками:
|
||
- `good first issue` - простые задачи для начинающих
|
||
- `help wanted` - задачи, где нужна помощь
|
||
- `documentation` - улучшение документации
|
||
|
||
### Подготовка окружения
|
||
|
||
1. **Форк репозитория**
|
||
|
||
Нажмите кнопку "Fork" на GitHub.
|
||
|
||
2. **Клонирование**
|
||
|
||
```bash
|
||
git clone https://github.com/YOUR_USERNAME/ospabhost8.1.git
|
||
cd ospabhost8.1/ospabhost
|
||
```
|
||
|
||
3. **Добавление upstream**
|
||
|
||
```bash
|
||
git remote add upstream https://github.com/Ospab/ospabhost8.1.git
|
||
```
|
||
|
||
4. **Установка зависимостей**
|
||
|
||
```bash
|
||
# Backend
|
||
cd backend
|
||
npm install
|
||
cp .env.example .env
|
||
# Настройте .env
|
||
|
||
# Миграции
|
||
npx prisma migrate dev
|
||
npx prisma generate
|
||
npx prisma db seed
|
||
|
||
# Frontend
|
||
cd ../frontend
|
||
npm install
|
||
cp .env.example .env
|
||
```
|
||
|
||
5. **Запуск**
|
||
|
||
```bash
|
||
# Terminal 1: Backend
|
||
cd backend
|
||
npm run dev
|
||
|
||
# Terminal 2: Frontend
|
||
cd frontend
|
||
npm run dev
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 Процесс разработки
|
||
|
||
### 1. Синхронизация с upstream
|
||
|
||
```bash
|
||
git checkout main
|
||
git fetch upstream
|
||
git merge upstream/main
|
||
git push origin main
|
||
```
|
||
|
||
### 2. Создание ветки
|
||
|
||
```bash
|
||
git checkout -b feature/your-feature-name
|
||
```
|
||
|
||
**Префиксы веток:**
|
||
- `feature/` - новая функциональность
|
||
- `fix/` - исправление бага
|
||
- `refactor/` - рефакторинг кода
|
||
- `docs/` - изменения в документации
|
||
- `test/` - добавление тестов
|
||
- `chore/` - рутинные задачи
|
||
|
||
**Примеры:**
|
||
```bash
|
||
git checkout -b feature/add-user-notifications
|
||
git checkout -b fix/ticket-assignment-bug
|
||
git checkout -b docs/update-api-documentation
|
||
```
|
||
|
||
### 3. Разработка
|
||
|
||
Внесите изменения, следуя [стандартам кода](#стандарты-кода).
|
||
|
||
### 4. Коммиты
|
||
|
||
```bash
|
||
git add .
|
||
git commit -m "feat: add user notifications"
|
||
```
|
||
|
||
См. [раздел о коммитах](#коммиты) для подробностей.
|
||
|
||
### 5. Пуш
|
||
|
||
```bash
|
||
git push origin feature/your-feature-name
|
||
```
|
||
|
||
### 6. Pull Request
|
||
|
||
Откройте PR на GitHub, следуя [шаблону](#pull-requests).
|
||
|
||
---
|
||
|
||
## 📝 Стандарты кода
|
||
|
||
### TypeScript/JavaScript
|
||
|
||
#### Общие правила
|
||
|
||
- ✅ Используйте TypeScript везде
|
||
- ✅ Строгая типизация (`strict: true`)
|
||
- ❌ Избегайте `any` (используйте `unknown` при необходимости)
|
||
- ✅ Используйте `const` и `let`, не `var`
|
||
- ✅ Предпочитайте стрелочные функции
|
||
- ✅ Асинхронный код через `async/await`
|
||
|
||
#### Именование
|
||
|
||
```typescript
|
||
// ✅ Хорошо
|
||
const userName = 'John';
|
||
const getUserById = async (id: number) => { ... };
|
||
class UserService { ... }
|
||
interface UserData { ... }
|
||
type UserId = number;
|
||
|
||
// ❌ Плохо
|
||
const username = 'John'; // camelCase для переменных
|
||
const get_user_by_id = () => { ... }; // не snake_case
|
||
class userService { ... } // PascalCase для классов
|
||
```
|
||
|
||
#### Функции и методы
|
||
|
||
```typescript
|
||
// ✅ Хорошо
|
||
async function getUserById(id: number): Promise<User | null> {
|
||
try {
|
||
const user = await prisma.user.findUnique({ where: { id } });
|
||
return user;
|
||
} catch (error) {
|
||
console.error('Error fetching user:', error);
|
||
throw new Error('Failed to fetch user');
|
||
}
|
||
}
|
||
|
||
// ❌ Плохо
|
||
function getUserById(id: any) { // any запрещён
|
||
const user = prisma.user.findUnique({ where: { id } }); // нет await
|
||
return user; // нет обработки ошибок
|
||
}
|
||
```
|
||
|
||
#### Обработка ошибок
|
||
|
||
```typescript
|
||
// ✅ Хорошо
|
||
try {
|
||
const result = await riskyOperation();
|
||
return res.json(result);
|
||
} catch (error) {
|
||
console.error('[Module] Error:', error);
|
||
const message = error instanceof Error
|
||
? error.message
|
||
: 'Unknown error';
|
||
return res.status(500).json({ error: message });
|
||
}
|
||
|
||
// ❌ Плохо
|
||
try {
|
||
const result = await riskyOperation();
|
||
return res.json(result);
|
||
} catch (error) {
|
||
return res.status(500).json({ error: error }); // может быть не Error
|
||
}
|
||
```
|
||
|
||
#### Express Controllers
|
||
|
||
```typescript
|
||
// ✅ Хорошо
|
||
export async function createServer(req: Request, res: Response) {
|
||
try {
|
||
const userId = (req as any).user?.id;
|
||
if (!userId) {
|
||
return res.status(401).json({ error: 'Unauthorized' });
|
||
}
|
||
|
||
const { osId, tariffId } = req.body;
|
||
if (!osId || !tariffId) {
|
||
return res.status(400).json({ error: 'Missing required fields' });
|
||
}
|
||
|
||
const server = await serverService.createServer({
|
||
userId,
|
||
osId: Number(osId),
|
||
tariffId: Number(tariffId),
|
||
});
|
||
|
||
return res.json({ server });
|
||
} catch (error) {
|
||
console.error('[Server] Create error:', error);
|
||
const message = error instanceof Error ? error.message : 'Server creation failed';
|
||
return res.status(500).json({ error: message });
|
||
}
|
||
}
|
||
```
|
||
|
||
### React/Frontend
|
||
|
||
#### Компоненты
|
||
|
||
```typescript
|
||
// ✅ Хорошо - функциональный компонент с типизацией
|
||
interface UserCardProps {
|
||
user: User;
|
||
onEdit: (userId: number) => void;
|
||
}
|
||
|
||
const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => {
|
||
const [isLoading, setIsLoading] = useState(false);
|
||
|
||
const handleEdit = useCallback(() => {
|
||
onEdit(user.id);
|
||
}, [user.id, onEdit]);
|
||
|
||
return (
|
||
<div className="bg-white rounded-lg shadow p-4">
|
||
<h3 className="text-lg font-semibold">{user.name}</h3>
|
||
<button
|
||
onClick={handleEdit}
|
||
disabled={isLoading}
|
||
className="mt-2 px-4 py-2 bg-blue-600 text-white rounded"
|
||
>
|
||
Edit
|
||
</button>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default UserCard;
|
||
```
|
||
|
||
#### Hooks
|
||
|
||
```typescript
|
||
// ✅ Хорошо - кастомный хук с типизацией
|
||
interface UseUserDataReturn {
|
||
user: User | null;
|
||
loading: boolean;
|
||
error: string | null;
|
||
refetch: () => Promise<void>;
|
||
}
|
||
|
||
export const useUserData = (userId: number): UseUserDataReturn => {
|
||
const [user, setUser] = useState<User | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
const [error, setError] = useState<string | null>(null);
|
||
|
||
const fetchUser = useCallback(async () => {
|
||
try {
|
||
setLoading(true);
|
||
const response = await apiClient.get(`/users/${userId}`);
|
||
setUser(response.data);
|
||
setError(null);
|
||
} catch (err) {
|
||
setError(err instanceof Error ? err.message : 'Unknown error');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [userId]);
|
||
|
||
useEffect(() => {
|
||
fetchUser();
|
||
}, [fetchUser]);
|
||
|
||
return { user, loading, error, refetch: fetchUser };
|
||
};
|
||
```
|
||
|
||
### Prisma
|
||
|
||
#### Схема
|
||
|
||
```prisma
|
||
// ✅ Хорошо - явные типы и связи
|
||
model User {
|
||
id Int @id @default(autoincrement())
|
||
email String @unique
|
||
username String @unique
|
||
password String
|
||
balance Float @default(0)
|
||
isAdmin Boolean @default(false)
|
||
operator Boolean @default(false)
|
||
createdAt DateTime @default(now())
|
||
updatedAt DateTime @updatedAt
|
||
|
||
servers Server[]
|
||
tickets Ticket[]
|
||
posts Post[]
|
||
comments Comment[]
|
||
|
||
@@index([email])
|
||
@@index([username])
|
||
}
|
||
```
|
||
|
||
#### Запросы
|
||
|
||
```typescript
|
||
// ✅ Хорошо - типизированные запросы с обработкой
|
||
async function getServerWithRelations(id: number, userId: number) {
|
||
const server = await prisma.server.findFirst({
|
||
where: {
|
||
id,
|
||
userId, // Проверка владельца
|
||
},
|
||
include: {
|
||
os: true,
|
||
tariff: true,
|
||
user: {
|
||
select: {
|
||
id: true,
|
||
username: true,
|
||
email: true,
|
||
},
|
||
},
|
||
},
|
||
});
|
||
|
||
if (!server) {
|
||
throw new Error('Server not found or access denied');
|
||
}
|
||
|
||
return server;
|
||
}
|
||
```
|
||
|
||
### SQL/Миграции
|
||
|
||
```sql
|
||
-- ✅ Хорошо - явные имена, индексы, значения по умолчанию
|
||
CREATE TABLE `StoragePlan` (
|
||
`id` INTEGER NOT NULL AUTO_INCREMENT,
|
||
`code` VARCHAR(191) NOT NULL,
|
||
`name` VARCHAR(191) NOT NULL,
|
||
`price` DECIMAL(10, 2) NOT NULL DEFAULT 0,
|
||
`pricePerGb` DECIMAL(10, 2) NULL,
|
||
`bandwidthPerGb` DECIMAL(10, 2) NULL,
|
||
`requestsPerGb` INTEGER NULL,
|
||
`quotaGb` INTEGER NOT NULL DEFAULT 0,
|
||
`bandwidthGb` INTEGER NOT NULL DEFAULT 0,
|
||
`requestLimit` VARCHAR(191) NOT NULL DEFAULT '0',
|
||
`description` TEXT NULL,
|
||
`order` INTEGER NOT NULL DEFAULT 0,
|
||
`isActive` BOOLEAN NOT NULL DEFAULT true,
|
||
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||
`updatedAt` DATETIME(3) NOT NULL,
|
||
|
||
UNIQUE INDEX `StoragePlan_code_key`(`code`),
|
||
INDEX `StoragePlan_isActive_idx`(`isActive`),
|
||
INDEX `StoragePlan_order_idx`(`order`),
|
||
PRIMARY KEY (`id`)
|
||
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||
```
|
||
|
||
---
|
||
|
||
## 📝 Коммиты
|
||
|
||
### Conventional Commits
|
||
|
||
Используем формат: `<type>(<scope>): <subject>`
|
||
|
||
**Types:**
|
||
- `feat` - новая функциональность
|
||
- `fix` - исправление бага
|
||
- `docs` - изменения в документации
|
||
- `style` - форматирование, отступы (не CSS)
|
||
- `refactor` - рефакторинг без изменения функциональности
|
||
- `perf` - улучшение производительности
|
||
- `test` - добавление тестов
|
||
- `chore` - обновление зависимостей, настройки
|
||
|
||
**Scope (опционально):**
|
||
- `server` - VPS модуль
|
||
- `storage` - S3 модуль
|
||
- `blog` - блог модуль
|
||
- `ticket` - тикеты
|
||
- `auth` - авторизация
|
||
- `admin` - админ панель
|
||
- `frontend` - фронтенд
|
||
- `backend` - бэкенд
|
||
|
||
**Примеры:**
|
||
|
||
```bash
|
||
# Новая функция
|
||
git commit -m "feat(storage): add custom tariff pricing with per-GB rates"
|
||
|
||
# Исправление бага
|
||
git commit -m "fix(ticket): auto-unassign operator on user close"
|
||
|
||
# Документация
|
||
git commit -m "docs: update API endpoints in README"
|
||
|
||
# Рефакторинг
|
||
git commit -m "refactor(auth): remove any types from middleware"
|
||
|
||
# Множественные изменения
|
||
git commit -m "feat(blog): add rich text editor
|
||
|
||
- Add react-quill integration
|
||
- Implement image upload
|
||
- Add comment moderation"
|
||
```
|
||
|
||
### Правила коммитов
|
||
|
||
- ✅ Subject в imperative mood ("add" не "added")
|
||
- ✅ Первая буква строчная
|
||
- ❌ Точка в конце не ставится
|
||
- ✅ Разделение логически независимых изменений
|
||
- ✅ Body коммита для пояснения "почему", если нужно
|
||
|
||
---
|
||
|
||
## 🔍 Pull Requests
|
||
|
||
### Перед созданием PR
|
||
|
||
- [ ] Код соответствует стандартам
|
||
- [ ] Нет ошибок компиляции
|
||
- [ ] Проверено локально
|
||
- [ ] Добавлена документация (если нужно)
|
||
- [ ] Обновлён CHANGELOG (если существенные изменения)
|
||
|
||
### Шаблон PR
|
||
|
||
```markdown
|
||
## 📝 Описание
|
||
|
||
Краткое описание изменений.
|
||
|
||
## 🎯 Тип изменений
|
||
|
||
- [ ] 🐛 Исправление бага
|
||
- [ ] ✨ Новая функция
|
||
- [ ] 📝 Документация
|
||
- [ ] ♻️ Рефакторинг
|
||
- [ ] ⚡️ Улучшение производительности
|
||
- [ ] ✅ Тесты
|
||
|
||
## 🔗 Связанные issues
|
||
|
||
Closes #123
|
||
|
||
## 🧪 Как тестировать
|
||
|
||
1. Шаг 1
|
||
2. Шаг 2
|
||
3. Ожидаемый результат
|
||
|
||
## 📸 Скриншоты (если применимо)
|
||
|
||
Добавьте скриншоты UI изменений.
|
||
|
||
## ✅ Checklist
|
||
|
||
- [ ] Код следует стандартам проекта
|
||
- [ ] Проведено самотестирование
|
||
- [ ] Комментарии добавлены для сложных мест
|
||
- [ ] Документация обновлена
|
||
- [ ] Нет warnings при компиляции
|
||
- [ ] Работает локально
|
||
|
||
## 📌 Дополнительные заметки
|
||
|
||
Любая дополнительная информация.
|
||
```
|
||
|
||
### Процесс ревью
|
||
|
||
1. **Автоматические проверки** - должны пройти успешно
|
||
2. **Code review** - минимум 1 аппрув от мейнтейнера
|
||
3. **Тестирование** - проверка на dev окружении
|
||
4. **Мёрдж** - после одобрения
|
||
|
||
### Работа с замечаниями
|
||
|
||
```bash
|
||
# Внесите изменения
|
||
git add .
|
||
git commit -m "fix: address review comments"
|
||
git push origin feature/your-feature
|
||
```
|
||
|
||
---
|
||
|
||
## 🏗️ Архитектурные решения
|
||
|
||
### Модульность
|
||
|
||
Каждый модуль должен быть независимым:
|
||
|
||
```
|
||
backend/src/modules/example/
|
||
├── example.controller.ts # HTTP handlers
|
||
├── example.service.ts # Business logic
|
||
├── example.routes.ts # Express routes
|
||
├── example.types.ts # TypeScript types
|
||
└── example.utils.ts # Helper functions
|
||
```
|
||
|
||
### Разделение ответственности
|
||
|
||
```typescript
|
||
// ❌ Плохо - всё в контроллере
|
||
export async function createUser(req: Request, res: Response) {
|
||
const { email, password } = req.body;
|
||
const hashedPassword = await bcrypt.hash(password, 10);
|
||
const user = await prisma.user.create({
|
||
data: { email, password: hashedPassword }
|
||
});
|
||
return res.json(user);
|
||
}
|
||
|
||
// ✅ Хорошо - разделение на слои
|
||
// controller
|
||
export async function createUser(req: Request, res: Response) {
|
||
try {
|
||
const userData = req.body;
|
||
const user = await userService.createUser(userData);
|
||
return res.json(user);
|
||
} catch (error) {
|
||
return res.status(400).json({ error: error.message });
|
||
}
|
||
}
|
||
|
||
// service
|
||
export async function createUser(data: CreateUserInput) {
|
||
const hashedPassword = await hashPassword(data.password);
|
||
return await prisma.user.create({
|
||
data: { ...data, password: hashedPassword }
|
||
});
|
||
}
|
||
```
|
||
|
||
### API дизайн
|
||
|
||
```typescript
|
||
// ✅ RESTful маршруты
|
||
GET /api/servers # Список
|
||
POST /api/servers # Создать
|
||
GET /api/servers/:id # Один
|
||
PUT /api/servers/:id # Обновить
|
||
DELETE /api/servers/:id # Удалить
|
||
|
||
# Действия над ресурсом
|
||
POST /api/servers/:id/start
|
||
POST /api/servers/:id/stop
|
||
POST /api/servers/:id/snapshot
|
||
|
||
# Вложенные ресурсы
|
||
GET /api/servers/:id/snapshots
|
||
DELETE /api/servers/:id/snapshots/:snapshotName
|
||
```
|
||
|
||
### Обработка ошибок
|
||
|
||
```typescript
|
||
// utils/errors.ts
|
||
export class AppError extends Error {
|
||
constructor(
|
||
public message: string,
|
||
public statusCode: number = 500,
|
||
public isOperational: boolean = true
|
||
) {
|
||
super(message);
|
||
}
|
||
}
|
||
|
||
export class NotFoundError extends AppError {
|
||
constructor(message: string) {
|
||
super(message, 404);
|
||
}
|
||
}
|
||
|
||
export class UnauthorizedError extends AppError {
|
||
constructor(message: string = 'Unauthorized') {
|
||
super(message, 401);
|
||
}
|
||
}
|
||
|
||
// Использование
|
||
if (!server) {
|
||
throw new NotFoundError('Server not found');
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🧪 Тестирование
|
||
|
||
### Ручное тестирование
|
||
|
||
Перед PR проверьте:
|
||
|
||
**Backend:**
|
||
```bash
|
||
# Компиляция без ошибок
|
||
npm run build
|
||
|
||
# API endpoints работают
|
||
curl -X POST http://localhost:5000/api/auth/login \
|
||
-H "Content-Type: application/json" \
|
||
-d '{"email":"test@test.com","password":"password"}'
|
||
```
|
||
|
||
**Frontend:**
|
||
```bash
|
||
# Сборка без ошибок
|
||
npm run build
|
||
|
||
# Линтинг без warnings
|
||
npm run lint
|
||
|
||
# Проверка в браузере
|
||
npm run dev
|
||
```
|
||
|
||
### Тестовые сценарии
|
||
|
||
Для новых функций опишите сценарии:
|
||
|
||
```markdown
|
||
## Тестовые сценарии
|
||
|
||
### Создание сервера
|
||
|
||
1. Войти как пользователь
|
||
2. Открыть `/dashboard/servers`
|
||
3. Нажать "Создать сервер"
|
||
4. Выбрать OS: Ubuntu 22.04
|
||
5. Выбрать тариф: Standard
|
||
6. Нажать "Создать"
|
||
7. Ожидать: сервер создаётся, статус "creating"
|
||
8. Через 2-3 минуты: статус "running"
|
||
|
||
### Ошибки
|
||
|
||
1. Без авторизации → 401 Unauthorized
|
||
2. Недостаточно средств → 400 Bad Request
|
||
3. Несуществующий OS → 404 Not Found
|
||
```
|
||
|
||
---
|
||
|
||
## 📚 Документация
|
||
|
||
### Комментарии в коде
|
||
|
||
```typescript
|
||
/**
|
||
* Создаёт новый VPS сервер в Proxmox
|
||
*
|
||
* @param userId - ID пользователя-владельца
|
||
* @param osId - ID операционной системы
|
||
* @param tariffId - ID тарифного плана
|
||
* @returns Объект созданного сервера
|
||
* @throws {Error} Если недостаточно средств или Proxmox недоступен
|
||
*/
|
||
export async function createServer(
|
||
userId: number,
|
||
osId: number,
|
||
tariffId: number
|
||
): Promise<Server> {
|
||
// Реализация
|
||
}
|
||
```
|
||
|
||
### API документация
|
||
|
||
При добавлении endpoint обновите README:
|
||
|
||
```markdown
|
||
#### Серверы (VPS)
|
||
|
||
```http
|
||
POST /api/servers/:id/snapshot
|
||
Authorization: Bearer TOKEN
|
||
Content-Type: application/json
|
||
|
||
{
|
||
"name": "backup-before-update"
|
||
}
|
||
```
|
||
|
||
**Response:**
|
||
```json
|
||
{
|
||
"snapshot": {
|
||
"name": "backup-before-update",
|
||
"createdAt": "2025-11-26T10:00:00Z"
|
||
}
|
||
}
|
||
```
|
||
```
|
||
|
||
### Изменения в БД
|
||
|
||
При добавлении миграций опишите в CHANGELOG:
|
||
|
||
```markdown
|
||
## [Unreleased]
|
||
|
||
### Added
|
||
- Поле `pricePerGb` в модель `StoragePlan` для кастомных тарифов
|
||
- Индекс на `StoragePlan.isActive` для быстрой фильтрации
|
||
|
||
### Changed
|
||
- Функция `serializePlan` теперь возвращает поля per-GB
|
||
|
||
### Migration
|
||
```bash
|
||
npx prisma migrate deploy
|
||
npx prisma generate
|
||
```
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 Приоритеты разработки
|
||
|
||
### High Priority 🔴
|
||
|
||
- Критические баги безопасности
|
||
- Потеря данных
|
||
- Падение сервиса
|
||
- Блокирующие ошибки
|
||
|
||
### Medium Priority 🟡
|
||
|
||
- Новые функции из роадмапа
|
||
- Улучшения UX
|
||
- Оптимизация производительности
|
||
- Рефакторинг сложных участков
|
||
|
||
### Low Priority 🟢
|
||
|
||
- Косметические исправления
|
||
- Документация
|
||
- Code style улучшения
|
||
- Nice-to-have функции
|
||
|
||
---
|
||
|
||
## 🔒 Безопасность
|
||
|
||
### Уязвимости
|
||
|
||
Если нашли уязвимость безопасности:
|
||
|
||
1. **НЕ создавайте публичный issue**
|
||
2. Напишите на security@ospab.host
|
||
3. Опишите подробно проблему
|
||
4. Предложите решение (если есть)
|
||
|
||
### Практики безопасности
|
||
|
||
```typescript
|
||
// ✅ Хорошо
|
||
const hashedPassword = await bcrypt.hash(password, 10);
|
||
|
||
// Проверка владельца ресурса
|
||
const server = await prisma.server.findFirst({
|
||
where: { id: serverId, userId } // Фильтр по userId
|
||
});
|
||
|
||
// Санитизация ввода
|
||
const sanitizedName = name.trim().slice(0, 100);
|
||
|
||
// ❌ Плохо
|
||
const server = await prisma.server.findUnique({
|
||
where: { id: serverId } // Любой может получить любой сервер
|
||
});
|
||
```
|
||
|
||
---
|
||
|
||
## 🌍 Интернационализация
|
||
|
||
В будущем планируется поддержка нескольких языков. При добавлении текста:
|
||
|
||
```typescript
|
||
// ✅ Хорошо - готово к i18n
|
||
const messages = {
|
||
server_created: 'Server created successfully',
|
||
server_error: 'Failed to create server',
|
||
};
|
||
|
||
// ❌ Плохо - хардкод
|
||
return res.json({ message: 'Сервер создан успешно' });
|
||
```
|
||
|
||
---
|
||
|
||
## 📞 Получение помощи
|
||
|
||
### Где задать вопрос
|
||
|
||
- **GitHub Discussions** - общие вопросы
|
||
- **GitHub Issues** - баги и фичи
|
||
- **Telegram** - [@ospab](https://t.me/ospab) - быстрая помощь
|
||
|
||
### Как задать хороший вопрос
|
||
|
||
```markdown
|
||
## Описание проблемы
|
||
|
||
Краткое описание что не работает.
|
||
|
||
## Шаги для воспроизведения
|
||
|
||
1. Шаг 1
|
||
2. Шаг 2
|
||
3. Результат
|
||
|
||
## Ожидаемое поведение
|
||
|
||
Что должно было произойти.
|
||
|
||
## Окружение
|
||
|
||
- OS: Windows 11
|
||
- Node.js: 18.17.0
|
||
- Browser: Chrome 120
|
||
- Backend: running on localhost:5000
|
||
|
||
## Логи/ошибки
|
||
|
||
```
|
||
Вставьте логи или скриншоты ошибок
|
||
```
|
||
|
||
## Что уже пробовали
|
||
|
||
- Перезапустили сервер
|
||
- Очистили npm cache
|
||
```
|
||
|
||
---
|
||
|
||
## 📅 Релизный цикл
|
||
|
||
### Версионирование
|
||
|
||
Следуем [Semantic Versioning](https://semver.org/):
|
||
|
||
- **MAJOR** (8.x.x) - несовместимые изменения API
|
||
- **MINOR** (x.1.x) - новая функциональность, обратно совместимая
|
||
- **PATCH** (x.x.1) - исправления багов
|
||
|
||
### Ветки
|
||
|
||
- `main` - стабильная production версия
|
||
- `develop` - разработка следующего релиза
|
||
- `feature/*` - новые функции
|
||
- `fix/*` - исправления
|
||
|
||
---
|
||
|
||
## ✨ Лучшие практики
|
||
|
||
### DRY (Don't Repeat Yourself)
|
||
|
||
```typescript
|
||
// ❌ Плохо
|
||
const user1 = await prisma.user.findUnique({ where: { id: 1 } });
|
||
const user2 = await prisma.user.findUnique({ where: { id: 2 } });
|
||
const user3 = await prisma.user.findUnique({ where: { id: 3 } });
|
||
|
||
// ✅ Хорошо
|
||
const userIds = [1, 2, 3];
|
||
const users = await prisma.user.findMany({
|
||
where: { id: { in: userIds } }
|
||
});
|
||
```
|
||
|
||
### Раннее возвращение
|
||
|
||
```typescript
|
||
// ✅ Хорошо
|
||
function processUser(user: User | null) {
|
||
if (!user) {
|
||
return null;
|
||
}
|
||
if (!user.isActive) {
|
||
return null;
|
||
}
|
||
// Основная логика
|
||
return user.name;
|
||
}
|
||
|
||
// ❌ Плохо
|
||
function processUser(user: User | null) {
|
||
if (user) {
|
||
if (user.isActive) {
|
||
// Основная логика
|
||
return user.name;
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
```
|
||
|
||
### Константы вместо магических чисел
|
||
|
||
```typescript
|
||
// ✅ Хорошо
|
||
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
||
const SESSION_TTL_MINUTES = 20;
|
||
const DEFAULT_PAGE_SIZE = 10;
|
||
|
||
if (fileSize > MAX_FILE_SIZE) {
|
||
throw new Error('File too large');
|
||
}
|
||
|
||
// ❌ Плохо
|
||
if (fileSize > 10485760) { // Что это за число?
|
||
throw new Error('File too large');
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🎓 Обучающие ресурсы
|
||
|
||
### TypeScript
|
||
- [Official Docs](https://www.typescriptlang.org/docs/)
|
||
- [TypeScript Deep Dive](https://basarat.gitbook.io/typescript/)
|
||
|
||
### React
|
||
- [Official Docs](https://react.dev/)
|
||
- [React TypeScript Cheatsheet](https://react-typescript-cheatsheet.netlify.app/)
|
||
|
||
### Prisma
|
||
- [Official Docs](https://www.prisma.io/docs/)
|
||
- [Prisma Best Practices](https://www.prisma.io/docs/guides/performance-and-optimization)
|
||
|
||
### Express
|
||
- [Official Docs](https://expressjs.com/)
|
||
- [Express Best Practices](https://expressjs.com/en/advanced/best-practice-performance.html)
|
||
|
||
---
|
||
|
||
## 🏆 Признание вкладчиков
|
||
|
||
Все участники будут упомянуты в:
|
||
- CHANGELOG.md
|
||
- Contributors страница на сайте
|
||
- Release notes
|
||
|
||
Спасибо за вклад в развитие Ospabhost! 🚀
|
||
|
||
---
|
||
|
||
**Последнее обновление:** 26 ноября 2025
|
||
**Версия:** 1.0.0
|