adamc@1022
|
1 signature INPUT = sig
|
adamc@1023
|
2 con paper :: {Type}
|
adamc@1022
|
3 constraint [Id, Document] ~ paper
|
adamc@1022
|
4
|
adamc@1022
|
5 type userId
|
adamc@1022
|
6 val userId_inj : sql_injectable_prim userId
|
adamc@1022
|
7 table user : {Id : userId, Nam : string, Password : string, Chair : bool, OnPc : bool}
|
adamc@1022
|
8 PRIMARY KEY Id,
|
adamc@1022
|
9 CONSTRAINT Nam UNIQUE Nam
|
adamc@1022
|
10
|
adamc@1022
|
11 type paperId
|
adamc@1022
|
12 val paperId_inj : sql_injectable_prim paperId
|
adamc@1023
|
13 val paperId_show : show paperId
|
adamc@1023
|
14 val paperId_read : read paperId
|
adamc@1023
|
15 table paper : ([Id = paperId, Document = blob] ++ paper)
|
adamc@1022
|
16 PRIMARY KEY Id
|
adamc@1022
|
17
|
adamc@1022
|
18 val checkLogin : transaction (option {Id : userId, Nam : string, Chair : bool, OnPc : bool})
|
adamc@1022
|
19 val getLogin : transaction {Id : userId, Nam : string, Chair : bool, OnPc : bool}
|
adamc@1023
|
20 val getPcLogin : transaction {Id : userId, Nam : string, Chair : bool}
|
adamc@1023
|
21 val summarizePaper : ctx ::: {Unit} -> [[Body] ~ ctx] => $paper -> xml ([Body] ++ ctx) [] []
|
adamc@1022
|
22 end
|
adamc@1022
|
23
|
adamc@1022
|
24 signature OUTPUT = sig
|
adamc@1023
|
25 con paper :: {Type}
|
adamc@1022
|
26 type userId
|
adamc@1022
|
27 type paperId
|
adamc@1022
|
28
|
adamc@1022
|
29 val linksForPc : xbody
|
adamc@1022
|
30
|
adamc@1022
|
31 con yourPaperTables :: {{Type}}
|
adamc@1022
|
32 constraint [Paper] ~ yourPaperTables
|
adamc@1022
|
33 val joinYourPaper : tabs ::: {{Type}} -> paper ::: {Type}
|
adamc@1022
|
34 -> [[Paper] ~ tabs] => [[Paper] ~ yourPaperTables] => [tabs ~ yourPaperTables] => [[Id] ~ paper] =>
|
adamc@1022
|
35 sql_from_items ([Paper = [Id = paperId] ++ paper] ++ tabs)
|
adamc@1022
|
36 -> sql_from_items (yourPaperTables ++ [Paper = [Id = paperId] ++ paper] ++ tabs)
|
adamc@1022
|
37 end
|
adamc@1022
|
38
|
adamc@1004
|
39 open Meta
|
adamc@1001
|
40
|
adamc@1001
|
41 functor Make(M : sig
|
adamc@1003
|
42 con paper :: {(Type * Type)}
|
adamc@1010
|
43 constraint [Id, Document, Authors] ~ paper
|
adamc@1003
|
44 val paper : $(map meta paper)
|
adamc@1007
|
45 val paperFolder : folder paper
|
adamc@1003
|
46
|
adamc@1001
|
47 con review :: {(Type * Type)}
|
adamc@1003
|
48 constraint [Paper, User] ~ review
|
adamc@1003
|
49 val review : $(map meta review)
|
adamc@1011
|
50 val reviewFolder : folder review
|
adamc@1006
|
51
|
adamc@1006
|
52 val submissionDeadline : time
|
adamc@1023
|
53 val summarizePaper : ctx ::: {Unit} -> [[Body] ~ ctx] => $(map fst paper) -> xml ([Body] ++ ctx) [] []
|
adamc@1022
|
54
|
adamc@1023
|
55 functor Make (M : INPUT where con paper = map fst paper)
|
adamc@1023
|
56 : OUTPUT where con paper = map fst paper
|
adamc@1023
|
57 where con userId = M.userId
|
adamc@1022
|
58 where con paperId = M.paperId
|
adamc@1001
|
59 end) = struct
|
adamc@1001
|
60
|
adamc@1003
|
61 table user : {Id : int, Nam : string, Password : string, Chair : bool, OnPc : bool}
|
adamc@1003
|
62 PRIMARY KEY Id,
|
adamc@1003
|
63 CONSTRAINT Nam UNIQUE Nam
|
adamc@1003
|
64 sequence userId
|
adamc@1003
|
65
|
adamc@1008
|
66 con paper = [Id = int, Document = blob] ++ map fst M.paper
|
adamc@1003
|
67 table paper : paper
|
adamc@1003
|
68 PRIMARY KEY Id
|
adamc@1003
|
69 sequence paperId
|
adamc@1003
|
70
|
adamc@1010
|
71 table authorship : {Paper : int, User : int}
|
adamc@1010
|
72 PRIMARY KEY (Paper, User),
|
adamc@1011
|
73 CONSTRAINT Paper FOREIGN KEY Paper REFERENCES paper(Id) ON DELETE CASCADE,
|
adamc@1010
|
74 CONSTRAINT User FOREIGN KEY User REFERENCES user(Id)
|
adamc@1010
|
75
|
adamc@1003
|
76 con review = [Paper = int, User = int] ++ map fst M.review
|
adamc@1003
|
77 table review : review
|
adamc@1003
|
78 PRIMARY KEY (Paper, User),
|
adamc@1003
|
79 CONSTRAINT Paper FOREIGN KEY Paper REFERENCES paper(Id),
|
adamc@1003
|
80 CONSTRAINT User FOREIGN KEY User REFERENCES user(Id)
|
adamc@1003
|
81 sequence reviewId
|
adamc@1003
|
82
|
adamc@1003
|
83 cookie login : {Id : int, Password : string}
|
adamc@1003
|
84
|
adamc@1004
|
85 val checkLogin =
|
adamc@1003
|
86 r <- getCookie login;
|
adamc@1003
|
87 case r of
|
adamc@1003
|
88 None => return None
|
adamc@1003
|
89 | Some r =>
|
adamc@1003
|
90 oneOrNoRows1 (SELECT user.Id, user.Nam, user.Chair, user.OnPc
|
adamc@1003
|
91 FROM user
|
adamc@1003
|
92 WHERE user.Id = {[r.Id]}
|
adamc@1003
|
93 AND user.Password = {[r.Password]})
|
adamc@1003
|
94
|
adamc@1010
|
95 val getLogin =
|
adamc@1010
|
96 ro <- checkLogin;
|
adamc@1010
|
97 case ro of
|
adamc@1010
|
98 None => error <xml>You must be logged in to do that.</xml>
|
adamc@1010
|
99 | Some r => return r
|
adamc@1010
|
100
|
adamc@1023
|
101 val getPcLogin =
|
adamc@1023
|
102 r <- getLogin;
|
adamc@1023
|
103 if r.OnPc then
|
adamc@1023
|
104 return (r -- #OnPc)
|
adamc@1023
|
105 else
|
adamc@1023
|
106 error <xml>You are not on the PC.</xml>
|
adamc@1023
|
107
|
adamc@1022
|
108 structure O = M.Make(struct
|
adamc@1022
|
109 val user = user
|
adamc@1022
|
110 val paper = paper
|
adamc@1022
|
111 val checkLogin = checkLogin
|
adamc@1022
|
112 val getLogin = getLogin
|
adamc@1023
|
113 val getPcLogin = getPcLogin
|
adamc@1023
|
114 val summarizePaper = @@M.summarizePaper
|
adamc@1022
|
115 end)
|
adamc@1022
|
116
|
adamc@1022
|
117 val checkOnPc =
|
adamc@1022
|
118 r <- getLogin;
|
adamc@1022
|
119 if r.OnPc then
|
adamc@1022
|
120 return ()
|
adamc@1022
|
121 else
|
adamc@1022
|
122 error <xml>You aren't authorized to do that.</xml>
|
adamc@1022
|
123
|
adamc@1009
|
124 fun checkPaper id =
|
adamc@1010
|
125 r <- getLogin;
|
adamc@1010
|
126 if r.OnPc then
|
adamc@1009
|
127 return ()
|
adamc@1009
|
128 else
|
adamc@1010
|
129 error <xml>You aren't authorized to see that paper.</xml>
|
adamc@1009
|
130
|
adamc@1004
|
131 structure Users = BulkEdit.Make(struct
|
adamc@1004
|
132 con keyName = #Id
|
adamc@1004
|
133 val visible = {Nam = string "Name",
|
adamc@1004
|
134 Chair = bool "Chair?",
|
adamc@1004
|
135 OnPc = bool "On PC?"}
|
adamc@1004
|
136
|
adamc@1004
|
137 val title = "Users"
|
adamc@1004
|
138 val isAllowed =
|
adamc@1004
|
139 me <- checkLogin;
|
adamc@1004
|
140 return (Option.isSome me)
|
adamc@1004
|
141
|
adamc@1004
|
142 val t = user
|
adamc@1004
|
143 end)
|
adamc@1004
|
144
|
adamc@1003
|
145 fun doRegister r =
|
adamc@1003
|
146 n <- oneRowE1 (SELECT COUNT( * ) AS N
|
adamc@1003
|
147 FROM user
|
adamc@1003
|
148 WHERE user.Nam = {[r.Nam]});
|
adamc@1003
|
149 if n > 0 then
|
adamc@1003
|
150 register (Some "Sorry; that username is taken.")
|
adamc@1003
|
151 else
|
adamc@1003
|
152 id <- nextval userId;
|
adamc@1003
|
153 dml (INSERT INTO user (Id, Nam, Password, Chair, OnPc)
|
adamc@1003
|
154 VALUES ({[id]}, {[r.Nam]}, {[r.Password]}, FALSE, FALSE));
|
adamc@1003
|
155 setCookie login {Id = id, Password = r.Password};
|
adamc@1003
|
156 main ()
|
adamc@1003
|
157
|
adamc@1003
|
158 and register msg = return <xml><body>
|
adamc@1003
|
159 <h1>Registering a New Account</h1>
|
adamc@1003
|
160
|
adamc@1003
|
161 {case msg of
|
adamc@1003
|
162 None => <xml/>
|
adamc@1003
|
163 | Some msg => <xml><div>{[msg]}</div></xml>}
|
adamc@1003
|
164
|
adamc@1003
|
165 <form><table>
|
adamc@1003
|
166 <tr> <th>Username:</th> <td><textbox{#Nam}/></td> </tr>
|
adamc@1003
|
167 <tr> <th>Password:</th> <td><password{#Password}/></td> </tr>
|
adamc@1003
|
168 <tr> <th><submit action={doRegister}/></th> </tr>
|
adamc@1003
|
169 </table></form>
|
adamc@1003
|
170 </body></xml>
|
adamc@1003
|
171
|
adamc@1006
|
172 and signin r =
|
adamc@1006
|
173 ro <- oneOrNoRowsE1 (SELECT user.Id AS N
|
adamc@1006
|
174 FROM user
|
adamc@1006
|
175 WHERE user.Nam = {[r.Nam]}
|
adamc@1006
|
176 AND user.Password = {[r.Password]});
|
adamc@1006
|
177 (case ro of
|
adamc@1006
|
178 None => return ()
|
adamc@1006
|
179 | Some id => setCookie login {Id = id, Password = r.Password});
|
adamc@1006
|
180 m <- main' ();
|
adamc@1006
|
181 return <xml><body>
|
adamc@1006
|
182 {case ro of
|
adamc@1006
|
183 None => <xml><div>Invalid username or password.</div></xml>
|
adamc@1006
|
184 | _ => <xml/>}
|
adamc@1006
|
185
|
adamc@1006
|
186 {m}
|
adamc@1006
|
187 </body></xml>
|
adamc@1006
|
188
|
adamc@1006
|
189 and main' () =
|
adamc@1004
|
190 me <- checkLogin;
|
adamc@1006
|
191 now <- now;
|
adamc@1006
|
192 return <xml><ul>
|
adamc@1003
|
193 {case me of
|
adamc@1006
|
194 None => <xml>
|
adamc@1006
|
195 <li><a link={register None}>Register for access</a></li>
|
adamc@1006
|
196 <li><b>Log in:</b> <form><table>
|
adamc@1006
|
197 <tr> <th>Username:</th> <td><textbox{#Nam}/></td> </tr>
|
adamc@1006
|
198 <tr> <th>Password:</th> <td><password{#Password}/></td> </tr>
|
adamc@1006
|
199 <tr> <th><submit value="Log in" action={signin}/></th> </tr>
|
adamc@1006
|
200 </table></form></li>
|
adamc@1006
|
201 </xml>
|
adamc@1004
|
202 | Some me => <xml>
|
adamc@1004
|
203 <div>Welcome, {[me.Nam]}!</div>
|
adamc@1004
|
204
|
adamc@1004
|
205 {if me.Chair then
|
adamc@1004
|
206 <xml><li><a link={Users.main ()}>Manage users</a></li></xml>
|
adamc@1004
|
207 else
|
adamc@1004
|
208 <xml/>}
|
adamc@1006
|
209
|
adamc@1009
|
210 {if me.OnPc then
|
adamc@1022
|
211 <xml>
|
adamc@1022
|
212 <li><a link={all ()}>All papers</a></li>
|
adamc@1023
|
213 <li><a link={your ()}>Your papers</a></li>
|
adamc@1022
|
214 {O.linksForPc}
|
adamc@1022
|
215 </xml>
|
adamc@1009
|
216 else
|
adamc@1009
|
217 <xml/>}
|
adamc@1009
|
218
|
adamc@1006
|
219 {if now < M.submissionDeadline then
|
adamc@1007
|
220 <xml><li><a link={submit ()}>Submit</a></li></xml>
|
adamc@1006
|
221 else
|
adamc@1006
|
222 <xml/>}
|
adamc@1004
|
223 </xml>}
|
adamc@1006
|
224 </ul></xml>
|
adamc@1006
|
225
|
adamc@1006
|
226 and main () =
|
adamc@1006
|
227 m <- main' ();
|
adamc@1006
|
228 return <xml><body>{m}</body></xml>
|
adamc@1001
|
229
|
adamc@1008
|
230 and submit () =
|
adamc@1008
|
231 let
|
adamc@1009
|
232 fun doSubmit r =
|
adamc@1010
|
233 me <- getLogin;
|
adamc@1010
|
234 coauthors <- List.mapM (fn name => oneOrNoRowsE1 (SELECT user.Id AS N
|
adamc@1010
|
235 FROM user
|
adamc@1010
|
236 WHERE user.Nam = {[name.Nam]})) r.Authors;
|
adamc@1010
|
237 if List.exists Option.isNone coauthors then
|
adamc@1010
|
238 error <xml>At least one of those coauthor usernames isn't registered.</xml>
|
adamc@1010
|
239 else
|
adamc@1010
|
240 id <- nextval paperId;
|
adamc@1010
|
241 dml (insert paper ({Id = sql_inject id, Document = sql_inject (fileData r.Document)}
|
adamc@1010
|
242 ++ ensql M.paper (r -- #Authors -- #Document) M.paperFolder));
|
adamc@1010
|
243 List.app (fn uid =>
|
adamc@1010
|
244 case uid of
|
adamc@1010
|
245 None => error <xml>Impossible empty uid!</xml>
|
adamc@1010
|
246 | Some uid => dml (INSERT INTO authorship (Paper, User)
|
adamc@1010
|
247 VALUES ({[id]}, {[uid]})))
|
adamc@1010
|
248 (Some me.Id :: coauthors);
|
adamc@1010
|
249 return <xml><body>
|
adamc@1010
|
250 Thanks for submitting!
|
adamc@1010
|
251 </body></xml>
|
adamc@1008
|
252 in
|
adamc@1010
|
253 me <- getLogin;
|
adamc@1015
|
254 numAuthors <- Dnat.zero;
|
adamc@1010
|
255
|
adamc@1008
|
256 return <xml><body>
|
adamc@1008
|
257 <h1>Submit a Paper</h1>
|
adamc@1008
|
258
|
adamc@1008
|
259 <form>
|
adamc@1010
|
260 <b>Author:</b> {[me.Nam]}<br/>
|
adamc@1010
|
261 <subforms{#Authors}>
|
adamc@1015
|
262 {Dnat.render <xml><entry><b>Author:</b> <textbox{#Nam}/><br/></entry></xml> numAuthors}
|
adamc@1010
|
263 </subforms>
|
adamc@1015
|
264 <button value="Add author" onclick={Dnat.inc numAuthors}/><br/>
|
adamc@1015
|
265 <button value="Remove author" onclick={Dnat.dec numAuthors}/><br/>
|
adamc@1010
|
266 <br/>
|
adamc@1010
|
267
|
adamc@1010
|
268 {useMore (allWidgets M.paper M.paperFolder)}
|
adamc@1008
|
269 <b>Paper:</b> <upload{#Document}/><br/>
|
adamc@1008
|
270 <submit value="Submit" action={doSubmit}/>
|
adamc@1008
|
271 </form>
|
adamc@1008
|
272 </body></xml>
|
adamc@1008
|
273 end
|
adamc@1007
|
274
|
adamc@1022
|
275 and listPapers [tabs] [[Paper] ~ tabs] (q : sql_query ([Paper = [Id = int] ++ map fst M.paper] ++ tabs) []) =
|
adamc@1022
|
276 checkOnPc;
|
adamc@1022
|
277 ps <- queryX q
|
adamc@1022
|
278 (fn r => <xml><li><a link={one r.Paper.Id}>{M.summarizePaper (r.Paper -- #Id)}</a></li></xml>);
|
adamc@1009
|
279 return <xml><body>
|
adamc@1009
|
280 <h1>All Papers</h1>
|
adamc@1022
|
281
|
adamc@1009
|
282 <ul>
|
adamc@1009
|
283 {ps}
|
adamc@1009
|
284 </ul>
|
adamc@1009
|
285 </body></xml>
|
adamc@1009
|
286
|
adamc@1022
|
287 and all () =
|
adamc@1022
|
288 checkOnPc;
|
adamc@1022
|
289 listPapers (SELECT paper.Id, paper.{{map fst M.paper}} FROM paper)
|
adamc@1022
|
290
|
adamc@1022
|
291 and your () =
|
adamc@1022
|
292 me <- getLogin;
|
adamc@1022
|
293 listPapers (sql_query {Rows = sql_query1 {Distinct = False,
|
adamc@1022
|
294 From = O.joinYourPaper (sql_from_table [#Paper] paper),
|
adamc@1022
|
295 Where = (WHERE TRUE),
|
adamc@1022
|
296 GroupBy = sql_subset_all [_],
|
adamc@1022
|
297 Having = (WHERE TRUE),
|
adamc@1022
|
298 SelectFields = sql_subset [[Paper = ([Id = _] ++ map fst M.paper, _)]
|
adamc@1022
|
299 ++ map (fn ts => ([], ts))
|
adamc@1022
|
300 O.yourPaperTables],
|
adamc@1022
|
301 SelectExps = {}},
|
adamc@1022
|
302 OrderBy = sql_order_by_Nil [_],
|
adamc@1022
|
303 Limit = sql_no_limit,
|
adamc@1022
|
304 Offset = sql_no_offset})
|
adamc@1022
|
305
|
adamc@1009
|
306 and one id =
|
adamc@1012
|
307 let
|
adamc@1012
|
308 fun newReview r =
|
adamc@1012
|
309 me <- getLogin;
|
adamc@1012
|
310 checkPaper id;
|
adamc@1012
|
311 dml (insert review ({Paper = sql_inject id, User = sql_inject me.Id}
|
adamc@1012
|
312 ++ ensql M.review r M.reviewFolder));
|
adamc@1012
|
313 one id
|
adamc@1009
|
314
|
adamc@1012
|
315 fun saveReview r =
|
adamc@1012
|
316 me <- getLogin;
|
adamc@1012
|
317 checkPaper id;
|
adamc@1012
|
318 dml (update [map fst M.review] ! (ensql M.review r M.reviewFolder)
|
adamc@1012
|
319 review (WHERE T.Paper = {[id]} AND T.User = {[me.Id]}));
|
adamc@1012
|
320 one id
|
adamc@1012
|
321 in
|
adamc@1012
|
322 me <- getLogin;
|
adamc@1012
|
323 checkPaper id;
|
adamc@1012
|
324 ro <- oneOrNoRows (SELECT paper.{{map fst M.paper}}, octet_length(paper.Document) AS N
|
adamc@1012
|
325 FROM paper
|
adamc@1012
|
326 WHERE paper.Id = {[id]});
|
adamc@1012
|
327 authors <- queryX (SELECT user.Nam
|
adamc@1012
|
328 FROM authorship
|
adamc@1012
|
329 JOIN user ON authorship.User = user.Id
|
adamc@1012
|
330 WHERE authorship.Paper = {[id]})
|
adamc@1012
|
331 (fn r => <xml><li>{[r.User.Nam]}</li></xml>);
|
adamc@1012
|
332 myReview <- oneOrNoRows1 (SELECT review.{{map fst M.review}}
|
adamc@1012
|
333 FROM review
|
adamc@1012
|
334 WHERE review.User = {[me.Id]}
|
adamc@1012
|
335 AND review.Paper = {[id]});
|
adamc@1012
|
336 case ro of
|
adamc@1012
|
337 None => error <xml>Paper not found!</xml>
|
adamc@1012
|
338 | Some r => return <xml><body>
|
adamc@1012
|
339 <h1>Paper #{[id]}</h1>
|
adamc@1010
|
340
|
adamc@1012
|
341 <h3>Authors:</h3>
|
adamc@1012
|
342 <ul>
|
adamc@1012
|
343 {authors}
|
adamc@1012
|
344 </ul>
|
adamc@1009
|
345
|
adamc@1012
|
346 {allContent M.paper r.Paper M.paperFolder}<br/>
|
adamc@1011
|
347
|
adamc@1012
|
348 {if r.N = 0 then
|
adamc@1012
|
349 <xml><div>No paper uploaded yet.</div></xml>
|
adamc@1012
|
350 else
|
adamc@1012
|
351 <xml><a link={download id}>Download paper</a> ({[r.N]} bytes)</xml>}
|
adamc@1011
|
352
|
adamc@1012
|
353 <hr/>
|
adamc@1011
|
354
|
adamc@1012
|
355 {case myReview of
|
adamc@1012
|
356 None => <xml>
|
adamc@1012
|
357 <h2>Add Your Review</h2>
|
adamc@1012
|
358
|
adamc@1012
|
359 <form>
|
adamc@1012
|
360 {allWidgets M.review M.reviewFolder}
|
adamc@1012
|
361 <submit value="Add" action={newReview}/>
|
adamc@1012
|
362 </form>
|
adamc@1012
|
363 </xml>
|
adamc@1012
|
364 | Some myReview => <xml>
|
adamc@1012
|
365 <h2>Edit Your Review</h2>
|
adamc@1012
|
366
|
adamc@1012
|
367 <form>
|
adamc@1012
|
368 {allPopulated M.review myReview M.reviewFolder}
|
adamc@1012
|
369 <submit value="Save" action={saveReview}/>
|
adamc@1012
|
370 </form>
|
adamc@1012
|
371 </xml>}
|
adamc@1012
|
372 </body></xml>
|
adamc@1012
|
373 end
|
adamc@1009
|
374
|
adamc@1009
|
375 and download id =
|
adamc@1009
|
376 checkPaper id;
|
adamc@1009
|
377 ro <- oneOrNoRows (SELECT paper.Document
|
adamc@1009
|
378 FROM paper
|
adamc@1009
|
379 WHERE paper.Id = {[id]});
|
adamc@1009
|
380 case ro of
|
adamc@1009
|
381 None => error <xml>Paper not found!</xml>
|
adamc@1009
|
382 | Some r => returnBlob r.Paper.Document (blessMime "application/pdf")
|
adamc@1009
|
383
|
adamc@1001
|
384 end
|