diff --git a/backend/global/base.go b/backend/global/base.go index df6fba8..e9b4b1b 100644 --- a/backend/global/base.go +++ b/backend/global/base.go @@ -10,13 +10,19 @@ import ( const ( // DataFolder stores all backend data in DataFolder = "./data/" - // FileFolder stores all blob files + // FileFolder stores all uploaded blob files FileFolder = DataFolder + "file/" + // ImageFolder stores images of questions + ImageFolder = DataFolder + "image/" + // PaperFolder stores all protected files + PaperFolder = DataFolder + "paper/" ) func init() { initdir(DataFolder) initdir(FileFolder) + initsecuredir(ImageFolder) + initsecuredir(PaperFolder) } func initdir(folder string) { @@ -27,6 +33,14 @@ func initdir(folder string) { } } +func initsecuredir(folder string) { + err := os.MkdirAll(folder, 0700) + if err != nil { + logrus.Errorln("[os.MkdirAll]", err) + os.Exit(line()) + } +} + func line() int { _, _, fileLine, ok := runtime.Caller(2) if ok { diff --git a/backend/global/file.go b/backend/global/file.go index 5d67a37..957f3fd 100644 --- a/backend/global/file.go +++ b/backend/global/file.go @@ -1,16 +1,133 @@ package global -import "time" +import ( + "os" + "strconv" + "time" +) + +const ( + FileTableFile = "file" + FileTableQuestion = "question" +) + +// PaperType [4 开 一页纸 闭] [4 上下] [4 中末] [4 AB] +type PaperType uint16 + +// AB default A +func (pt PaperType) AB() byte { + switch pt & 0x0f { + case 1: + return 'A' + case 2: + return 'B' + default: + return 'A' + } +} + +// MiddleFinal default 平时 +func (pt PaperType) MiddleFinal() string { + switch (pt & 0xf0) >> 4 { + case 1: + return "期中" + case 2: + return "期末" + default: + return "平时" + } +} + +// FirstSecond default is 年度 +func (pt PaperType) FirstSecond() string { + switch (pt & 0x0f00) >> 8 { + case 1: + return "第1学期" + case 2: + return "第2学期" + default: + return "年度" + } +} + +// OpenClose default 闭卷 +func (pt PaperType) OpenClose() string { + switch (pt & 0xf000) >> 12 { + case 1: + return "开卷" + case 2: + return "一页纸开卷" + case 3: + return "闭卷" + default: + return "闭卷" + } +} + +// StudyYear 学年 +type StudyYear uint16 + +// String ex. 2022-2023学年 +func (sy StudyYear) String() string { + next := sy + 1 + return strconv.Itoa(int(sy)) + "-" + strconv.Itoa(int(next)) + "学年" +} func init() { err := FileDB.db.Open(time.Hour) if err != nil { panic(err) } + err = FileDB.db.Create(FileTableFile, &File{}) + if err != nil { + panic(err) + } + err = FileDB.db.Close() + if err != nil { + panic(err) + } + err = os.Chmod(FileDB.db.DBPath, 0600) + if err != nil { + panic(err) + } + err = FileDB.db.Open(time.Hour) + if err != nil { + panic(err) + } } type File struct { - ID *int + ID uint64 // ID is the first 8 bytes of the original file's md5 + Year StudyYear + Type PaperType + Date uint32 // Date is the yyyymmdd of 考试日期 + Time time.Duration // Time is 考试时长 + Class string // Class is 考试科目 + Rate string // Rate is 成绩构成比例 + Path string // Path is like paper/Class/2023/第一学期/期末/A/xxx.docx + Questions []byte // Questions is for json struct QuestionJSON } func (f *FileDatabase) AddFile() {} + +// QuestionJSON is the struct representation of File.Questions +type QuestionJSON struct { + Name string `json:"name"` // Name is name or Question ID + Points int `json:"points"` // Points is sum of subs' points or self + Sub []QuestionJSON `json:"sub,omitempty"` +} + +type Question struct { + ID uint64 // ID is the first 8 bytes of the Plain's md5 + Plain string // Plain is the plain text of the question (like markdown format) + XML []byte // XML is the OpenXML bytes of the question + Images []byte // Images is json of the image paths in XML, ex. ['md5.jpg', 'md5.png', ...] + Dup []byte // Dup is json of Duplication struct +} + +// Duplication is the struct representation of Question.Dup +type Duplication struct { + ID string `json:"id"` // ID is hex string for json's 53 bits number + Rate float64 `json:"rate"` // Rate is the 重复率 or 总重复率 + To []Duplication `json:"to,omitempty"` +} diff --git a/backend/global/user.go b/backend/global/user.go index 5bfaaa7..3eb19a4 100644 --- a/backend/global/user.go +++ b/backend/global/user.go @@ -2,6 +2,7 @@ package global import ( "errors" + "os" "strconv" "time" @@ -114,6 +115,18 @@ func init() { }, "系统") logrus.Warn("[user] 初次启动, 创建初始账户 fumiama 密码 123456") } + err = UserDB.db.Close() + if err != nil { + panic(err) + } + err = os.Chmod(UserDB.db.DBPath, 0600) + if err != nil { + panic(err) + } + err = UserDB.db.Open(time.Hour) + if err != nil { + panic(err) + } } // User stores a user in table named UserTableUser diff --git a/backend/paper.go b/backend/paper.go new file mode 100644 index 0000000..54c17b7 --- /dev/null +++ b/backend/paper.go @@ -0,0 +1,28 @@ +package backend + +import ( + "net/http" + + "github.com/fumiama/paper-manager/backend/global" + "github.com/fumiama/paper-manager/backend/utils" + "github.com/sirupsen/logrus" +) + +// PaperHandler serves protected contents in global.FileFolder +func PaperHandler(w http.ResponseWriter, r *http.Request) { + if !utils.IsMethod("GET", w, r) { + return + } + global.UserDB.VisitAPI() + if r.URL.Path[0] != '/' { + r.URL.Path = "/" + r.URL.Path + } + fn := r.URL.Path[6:] + if fn == "" { + http.Error(w, "400 Bad Request: empty path", http.StatusBadRequest) + return + } + name := global.FileFolder + fn + logrus.Infoln("[file.FileHandler] serve", name) + http.ServeFile(w, r, name) +}