structure Roll :> ROLL = struct open Init Sql Util fun activeUsernames () = let fun mkRow [name] = C.stringFromSql name | mkRow row = rowError ("activeUsernames", row) in C.map (getDb ()) mkRow "SELECT name FROM WebUserActive ORDER BY name" end type roll_call = { id : int, title : string, msg : string, started : C.timestamp } fun mkRollRow [id, title, msg, started] = {id = C.intFromSql id, title = C.stringFromSql title, msg = C.stringFromSql msg, started = C.timestampFromSql started} | mkRollRow row = rowError ("roll", row) fun addRollCall (title, msg) = let val db = getDb () val id = nextSeq (db, "RollCallSeq") fun addUser [uid, name] = let val uid = C.intFromSql uid val name = C.stringFromSql name val code = randomPassword () val _ = C.dml db ($`INSERT INTO RollCallEntry (rol, usr, code, responded) VALUES (^(C.intToSql id), ^(C.intToSql uid), ^(C.stringToSql code), NULL)`) val mail = Mail.mopen () in Mail.mwrite (mail, "From: Hcoop Portal \nTo: "); Mail.mwrite (mail, name); Mail.mwrite (mail, emailSuffix); Mail.mwrite (mail, "\nSubject: HCoop Roll Call: "); Mail.mwrite (mail, title); Mail.mwrite (mail, "\n\n"); Mail.mwrite (mail, "The admins want to make sure that they can reach everyone by e-mail. To let them know that you're here, please visit this URL:\n\t"); Mail.mwrite (mail, urlPrefix); Mail.mwrite (mail, "roll?cmd=respond&rol="); Mail.mwrite (mail, Int.toString id); Mail.mwrite (mail, "&code="); Mail.mwrite (mail, code); Mail.mwrite (mail, "\nYou may have to hit \"reload\" in your web browser, if you aren't already logged into the portal.\n\n"); Mail.mwrite (mail, msg); ignore (Mail.mclose mail) end | addUser row = rowError ("add roll entry", row) in C.dml db ($`INSERT INTO RollCall (id, title, msg, started) VALUES (^(C.intToSql id), ^(C.stringToSql title), ^(C.stringToSql msg), CURRENT_TIMESTAMP)`); C.app db addUser "SELECT id, name FROM WebUserActive"; id end fun modRollCall (rc : roll_call) = let val db = getDb () in ignore (C.dml db ($`UPDATE RollCall SET title = ^(C.stringToSql (#title rc)), msg = ^(C.stringToSql (#msg rc)) WHERE id = ^(C.intToSql (#id rc))`)) end fun deleteRollCall id = ignore (C.dml (getDb ()) ($`DELETE FROM RollCall WHERE id = ^(C.intToSql id)`)) fun lookupRollCall id = case C.oneOrNoRows (getDb ()) ($`SELECT id, title, msg, started FROM RollCall WHERE id = ^(C.intToSql id)`) of NONE => raise Fail "Roll call not found" | SOME row => mkRollRow row fun listRollCalls id = C.map (getDb ()) mkRollRow "SELECT id, title, msg, started FROM RollCall ORDER BY started DESC" type roll_call_entry = { rol : int, usr : int, code : string, responded : C.timestamp option } fun mkEntryRow [rol, usr, code, responded] = {rol = C.intFromSql rol, usr = C.intFromSql usr, code = C.stringFromSql code, responded = (if C.isNull responded then NONE else SOME (C.timestampFromSql responded))} | mkEntryRow row = rowError ("roll entry", row) fun lookupEntry (rol, usr) = case C.oneOrNoRows (getDb ()) ($`SELECT rol, usr, code, responded FROM RollCallEntry WHERE rol = ^(C.intToSql rol) AND usr = ^(C.intToSql usr)`) of NONE => raise Fail "Roll call entry not found" | SOME row => mkEntryRow row fun listEntries id = let fun folder (_ :: row, (didnt, did)) = let val ent = mkEntryRow row val uent = (Init.lookupUser (#usr ent), ent) in case #responded ent of NONE => (uent :: didnt, did) | SOME _ => (didnt, uent :: did) end | folder (row, _) = rowError ("listEntries folder", row) in C.fold (getDb ()) folder ([], []) ($`SELECT name, rol, usr, code, responded FROM RollCallEntry JOIN WebUser ON id = usr WHERE rol = ^(C.intToSql id) ORDER BY responded, name DESC`) end fun respond (rol, usr) = ignore (C.dml (getDb ()) ($`UPDATE RollCallEntry SET responded = CURRENT_TIMESTAMP WHERE rol = ^(C.intToSql rol) AND usr = ^(C.intToSql usr)`)) end