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