This commit is contained in:
2026-09-16 23:56:15 +07:00
parent b1ec8284b2
commit 23caa76e14
7 changed files with 264 additions and 91 deletions
+66
View File
@@ -0,0 +1,66 @@
package app
import (
"context"
"errors"
"evening_detective_server/internal/modules/file_storage"
"evening_detective_server/internal/modules/processor_jwt"
"evening_detective_server/internal/modules/roles"
"evening_detective_server/internal/services/file_service"
proto "evening_detective_server/proto"
"google.golang.org/genproto/googleapis/api/httpbody"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
func (s *server) UploadFile(ctx context.Context, req *proto.UploadFileReq) (*proto.UploadFileRsp, error) {
// Файлы (в т.ч. улики) загружают авторы сценариев в редакторе.
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
if !roles.HasRole(claims, roles.Author) {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
filename, fileType, err := s.fileService.UploadFile(ctx, req.Filename, req.Data)
if err != nil {
switch {
case errors.Is(err, file_service.ErrFileTooLarge):
return nil, status.Errorf(codes.ResourceExhausted, "%v", err)
case errors.Is(err, file_service.ErrEmptyName),
errors.Is(err, file_service.ErrEmptyData),
errors.Is(err, file_service.ErrInvalidExtension),
errors.Is(err, file_service.ErrInvalidContentType):
return nil, status.Errorf(codes.InvalidArgument, "%v", err)
default:
return nil, status.Errorf(codes.Internal, "%v", err)
}
}
return &proto.UploadFileRsp{
Filename: filename,
FileType: fileType,
}, nil
}
func (s *server) DownloadFile(ctx context.Context, req *proto.DownloadFileReq) (*httpbody.HttpBody, error) {
file, err := s.fileService.DownloadFile(ctx, req.Filename)
if err != nil {
switch {
case errors.Is(err, file_storage.ErrFileNotFound):
return nil, status.Errorf(codes.NotFound, "%v", err)
default:
return nil, status.Errorf(codes.Internal, "%v", err)
}
}
// nosniff — при публичном скачивании браузер не должен угадывать
// Content-Type по содержимому (защита от stored-XSS при расхождении).
if err := grpc.SetHeader(ctx, metadata.Pairs("X-Content-Type-Options", "nosniff")); err != nil {
return nil, status.Errorf(codes.Internal, "failed to set response header: %v", err)
}
return &httpbody.HttpBody{
Data: file.Data,
ContentType: file.Mime,
}, nil
}
+15
View File
@@ -0,0 +1,15 @@
package app
import (
"context"
proto "evening_detective_server/proto"
)
func (s *server) Ping(_ context.Context, _ *proto.PingReq) (*proto.PingRsp, error) {
return &proto.PingRsp{}, nil
}
func (s *server) Echo(_ context.Context, req *proto.EchoReq) (*proto.EchoRsp, error) {
return &proto.EchoRsp{Text: req.Text}, nil
}
+38
View File
@@ -0,0 +1,38 @@
package app
import (
"context"
"evening_detective_server/docs"
proto "evening_detective_server/proto"
)
// GetTerms возвращает текст Пользовательского соглашения (постоянный URL
// /api/terms, на который ссылается форма регистрации при акцепте).
func (s *server) GetTerms(_ context.Context, _ *proto.GetTermsReq) (*proto.GetTermsRsp, error) {
text, err := docs.FS.ReadFile("TERMS.md")
if err != nil {
return &proto.GetTermsRsp{Error: "Не удалось получить документ"}, nil
}
return &proto.GetTermsRsp{Text: string(text)}, nil
}
// GetPrivacy возвращает текст Политики конфиденциальности (постоянный URL
// /api/privacy).
func (s *server) GetPrivacy(_ context.Context, _ *proto.GetPrivacyReq) (*proto.GetPrivacyRsp, error) {
text, err := docs.FS.ReadFile("PRIVACY.md")
if err != nil {
return &proto.GetPrivacyRsp{Error: "Не удалось получить документ"}, nil
}
return &proto.GetPrivacyRsp{Text: string(text)}, nil
}
// GetConsent возвращает текст согласия на обработку персональных данных
// (постоянный URL /api/consent).
func (s *server) GetConsent(_ context.Context, _ *proto.GetConsentReq) (*proto.GetConsentRsp, error) {
text, err := docs.FS.ReadFile("CONSENT.md")
if err != nil {
return &proto.GetConsentRsp{Error: "Не удалось получить документ"}, nil
}
return &proto.GetConsentRsp{Text: string(text)}, nil
}
-88
View File
@@ -6,8 +6,6 @@ import (
"fmt"
"strings"
"evening_detective_server/docs"
"evening_detective_server/internal/modules/file_storage"
"evening_detective_server/internal/modules/processor_jwt"
"evening_detective_server/internal/modules/roles"
"evening_detective_server/internal/modules/scenario_archive"
@@ -64,92 +62,6 @@ func NewServer(
}
}
func (s *server) Ping(_ context.Context, _ *proto.PingReq) (*proto.PingRsp, error) {
return &proto.PingRsp{}, nil
}
func (s *server) Echo(_ context.Context, req *proto.EchoReq) (*proto.EchoRsp, error) {
return &proto.EchoRsp{Text: req.Text}, nil
}
// GetTerms возвращает текст Пользовательского соглашения (постоянный URL
// /api/terms, на который ссылается форма регистрации при акцепте).
func (s *server) GetTerms(_ context.Context, _ *proto.GetTermsReq) (*proto.GetTermsRsp, error) {
text, err := docs.FS.ReadFile("TERMS.md")
if err != nil {
return &proto.GetTermsRsp{Error: "Не удалось получить документ"}, nil
}
return &proto.GetTermsRsp{Text: string(text)}, nil
}
// GetPrivacy возвращает текст Политики конфиденциальности (постоянный URL
// /api/privacy).
func (s *server) GetPrivacy(_ context.Context, _ *proto.GetPrivacyReq) (*proto.GetPrivacyRsp, error) {
text, err := docs.FS.ReadFile("PRIVACY.md")
if err != nil {
return &proto.GetPrivacyRsp{Error: "Не удалось получить документ"}, nil
}
return &proto.GetPrivacyRsp{Text: string(text)}, nil
}
// GetConsent возвращает текст согласия на обработку персональных данных
// (постоянный URL /api/consent).
func (s *server) GetConsent(_ context.Context, _ *proto.GetConsentReq) (*proto.GetConsentRsp, error) {
text, err := docs.FS.ReadFile("CONSENT.md")
if err != nil {
return &proto.GetConsentRsp{Error: "Не удалось получить документ"}, nil
}
return &proto.GetConsentRsp{Text: string(text)}, nil
}
func (s *server) UploadFile(ctx context.Context, req *proto.UploadFileReq) (*proto.UploadFileRsp, error) {
// Файлы (в т.ч. улики) загружают авторы сценариев в редакторе.
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
if !roles.HasRole(claims, roles.Author) {
return nil, status.Errorf(codes.PermissionDenied, "permission denied")
}
filename, fileType, err := s.fileService.UploadFile(ctx, req.Filename, req.Data)
if err != nil {
switch {
case errors.Is(err, file_service.ErrFileTooLarge):
return nil, status.Errorf(codes.ResourceExhausted, "%v", err)
case errors.Is(err, file_service.ErrEmptyName),
errors.Is(err, file_service.ErrEmptyData),
errors.Is(err, file_service.ErrInvalidExtension),
errors.Is(err, file_service.ErrInvalidContentType):
return nil, status.Errorf(codes.InvalidArgument, "%v", err)
default:
return nil, status.Errorf(codes.Internal, "%v", err)
}
}
return &proto.UploadFileRsp{
Filename: filename,
FileType: fileType,
}, nil
}
func (s *server) DownloadFile(ctx context.Context, req *proto.DownloadFileReq) (*httpbody.HttpBody, error) {
file, err := s.fileService.DownloadFile(ctx, req.Filename)
if err != nil {
switch {
case errors.Is(err, file_storage.ErrFileNotFound):
return nil, status.Errorf(codes.NotFound, "%v", err)
default:
return nil, status.Errorf(codes.Internal, "%v", err)
}
}
// nosniff — при публичном скачивании браузер не должен угадывать
// Content-Type по содержимому (защита от stored-XSS при расхождении).
if err := grpc.SetHeader(ctx, metadata.Pairs("X-Content-Type-Options", "nosniff")); err != nil {
return nil, status.Errorf(codes.Internal, "failed to set response header: %v", err)
}
return &httpbody.HttpBody{
Data: file.Data,
ContentType: file.Mime,
}, nil
}
func (s *server) AddScenario(ctx context.Context, req *proto.AddScenarioReq) (*proto.AddScenarioRsp, error) {
claims := ctx.Value("claims").(*processor_jwt.JWTClaims)
if !roles.HasRole(claims, roles.Author) {