package usecase import ( "context" "errors" "fmt" "git.sch9.ru/new_gate/ms-tester/internal/models" "git.sch9.ru/new_gate/ms-tester/internal/tester" "git.sch9.ru/new_gate/ms-tester/pkg" "github.com/microcosm-cc/bluemonday" "strings" ) type ProblemUseCase struct { problemRepo tester.ProblemPostgresRepository pandocClient pkg.PandocClient } func NewProblemUseCase( problemRepo tester.ProblemPostgresRepository, pandocClient pkg.PandocClient, ) *ProblemUseCase { return &ProblemUseCase{ problemRepo: problemRepo, pandocClient: pandocClient, } } func (u *ProblemUseCase) CreateProblem(ctx context.Context, title string) (int32, error) { return u.problemRepo.CreateProblem(ctx, u.problemRepo.DB(), title) } func (u *ProblemUseCase) ReadProblemById(ctx context.Context, id int32) (*models.Problem, error) { return u.problemRepo.ReadProblemById(ctx, u.problemRepo.DB(), id) } func (u *ProblemUseCase) DeleteProblem(ctx context.Context, id int32) error { return u.problemRepo.DeleteProblem(ctx, u.problemRepo.DB(), id) } func (u *ProblemUseCase) ListProblems(ctx context.Context, filter models.ProblemsFilter) (*models.ProblemsList, error) { return u.problemRepo.ListProblems(ctx, u.problemRepo.DB(), filter) } func (u *ProblemUseCase) UpdateProblem(ctx context.Context, id int32, problemUpdate models.ProblemUpdate) error { if isEmpty(problemUpdate) { return pkg.Wrap(pkg.ErrBadInput, nil, "UpdateProblem", "empty problem update") } tx, err := u.problemRepo.BeginTx(ctx) if err != nil { return err } problem, err := u.problemRepo.ReadProblemById(ctx, tx, id) if err != nil { return errors.Join(err, tx.Rollback()) } statement := models.ProblemStatement{ Legend: problem.Legend, InputFormat: problem.InputFormat, OutputFormat: problem.OutputFormat, Notes: problem.Notes, Scoring: problem.Scoring, } if problemUpdate.Legend != nil { statement.Legend = *problemUpdate.Legend } if problemUpdate.InputFormat != nil { statement.InputFormat = *problemUpdate.InputFormat } if problemUpdate.OutputFormat != nil { statement.OutputFormat = *problemUpdate.OutputFormat } if problemUpdate.Notes != nil { statement.Notes = *problemUpdate.Notes } if problemUpdate.Scoring != nil { statement.Scoring = *problemUpdate.Scoring } builtStatement, err := build(ctx, u.pandocClient, trimSpaces(statement)) if err != nil { return errors.Join(err, tx.Rollback()) } if builtStatement.LegendHtml != problem.LegendHtml { problemUpdate.LegendHtml = &builtStatement.LegendHtml } if builtStatement.InputFormatHtml != problem.InputFormatHtml { problemUpdate.InputFormatHtml = &builtStatement.InputFormatHtml } if builtStatement.OutputFormatHtml != problem.OutputFormatHtml { problemUpdate.OutputFormatHtml = &builtStatement.OutputFormatHtml } if builtStatement.NotesHtml != problem.NotesHtml { problemUpdate.NotesHtml = &builtStatement.NotesHtml } if builtStatement.ScoringHtml != problem.ScoringHtml { problemUpdate.ScoringHtml = &builtStatement.ScoringHtml } err = u.problemRepo.UpdateProblem(ctx, tx, id, problemUpdate) if err != nil { return errors.Join(err, tx.Rollback()) } err = tx.Commit() if err != nil { return err } return nil } func isEmpty(p models.ProblemUpdate) bool { return p.Title == nil && p.Legend == nil && p.InputFormat == nil && p.OutputFormat == nil && p.Notes == nil && p.Scoring == nil && p.MemoryLimit == nil && p.TimeLimit == nil } func wrap(s string) string { return fmt.Sprintf("\\begin{document}\n%s\n\\end{document}\n", s) } func trimSpaces(statement models.ProblemStatement) models.ProblemStatement { return models.ProblemStatement{ Legend: strings.TrimSpace(statement.Legend), InputFormat: strings.TrimSpace(statement.InputFormat), OutputFormat: strings.TrimSpace(statement.OutputFormat), Notes: strings.TrimSpace(statement.Notes), Scoring: strings.TrimSpace(statement.Scoring), } } func sanitize(statement models.Html5ProblemStatement) models.Html5ProblemStatement { p := bluemonday.UGCPolicy() p.AllowAttrs("class").Globally() p.AllowAttrs("style").Globally() p.AllowStyles("text-align").MatchingEnum("center", "left", "right").Globally() p.AllowStyles("display").MatchingEnum("block", "inline", "inline-block").Globally() p.AllowStandardURLs() p.AllowAttrs("cite").OnElements("blockquote", "q") p.AllowAttrs("href").OnElements("a", "area") p.AllowAttrs("src").OnElements("img") if statement.LegendHtml != "" { statement.LegendHtml = p.Sanitize(statement.LegendHtml) } if statement.InputFormatHtml != "" { statement.InputFormatHtml = p.Sanitize(statement.InputFormatHtml) } if statement.OutputFormatHtml != "" { statement.OutputFormatHtml = p.Sanitize(statement.OutputFormatHtml) } if statement.NotesHtml != "" { statement.NotesHtml = p.Sanitize(statement.NotesHtml) } if statement.ScoringHtml != "" { statement.ScoringHtml = p.Sanitize(statement.ScoringHtml) } return statement } func build(ctx context.Context, pandocClient pkg.PandocClient, p models.ProblemStatement) (models.Html5ProblemStatement, error) { p = trimSpaces(p) latex := models.ProblemStatement{} if p.Legend != "" { latex.Legend = wrap(p.Legend) } if p.InputFormat != "" { latex.InputFormat = wrap(p.InputFormat) } if p.OutputFormat != "" { latex.OutputFormat = wrap(p.OutputFormat) } if p.Notes != "" { latex.Notes = wrap(p.Notes) } if p.Scoring != "" { latex.Scoring = wrap(p.Scoring) } req := []string{ latex.Legend, latex.InputFormat, latex.OutputFormat, latex.Notes, latex.Scoring, } res, err := pandocClient.BatchConvertLatexToHtml5(ctx, req) if err != nil { return models.Html5ProblemStatement{}, err } if len(res) != len(req) { return models.Html5ProblemStatement{}, fmt.Errorf("wrong number of fieilds returned: %d", len(res)) } sanitizedStatement := sanitize(models.Html5ProblemStatement{ LegendHtml: res[0], InputFormatHtml: res[1], OutputFormatHtml: res[2], NotesHtml: res[3], ScoringHtml: res[4], }) return sanitizedStatement, nil }