Skip to content

Commit 24d0921

Browse files
committed
feat: support pdf assignment detail
1 parent e4a1c10 commit 24d0921

5 files changed

Lines changed: 102 additions & 22 deletions

File tree

domain/assignment.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ type CreateAssignment struct {
6060
Level AssignmentLevel
6161
PublishDate time.Time
6262
DueDate *time.Time
63-
DetailFile io.Reader
63+
DetailFile *File
6464
TestcaseFiles []TestcaseFile
6565
}
6666

@@ -72,7 +72,7 @@ type UpdateAssignment struct {
7272
Level *AssignmentLevel
7373
PublishDate *time.Time
7474
DueDate *time.Time
75-
DetailFile io.Reader
75+
DetailFile *File
7676
TestcaseFiles *[]TestcaseFile
7777
}
7878

domain/domain.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package domain
22

33
import (
4+
"io"
5+
46
"github.com/codern-org/codern/platform"
57
)
68

@@ -34,3 +36,8 @@ type Usecase struct {
3436
type Publisher struct {
3537
Grading GradingPublisher
3638
}
39+
40+
type File struct {
41+
Reader io.Reader
42+
MimeType string
43+
}

internal/validator/payload.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@ package validator
33
import (
44
"errors"
55
"fmt"
6+
"io"
67
"mime/multipart"
8+
"net/http"
79
"reflect"
10+
"strings"
811

912
"github.com/codern-org/codern/domain"
1013
errs "github.com/codern-org/codern/domain/error"
@@ -123,3 +126,35 @@ func fileParser(payload interface{}, ctx *fiber.Ctx) error {
123126

124127
return nil
125128
}
129+
130+
func GetMimeType(seeker io.ReadSeeker) (string, error) {
131+
if seeker == nil {
132+
return "", nil
133+
}
134+
135+
// https://golang.org/src/net/http/sniff.go?s=646:688#L11
136+
buf := make([]byte, 512)
137+
138+
_, err := seeker.Seek(0, io.SeekStart)
139+
if err != nil {
140+
return "", err
141+
}
142+
143+
bytesRead, err := seeker.Read(buf)
144+
if err != nil && err != io.EOF {
145+
return "", err
146+
}
147+
148+
_, err = seeker.Seek(0, 0)
149+
if err != nil {
150+
return "", err
151+
}
152+
153+
// Slice buf to remove fill-up zero values which cause a wrong content type detection
154+
mimeType := http.DetectContentType(buf[:bytesRead])
155+
156+
mimeType, _, _ = strings.Cut(mimeType, ";")
157+
mimeType = strings.TrimSpace(mimeType)
158+
159+
return mimeType, nil
160+
}

platform/server/controller/assignment.go

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/codern-org/codern/domain"
77
errs "github.com/codern-org/codern/domain/error"
8+
"github.com/codern-org/codern/internal/validator"
89
"github.com/codern-org/codern/platform/server/middleware"
910
"github.com/codern-org/codern/platform/server/payload"
1011
"github.com/codern-org/codern/platform/server/response"
@@ -39,18 +40,29 @@ func (c *AssignmentController) Create(ctx *fiber.Ctx) error {
3940
user := middleware.GetUserFromCtx(ctx)
4041
testcaseFiles := domain.CreateTestcaseFiles(pl.TestcaseInputFiles, pl.TestcaseOutputFiles)
4142

43+
fileMimeType, err := validator.GetMimeType(pl.DetailFile)
44+
if err != nil {
45+
return err
46+
}
47+
if fileMimeType != "text/plain" && fileMimeType != "application/pdf" {
48+
return errs.New(errs.ErrBodyParser, "unsupported file type")
49+
}
50+
4251
if err := c.assignmentUsecase.Create(
4352
user.Id,
4453
pl.WorkspaceId,
4554
&domain.CreateAssignment{
46-
Name: pl.Name,
47-
Description: pl.Description,
48-
MemoryLimit: pl.MemoryLimit,
49-
TimeLimit: pl.TimeLimit,
50-
Level: pl.Level,
51-
PublishDate: pl.PublishDate,
52-
DueDate: pl.DueDate,
53-
DetailFile: pl.DetailFile,
55+
Name: pl.Name,
56+
Description: pl.Description,
57+
MemoryLimit: pl.MemoryLimit,
58+
TimeLimit: pl.TimeLimit,
59+
Level: pl.Level,
60+
PublishDate: pl.PublishDate,
61+
DueDate: pl.DueDate,
62+
DetailFile: &domain.File{
63+
Reader: pl.DetailFile,
64+
MimeType: fileMimeType,
65+
},
5466
TestcaseFiles: testcaseFiles,
5567
},
5668
); err != nil {
@@ -202,18 +214,29 @@ func (c *AssignmentController) Update(ctx *fiber.Ctx) error {
202214
user := middleware.GetUserFromCtx(ctx)
203215
testcaseFiles := domain.CreateTestcaseFiles(pl.TestcaseInputFiles, pl.TestcaseOutputFiles)
204216

217+
fileMimeType, err := validator.GetMimeType(pl.DetailFile)
218+
if err != nil {
219+
return err
220+
}
221+
if fileMimeType != "text/plain" && fileMimeType != "application/pdf" {
222+
return errs.New(errs.ErrBodyParser, "unsupported file type")
223+
}
224+
205225
if err := c.assignmentUsecase.Update(
206226
user.Id,
207227
pl.AssignmentId,
208228
&domain.UpdateAssignment{
209-
Name: pl.Name,
210-
Description: pl.Description,
211-
MemoryLimit: pl.MemoryLimit,
212-
TimeLimit: pl.TimeLimit,
213-
Level: pl.Level,
214-
PublishDate: pl.PublishDate,
215-
DueDate: pl.DueDate,
216-
DetailFile: pl.DetailFile,
229+
Name: pl.Name,
230+
Description: pl.Description,
231+
MemoryLimit: pl.MemoryLimit,
232+
TimeLimit: pl.TimeLimit,
233+
Level: pl.Level,
234+
PublishDate: pl.PublishDate,
235+
DueDate: pl.DueDate,
236+
DetailFile: &domain.File{
237+
Reader: pl.DetailFile,
238+
MimeType: fileMimeType,
239+
},
217240
TestcaseFiles: &testcaseFiles,
218241
},
219242
); err != nil {

usecase/assignment.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"io"
66
"math"
7+
"strings"
78
"time"
89

910
"github.com/codern-org/codern/domain"
@@ -46,10 +47,15 @@ func (u *assignmentUsecase) Create(
4647
return errs.New(errs.ErrWorkspaceNoPerm, "permission denied")
4748
}
4849

50+
fileExt := "md"
51+
if ca.DetailFile.MimeType == "application/pdf" {
52+
fileExt = "pdf"
53+
}
54+
4955
id := generator.GetId()
5056
filePath := fmt.Sprintf(
51-
"/workspaces/%d/assignments/%d/detail/problem.md",
52-
workspaceId, id,
57+
"/workspaces/%d/assignments/%d/detail/problem.%s",
58+
workspaceId, id, fileExt,
5359
)
5460

5561
assignment := &domain.Assignment{
@@ -70,7 +76,7 @@ func (u *assignmentUsecase) Create(
7076
}
7177

7278
// TODO: retry strategy, error
73-
if err := u.seaweedfs.Upload(ca.DetailFile, 0, filePath); err != nil {
79+
if err := u.seaweedfs.Upload(ca.DetailFile.Reader, 0, filePath); err != nil {
7480
return errs.New(errs.ErrFileSystem, "cannot upload file", err)
7581
}
7682

@@ -123,12 +129,21 @@ func (u *assignmentUsecase) Update(
123129

124130
assignment.DueDate = ua.DueDate
125131

132+
fileExt := "md"
133+
if ua.DetailFile.MimeType == "application/pdf" {
134+
fileExt = "pdf"
135+
}
136+
fileNameTokens := strings.Split(assignment.DetailUrl, "/")
137+
fileName := fileNameTokens[len(fileNameTokens)-1]
138+
fileName, _, _ = strings.Cut(fileName, ".")
139+
assignment.DetailUrl = strings.Join(fileNameTokens[:len(fileNameTokens)-1], "/") + fmt.Sprintf("/%s.%s", fileName, fileExt)
140+
126141
if err := u.assignmentRepository.Update(assignment); err != nil {
127142
return errs.New(errs.ErrUpdateAssignment, "cannot update assignment id %d", assignmentId, err)
128143
}
129144

130145
// TODO: retry strategy, error
131-
if err := u.seaweedfs.Upload(ua.DetailFile, 0, assignment.DetailUrl); err != nil {
146+
if err := u.seaweedfs.Upload(ua.DetailFile.Reader, 0, assignment.DetailUrl); err != nil {
132147
return errs.New(errs.ErrFileSystem, "cannot upload detail file while updating assignment id %d", assignmentId, err)
133148
}
134149

0 commit comments

Comments
 (0)