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