CodingSouls团队项目冲刺(6)-个人概况

团队冲刺第六天:

  题库数据库设计与交互:

  

  1 import * as TypeORM from "typeorm";
  2 import Model from "./common";
  3 
  4 declare var syzoj, ErrorMessage: any;
  5 
  6 import User from "./user";
  7 import File from "./file";
  8 import JudgeState from "./judge_state";
  9 import Contest from "./contest";
 10 import ProblemTag from "./problem_tag";
 11 import ProblemTagMap from "./problem_tag_map";
 12 import SubmissionStatistics, { StatisticsType } from "./submission_statistics";
 13 
 14 import * as fs from "fs-extra";
 15 import * as path from "path";
 16 import * as util from "util";
 17 import * as LRUCache from "lru-cache";
 18 import * as DeepCopy from "deepcopy";
 19 
 20 const problemTagCache = new LRUCache<number, number[]>({
 21   max: syzoj.config.db.cache_size
 22 });
 23 
 24 enum ProblemType {
 25   Traditional = "traditional",
 26   SubmitAnswer = "submit-answer",
 27   Interaction = "interaction"
 28 }
 29 
 30 const statisticsTypes = {
 31   fastest: ['total_time', 'ASC'],
 32   slowest: ['total_time', 'DESC'],
 33   shortest: ['code_length', 'ASC'],
 34   longest: ['code_length', 'DESC'],
 35   min: ['max_memory', 'ASC'],
 36   max: ['max_memory', 'DESC'],
 37   earliest: ['submit_time', 'ASC']
 38 };
 39 
 40 const statisticsCodeOnly = ["fastest", "slowest", "min", "max"];
 41 
 42 @TypeORM.Entity()
 43 export default class Problem extends Model {
 44   static cache = true;
 45 
 46   @TypeORM.PrimaryGeneratedColumn()
 47   id: number;
 48 
 49   @TypeORM.Column({ nullable: true, type: "varchar", length: 80 })
 50   title: string;
 51 
 52   @TypeORM.Index()
 53   @TypeORM.Column({ nullable: true, type: "integer" })
 54   user_id: number;
 55 
 56   @TypeORM.Column({ nullable: true, type: "integer" })
 57   publicizer_id: number;
 58 
 59   @TypeORM.Column({ nullable: true, type: "boolean" })
 60   is_anonymous: boolean;
 61 
 62   @TypeORM.Column({ nullable: true, type: "text" })
 63   description: string;
 64 
 65   @TypeORM.Column({ nullable: true, type: "text" })
 66   input_format: string;
 67 
 68   @TypeORM.Column({ nullable: true, type: "text" })
 69   output_format: string;
 70 
 71   @TypeORM.Column({ nullable: true, type: "text" })
 72   example: string;
 73  type: ProblemType;
 74 
 75   user?: User;
 76   publicizer?: User;
 77   additional_file?: File;
 78 
 79   async loadRelationships() {
 80     this.user = await User.findById(this.user_id);
 81     this.publicizer = await User.findById(this.publicizer_id);
 82     this.additional_file = await File.findById(this.additional_file_id);
 83   }
 84 
 85   async isAllowedEditBy(user) {
 86     if (!user) return false;
 87     if (await user.hasPrivilege('manage_problem')) return true;
 88     return this.user_id === user.id;
 89   }
 90 
 91   async isAllowedUseBy(user) {
 92     if (this.is_public) return true;
 93     if (!user) return false;
 94     if (await user.hasPrivilege('manage_problem')) return true;
 95     return this.user_id === user.id;
 96   }
 97 
 98   async isAllowedManageBy(user) {
 99     if (!user) return false;
100     if (await user.hasPrivilege('manage_problem')) return true;
101     return user.is_admin;
102   }
103 
104   getTestdataPath() {
105     return syzoj.utils.resolvePath(syzoj.config.upload_dir, 'testdata', this.id.toString());
106   }
107 
108   getTestdataArchivePath() {
109     return syzoj.utils.resolvePath(syzoj.config.upload_dir, 'testdata-archive', this.id.toString() + '.zip');
110   }
111  async deleteTestdataSingleFile(filename) {
112     await syzoj.utils.lock(['Promise::Testdata', this.id], async () => {
113       await fs.remove(path.join(this.getTestdataPath(), filename));
114       await fs.remove(this.getTestdataArchivePath());
115     });
116   }
117 
118   async makeTestdataZip() {
119     await syzoj.utils.lock(['Promise::Testdata', this.id], async () => {
120       let dir = this.getTestdataPath();
121       if (!await syzoj.utils.isDir(dir)) throw new ErrorMessage('无测试数据。');
122 
123       let p7zip = new (require('node-7z'));
124 
125       let list = await this.listTestdata(), pathlist = list.files.map(file => path.join(dir, file.filename));
126       if (!pathlist.length) throw new ErrorMessage('无测试数据。');
127       await fs.ensureDir(path.resolve(this.getTestdataArchivePath(), '..'));
128       await p7zip.add(this.getTestdataArchivePath(), pathlist);
129     });
130   }
131 
132   async hasSpecialJudge() {
133     try {
134       let dir = this.getTestdataPath();
135       let list = await fs.readdir(dir);
136       return list.includes('spj.js') || list.find(x => x.startsWith('spj_')) !== undefined;
137     } catch (e) {
138       return false;
139     }
140   }
141 
142   async listTestdata() {
143     try {
144       let dir = this.getTestdataPath();
145       let filenameList = await fs.readdir(dir);
146       let list = await Promise.all(filenameList.map(async x => {
147         let stat = await fs.stat(path.join(dir, x));
148         if (!stat.isFile()) return undefined;
149         return {
150           filename: x,
151           size: stat.size
152         };
153       }));
154 
155       list = list.filter(x => x);
156 
157       let res = {
158         files: list,
159         zip: null
160       };
161 
162       try {
163         let stat = await fs.stat(this.getTestdataArchivePath());
164         if (stat.isFile()) {
165           res.zip = {
166             size: stat.size
167           };
168         }
169       } catch (e) {
170         if (list) {
171           res.zip = {
172             size: null
173           };
174         }
175       }
176 
177       return res;
178     } catch (e) {
179       return null;
180     }
181   }
182 
183   async updateFile(path, type, noLimit) {
184     let file = await File.upload(path, type, noLimit);
185 
186     if (type === 'additional_file') {
187       this.additional_file_id = file.id;
188     }
189 
190     await this.save();
191   }
192 
193   async validate() {
194     if (this.time_limit <= 0) return 'Invalid time limit';
195     if (this.time_limit > syzoj.config.limit.time_limit) return 'Time limit too large';
196     if (this.memory_limit <= 0) return 'Invalid memory limit';
197     if (this.memory_limit > syzoj.config.limit.memory_limit) return 'Memory limit too large';
198     if (!['traditional', 'submit-answer', 'interaction'].includes(this.type)) return 'Invalid problem type';
199 
200     if (this.type === 'traditional') {
201       let filenameRE = /^[\w \-\+\.]*$/;
202       if (this.file_io_input_name && !filenameRE.test(this.file_io_input_name)) return 'Invalid input file name';
203       if (this.file_io_output_name && !filenameRE.test(this.file_io_output_name)) return 'Invalid output file name';
204 
205       if (this.file_io) {
206         if (!this.file_io_input_name) return 'No input file name';
207         if (!this.file_io_output_name) return 'No output file name';
208       }
209     }
210 
211     return null;
212   }
213   async setTags(newTagIDs) {
214     let oldTagIDs = (await this.getTags()).map(x => x.id);
215 
216     let delTagIDs = oldTagIDs.filter(x => !newTagIDs.includes(x));
217     let addTagIDs = newTagIDs.filter(x => !oldTagIDs.includes(x));
218 
219     for (let tagID of delTagIDs) {
220       let map = await ProblemTagMap.findOne({
221         where: {
222           problem_id: this.id,
223           tag_id: tagID
224         }
225       });
226 
227       await map.destroy();
228     }
229 
230     for (let tagID of addTagIDs) {
231       let map = await ProblemTagMap.create({
232         problem_id: this.id,
233         tag_id: tagID
234       });
235 
236       await map.save();
237     }
238 
239     problemTagCache.set(this.id, newTagIDs);
240   }
241 
242   async changeID(id) {
243     const entityManager = TypeORM.getManager();
244 
245     id = parseInt(id);
246     await entityManager.query('UPDATE `problem`               SET `id`         = ' + id + ' WHERE `id`         = ' + this.id);
247     await entityManager.query('UPDATE `judge_state`           SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
248     await entityManager.query('UPDATE `problem_tag_map`       SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
249     await entityManager.query('UPDATE `article`               SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
250     await entityManager.query('UPDATE `submission_statistics` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
251 
252     let contests = await Contest.find();
253     for (let contest of contests) {
254       let problemIDs = await contest.getProblems();
255 
256       let flag = false;
257       for (let i in problemIDs) {
258         if (problemIDs[i] === this.id) {
259           problemIDs[i] = id;
260           flag = true;
261         }
262       }
263 
264       if (flag) {
265         await contest.setProblemsNoCheck(problemIDs);
266         await contest.save();
267       }
268     }
269 
270     let oldTestdataDir = this.getTestdataPath(), oldTestdataZip = this.getTestdataArchivePath();
271 
272     const oldID = this.id;
273     this.id = id;
274 
275     // Move testdata
276     let newTestdataDir = this.getTestdataPath(), newTestdataZip = this.getTestdataArchivePath();
277     if (await syzoj.utils.isDir(oldTestdataDir)) {
278       await fs.move(oldTestdataDir, newTestdataDir);
279     }
280 
281     if (await syzoj.utils.isFile(oldTestdataZip)) {
282       await fs.move(oldTestdataZip, newTestdataZip);
283     }
284 
285     await this.save();
286 
287     await Problem.deleteFromCache(oldID);
288     await problemTagCache.del(oldID);
289   }
290 
291   async delete() {
292     const entityManager = TypeORM.getManager();
293 
294     let oldTestdataDir = this.getTestdataPath(), oldTestdataZip = this.getTestdataPath();
295     await fs.remove(oldTestdataDir);
296     await fs.remove(oldTestdataZip);
297 
298     let submissions = await JudgeState.find({
299       where: {
300         problem_id: this.id
301       }
302     }), submitCnt = {}, acUsers = new Set();
303     for (let sm of submissions) {
304       if (sm.status === 'Accepted') acUsers.add(sm.user_id);
305       if (!submitCnt[sm.user_id]) {
306         submitCnt[sm.user_id] = 1;
307       } else {
308         submitCnt[sm.user_id]++;
309       }
310     }
311 
312     for (let u in submitCnt) {
313       let user = await User.findById(parseInt(u));
314       user.submit_num -= submitCnt[u];
315       if (acUsers.has(parseInt(u))) user.ac_num--;
316       await user.save();
317     }
318 
319     problemTagCache.del(this.id);
320 
321     await entityManager.query('DELETE FROM `judge_state`           WHERE `problem_id` = ' + this.id);
322     await entityManager.query('DELETE FROM `problem_tag_map`       WHERE `problem_id` = ' + this.id);
323     await entityManager.query('DELETE FROM `article`               WHERE `problem_id` = ' + this.id);
324     await entityManager.query('DELETE FROM `submission_statistics` WHERE `problem_id` = ' + this.id);
325 
326     await this.destroy();
327   }
328 }
题库后端

  没写完,太难了吧。

posted @ 2020-04-20 18:24  DemonSlayer  阅读(128)  评论(0编辑  收藏  举报