mirror of
https://github.com/serty2005/rmser.git
synced 2026-02-04 19:02:33 -06:00
2512-есть Maintenance режим, но бот всё равно отвечает фульно
This commit is contained in:
@@ -96,6 +96,8 @@ func main() {
|
|||||||
syncService := sync.NewService(rmsFactory, accountRepo, catalogRepo, recipesRepo, invoicesRepo, opsRepo, supplierRepo)
|
syncService := sync.NewService(rmsFactory, accountRepo, catalogRepo, recipesRepo, invoicesRepo, opsRepo, supplierRepo)
|
||||||
recService := recServicePkg.NewService(recRepo)
|
recService := recServicePkg.NewService(recRepo)
|
||||||
ocrService := ocrServicePkg.NewService(ocrRepo, catalogRepo, draftsRepo, accountRepo, photosRepo, pyClient, cfg.App.StoragePath)
|
ocrService := ocrServicePkg.NewService(ocrRepo, catalogRepo, draftsRepo, accountRepo, photosRepo, pyClient, cfg.App.StoragePath)
|
||||||
|
// Устанавливаем DevIDs для OCR сервиса
|
||||||
|
ocrService.SetDevIDs(cfg.App.DevIDs)
|
||||||
draftsService := draftsServicePkg.NewService(draftsRepo, ocrRepo, catalogRepo, accountRepo, supplierRepo, photosRepo, invoicesRepo, rmsFactory, billingService)
|
draftsService := draftsServicePkg.NewService(draftsRepo, ocrRepo, catalogRepo, accountRepo, supplierRepo, photosRepo, invoicesRepo, rmsFactory, billingService)
|
||||||
invoicesService := invoicesServicePkg.NewService(invoicesRepo, draftsRepo, supplierRepo, rmsFactory)
|
invoicesService := invoicesServicePkg.NewService(invoicesRepo, draftsRepo, supplierRepo, rmsFactory)
|
||||||
photosService := photosServicePkg.NewService(photosRepo, draftsRepo, accountRepo)
|
photosService := photosServicePkg.NewService(photosRepo, draftsRepo, accountRepo)
|
||||||
@@ -117,6 +119,8 @@ func main() {
|
|||||||
}
|
}
|
||||||
billingService.SetNotifier(bot)
|
billingService.SetNotifier(bot)
|
||||||
settingsHandler.SetNotifier(bot)
|
settingsHandler.SetNotifier(bot)
|
||||||
|
// Устанавливаем нотификатор для OCR сервиса
|
||||||
|
ocrService.SetNotifier(bot)
|
||||||
go bot.Start()
|
go bot.Start()
|
||||||
defer bot.Stop()
|
defer bot.Stop()
|
||||||
}
|
}
|
||||||
@@ -141,7 +145,7 @@ func main() {
|
|||||||
|
|
||||||
api := r.Group("/api")
|
api := r.Group("/api")
|
||||||
|
|
||||||
api.Use(middleware.AuthMiddleware(accountRepo, cfg.Telegram.Token))
|
api.Use(middleware.AuthMiddleware(accountRepo, cfg.Telegram.Token, cfg.App.MaintenanceMode, cfg.App.DevIDs))
|
||||||
{
|
{
|
||||||
// Drafts & Invoices
|
// Drafts & Invoices
|
||||||
api.GET("/drafts", draftsHandler.GetDrafts)
|
api.GET("/drafts", draftsHandler.GetDrafts)
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ import (
|
|||||||
"rmser/internal/infrastructure/ocr_client"
|
"rmser/internal/infrastructure/ocr_client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DevNotifier - интерфейс для уведомления разработчиков
|
||||||
|
type DevNotifier interface {
|
||||||
|
NotifyDevs(devIDs []int64, photoPath string, serverName string, serverID string)
|
||||||
|
}
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
ocrRepo ocr.Repository
|
ocrRepo ocr.Repository
|
||||||
catalogRepo catalog.Repository
|
catalogRepo catalog.Repository
|
||||||
@@ -27,6 +32,8 @@ type Service struct {
|
|||||||
photoRepo photos.Repository
|
photoRepo photos.Repository
|
||||||
pyClient *ocr_client.Client
|
pyClient *ocr_client.Client
|
||||||
storagePath string
|
storagePath string
|
||||||
|
notifier DevNotifier
|
||||||
|
devIDs []int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(
|
func NewService(
|
||||||
@@ -49,6 +56,16 @@ func NewService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetNotifier - устанавливает notifier для уведомлений разработчиков
|
||||||
|
func (s *Service) SetNotifier(n DevNotifier) {
|
||||||
|
s.notifier = n
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDevIDs - устанавливает список ID разработчиков для уведомлений
|
||||||
|
func (s *Service) SetDevIDs(ids []int64) {
|
||||||
|
s.devIDs = ids
|
||||||
|
}
|
||||||
|
|
||||||
// checkWriteAccess - вспомогательный метод проверки прав
|
// checkWriteAccess - вспомогательный метод проверки прав
|
||||||
func (s *Service) checkWriteAccess(userID, serverID uuid.UUID) error {
|
func (s *Service) checkWriteAccess(userID, serverID uuid.UUID) error {
|
||||||
role, err := s.accountRepo.GetUserRole(userID, serverID)
|
role, err := s.accountRepo.GetUserRole(userID, serverID)
|
||||||
@@ -74,15 +91,20 @@ func (s *Service) ProcessReceiptImage(ctx context.Context, userID uuid.UUID, img
|
|||||||
draftID := uuid.New()
|
draftID := uuid.New()
|
||||||
|
|
||||||
fileName := fmt.Sprintf("receipt_%s.jpg", photoID.String())
|
fileName := fmt.Sprintf("receipt_%s.jpg", photoID.String())
|
||||||
filePath := filepath.Join(s.storagePath, fileName)
|
filePath := filepath.Join(s.storagePath, serverID.String(), fileName)
|
||||||
|
|
||||||
// 2. Сохраняем файл
|
// 2. Создаем директорию если не существует
|
||||||
|
if err := os.MkdirAll(filepath.Dir(filePath), 0755); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Сохраняем файл
|
||||||
if err := os.WriteFile(filePath, imgData, 0644); err != nil {
|
if err := os.WriteFile(filePath, imgData, 0644); err != nil {
|
||||||
return nil, fmt.Errorf("failed to save image: %w", err)
|
return nil, fmt.Errorf("failed to save image: %w", err)
|
||||||
}
|
}
|
||||||
fileURL := "/uploads/" + fileName
|
fileURL := "/uploads/" + fileName
|
||||||
|
|
||||||
// 3. Создаем запись ReceiptPhoto
|
// 4. Создаем запись ReceiptPhoto
|
||||||
photo := &photos.ReceiptPhoto{
|
photo := &photos.ReceiptPhoto{
|
||||||
ID: photoID,
|
ID: photoID,
|
||||||
RMSServerID: serverID,
|
RMSServerID: serverID,
|
||||||
@@ -98,7 +120,7 @@ func (s *Service) ProcessReceiptImage(ctx context.Context, userID uuid.UUID, img
|
|||||||
return nil, fmt.Errorf("failed to create photo record: %w", err)
|
return nil, fmt.Errorf("failed to create photo record: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. Создаем черновик
|
// 5. Создаем черновик
|
||||||
draft := &drafts.DraftInvoice{
|
draft := &drafts.DraftInvoice{
|
||||||
ID: draftID,
|
ID: draftID,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
@@ -112,7 +134,12 @@ func (s *Service) ProcessReceiptImage(ctx context.Context, userID uuid.UUID, img
|
|||||||
return nil, fmt.Errorf("failed to create draft: %w", err)
|
return nil, fmt.Errorf("failed to create draft: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Отправляем в Python OCR
|
// Уведомляем разработчиков если devIDs заданы
|
||||||
|
if len(s.devIDs) > 0 && s.notifier != nil {
|
||||||
|
s.notifier.NotifyDevs(s.devIDs, filePath, server.Name, serverID.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Отправляем в Python OCR
|
||||||
rawResult, err := s.pyClient.ProcessImage(ctx, imgData, "receipt.jpg")
|
rawResult, err := s.pyClient.ProcessImage(ctx, imgData, "receipt.jpg")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
draft.Status = drafts.StatusError
|
draft.Status = drafts.StatusError
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AuthMiddleware проверяет initData от Telegram
|
// AuthMiddleware проверяет initData от Telegram
|
||||||
func AuthMiddleware(accountRepo account.Repository, botToken string) gin.HandlerFunc {
|
func AuthMiddleware(accountRepo account.Repository, botToken string, maintenanceMode bool, devIDs []int64) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
// 1. Извлекаем данные авторизации
|
// 1. Извлекаем данные авторизации
|
||||||
authHeader := c.GetHeader("Authorization")
|
authHeader := c.GetHeader("Authorization")
|
||||||
@@ -55,6 +55,21 @@ func AuthMiddleware(accountRepo account.Repository, botToken string) gin.Handler
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Проверка режима обслуживания: если включен, разрешаем доступ только разработчикам
|
||||||
|
if maintenanceMode {
|
||||||
|
isDev := false
|
||||||
|
for _, devID := range devIDs {
|
||||||
|
if tgID == devID {
|
||||||
|
isDev = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isDev {
|
||||||
|
c.AbortWithStatusJSON(503, gin.H{"error": "maintenance_mode", "message": "Сервис на обслуживании"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Ищем пользователя в БД
|
// 4. Ищем пользователя в БД
|
||||||
user, err := accountRepo.GetUserByTelegramID(tgID)
|
user, err := accountRepo.GetUserByTelegramID(tgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -928,6 +928,25 @@ func (bot *Bot) renderDeleteServerMenu(c tele.Context) error {
|
|||||||
return c.EditOrSend("⚙️ <b>Управление серверами</b>\n\nЗдесь вы можете удалить сервер или пригласить сотрудников.", menu, tele.ModeHTML)
|
return c.EditOrSend("⚙️ <b>Управление серверами</b>\n\nЗдесь вы можете удалить сервер или пригласить сотрудников.", menu, tele.ModeHTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NotifyDevs отправляет фото разработчикам для отладки
|
||||||
|
func (bot *Bot) NotifyDevs(devIDs []int64, photoPath string, serverName string, serverID string) {
|
||||||
|
// Формируем подпись для фото
|
||||||
|
caption := fmt.Sprintf("🛠 **Debug Capture**\nServer: %s (`%s`)\nFile: %s", serverName, serverID, photoPath)
|
||||||
|
|
||||||
|
// В цикле отправляем фото каждому разработчику
|
||||||
|
for _, id := range devIDs {
|
||||||
|
photo := &tele.Photo{
|
||||||
|
File: tele.FromDisk(photoPath),
|
||||||
|
Caption: caption,
|
||||||
|
}
|
||||||
|
// Отправляем фото пользователю
|
||||||
|
_, err := bot.b.Send(&tele.User{ID: id}, photo)
|
||||||
|
if err != nil {
|
||||||
|
logger.Log.Error("Failed to send debug photo", zap.Int64("userID", id), zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func parseUUID(s string) uuid.UUID {
|
func parseUUID(s string) uuid.UUID {
|
||||||
id, _ := uuid.Parse(s)
|
id, _ := uuid.Parse(s)
|
||||||
return id
|
return id
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import { InvoiceDraftPage } from "./pages/InvoiceDraftPage";
|
|||||||
import { InvoiceViewPage } from "./pages/InvoiceViewPage";
|
import { InvoiceViewPage } from "./pages/InvoiceViewPage";
|
||||||
import { DraftsList } from "./pages/DraftsList";
|
import { DraftsList } from "./pages/DraftsList";
|
||||||
import { SettingsPage } from "./pages/SettingsPage";
|
import { SettingsPage } from "./pages/SettingsPage";
|
||||||
import { UNAUTHORIZED_EVENT } from "./services/api";
|
import { UNAUTHORIZED_EVENT, MAINTENANCE_EVENT } from "./services/api";
|
||||||
|
import MaintenancePage from "./pages/MaintenancePage";
|
||||||
|
|
||||||
// Компонент-заглушка для внешних браузеров
|
// Компонент-заглушка для внешних браузеров
|
||||||
const NotInTelegramScreen = () => (
|
const NotInTelegramScreen = () => (
|
||||||
@@ -37,6 +38,7 @@ const NotInTelegramScreen = () => (
|
|||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isUnauthorized, setIsUnauthorized] = useState(false);
|
const [isUnauthorized, setIsUnauthorized] = useState(false);
|
||||||
|
const [isMaintenance, setIsMaintenance] = useState(false);
|
||||||
const tg = window.Telegram?.WebApp;
|
const tg = window.Telegram?.WebApp;
|
||||||
|
|
||||||
// Проверяем, есть ли данные от Telegram
|
// Проверяем, есть ли данные от Telegram
|
||||||
@@ -44,7 +46,9 @@ function App() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleUnauthorized = () => setIsUnauthorized(true);
|
const handleUnauthorized = () => setIsUnauthorized(true);
|
||||||
|
const handleMaintenance = () => setIsMaintenance(true);
|
||||||
window.addEventListener(UNAUTHORIZED_EVENT, handleUnauthorized);
|
window.addEventListener(UNAUTHORIZED_EVENT, handleUnauthorized);
|
||||||
|
window.addEventListener(MAINTENANCE_EVENT, handleMaintenance);
|
||||||
|
|
||||||
if (tg) {
|
if (tg) {
|
||||||
tg.expand(); // Расширяем приложение на все окно
|
tg.expand(); // Расширяем приложение на все окно
|
||||||
@@ -52,6 +56,7 @@ function App() {
|
|||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener(UNAUTHORIZED_EVENT, handleUnauthorized);
|
window.removeEventListener(UNAUTHORIZED_EVENT, handleUnauthorized);
|
||||||
|
window.removeEventListener(MAINTENANCE_EVENT, handleMaintenance);
|
||||||
};
|
};
|
||||||
}, [tg]);
|
}, [tg]);
|
||||||
|
|
||||||
@@ -80,6 +85,11 @@ function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Если бэкенд вернул 503 (режим технического обслуживания)
|
||||||
|
if (isMaintenance) {
|
||||||
|
return <MaintenancePage />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Providers>
|
<Providers>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
|||||||
28
rmser-view/src/pages/MaintenancePage.tsx
Normal file
28
rmser-view/src/pages/MaintenancePage.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { Result, Button } from "antd";
|
||||||
|
|
||||||
|
// Страница-заглушка для режима технического обслуживания
|
||||||
|
const MaintenancePage = () => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
height: "100vh",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
background: "#fff",
|
||||||
|
padding: 20,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Result
|
||||||
|
status="warning"
|
||||||
|
title="Сервис на техническом обслуживании"
|
||||||
|
subTitle="Мы скоро вернемся с новыми функциями!"
|
||||||
|
extra={
|
||||||
|
<Button type="primary" onClick={() => window.location.reload()}>
|
||||||
|
Попробовать снова
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default MaintenancePage;
|
||||||
@@ -46,6 +46,9 @@ const tg = window.Telegram?.WebApp;
|
|||||||
// Событие для глобальной обработки 401
|
// Событие для глобальной обработки 401
|
||||||
export const UNAUTHORIZED_EVENT = 'rms_unauthorized';
|
export const UNAUTHORIZED_EVENT = 'rms_unauthorized';
|
||||||
|
|
||||||
|
// Событие для режима технического обслуживания (503)
|
||||||
|
export const MAINTENANCE_EVENT = 'rms_maintenance';
|
||||||
|
|
||||||
const apiClient = axios.create({
|
const apiClient = axios.create({
|
||||||
baseURL: API_BASE_URL,
|
baseURL: API_BASE_URL,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -84,6 +87,11 @@ apiClient.interceptors.response.use(
|
|||||||
window.dispatchEvent(new Event(UNAUTHORIZED_EVENT));
|
window.dispatchEvent(new Event(UNAUTHORIZED_EVENT));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (error.response && error.response.status === 503) {
|
||||||
|
// Режим технического обслуживания
|
||||||
|
window.dispatchEvent(new Event(MAINTENANCE_EVENT));
|
||||||
|
}
|
||||||
|
|
||||||
// Если запрос был отменен нами (нет initData), не выводим стандартную ошибку API
|
// Если запрос был отменен нами (нет initData), не выводим стандартную ошибку API
|
||||||
if (error.message === 'MISSING_TELEGRAM_DATA') {
|
if (error.message === 'MISSING_TELEGRAM_DATA') {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|||||||
Reference in New Issue
Block a user