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