payment: note that Stripe has instituted an additional 1% fee for non-US cards
[hcoop/portal.git] / init.sml
index 390b9a8..a44bc35 100644 (file)
--- a/init.sml
+++ b/init.sml
@@ -1,40 +1,94 @@
 structure Init :> INIT =
 struct
 
-open Util Sql
+open Util Sql Config
 structure C = PgClient
 
+fun nullableFromSql f x =
+    if C.isNull x then
+       NONE
+    else
+       SOME (f x)
+fun nullableToSql f x =
+    case x of
+       NONE => "NULL"
+      | SOME x => f x
+
 exception Access of string
+exception NeedTos
 
-fun conn () = C.conn "dbname='hcoop'"
+fun conn () = C.conn dbstring
 val close = C.close
 
-type user = {id : int, name : string, rname : string, bal : int, joined : C.timestamp}
+type user = {id : int, name : string, rname : string, bal : int, joined : C.timestamp,
+            app : int, shares : int, paypal : string option, checkout : string option }
 
 val db = ref (NONE : C.conn option)
 val user = ref (NONE : user option)
 
+fun fromSql v =
+    if C.isNull v then
+       "NULL"
+    else
+       C.stringFromSql v
+
+fun rowError (tab, vs) = raise Fail ("Bad " ^ tab ^ "row: " ^ makeSet fromSql vs)
+
 fun getDb () = valOf (!db)
 
-fun mkUserRow [id, name, rname, bal, joined] =
+fun mkUserRow [id, name, rname, bal, joined, app, shares, paypal, checkout] =
     {id = C.intFromSql id, name = C.stringFromSql name, rname = C.stringFromSql rname,
-     bal = C.intFromSql bal, joined = C.timestampFromSql joined}
-  | mkUserRow row = raise Fail ("Bad user row : " ^ makeSet id row)
+     bal = C.intFromSql bal, joined = C.timestampFromSql joined,
+     app = C.intFromSql app, shares = C.intFromSql shares,
+     paypal = nullableFromSql C.stringFromSql paypal,
+     checkout = nullableFromSql C.stringFromSql checkout}
+  | mkUserRow row = rowError ("user", row)
 
 fun init () =
     let
+       val _ = Util.init ()
+
        val c = conn ()
     in
+       db := SOME c;
        C.dml c "BEGIN";
        case Web.getCgi "REMOTE_USER" of
            NONE => raise Fail "Not logged in"
          | SOME name =>
-           (case C.oneOrNoRows c ($`SELECT id, name, rname, bal, joined
-                                    FROM WebUser
-                                    WHERE name=^(C.stringToSql name)`) of
-                NONE => raise Fail "User not found"
-              | SOME r => user := SOME (mkUserRow r));
-           db := SOME c
+           let
+               val name =
+                   if String.isSuffix kerberosSuffix name then
+                       String.substring (name, 0, size name - size kerberosSuffix)
+                   else
+                       name
+           in
+               case C.oneOrNoRows c ($`SELECT id, name, rname, bal, joined, app, shares, paypal, checkout
+                                       FROM WebUserActive
+                                       WHERE name=^(C.stringToSql name)`) of
+                   NONE => raise Fail "User not found"
+                 | SOME r =>
+                   let
+                       val r = mkUserRow r
+                   in
+                       user := SOME r;
+                       case C.oneOrNoRows c ($`SELECT ipaddr
+                                               FROM MemberApp
+                                               WHERE id = ^(C.intToSql (#app r))
+                                                               AND ipaddr IS NOT NULL`) of
+                           NONE =>
+                           if Web.getParam "agree" = "on" then
+                               (case Web.getCgi "REMOTE_ADDR" of
+                                    NONE => raise Fail "REMOTE_ADDR not set"
+                                  | SOME ra =>
+                                    ignore (C.dml c ($`UPDATE MemberApp
+                                                       SET ipaddr = ^(C.stringToSql ra),
+                                                         applied = CURRENT_TIMESTAMP
+                                                       WHERE id = ^(C.intToSql (#app r))`)))
+                           else
+                               raise NeedTos
+                         | _ => ()
+                   end
+           end
     end
 
 fun done () =
@@ -50,28 +104,36 @@ fun getUserId () = #id (getUser ())
 fun getUserName () = #name (getUser ())
 
 fun lookupUser id =
-    mkUserRow (C.oneRow (getDb ()) ($`SELECT id, name, rname, bal, joined
+    mkUserRow (C.oneRow (getDb ()) ($`SELECT id, name, rname, bal, joined, app, shares, paypal, checkout
                                      FROM WebUser
                                      WHERE id = ^(C.intToSql id)`))
 
 fun listUsers () =
-    C.map (getDb ()) mkUserRow ($`SELECT id, name, rname, bal, joined
+    C.map (getDb ()) mkUserRow ($`SELECT id, name, rname, bal, joined, app, shares, paypal, checkout
                                  FROM WebUser
                                  ORDER BY name`)
 
+fun listActiveUsers () =
+    C.map (getDb ()) mkUserRow ($`SELECT id, name, rname, bal, joined, app, shares, paypal, checkout
+                                 FROM WebUserActive
+                                 ORDER BY name`)
+
 fun nextSeq (db, seq) =
     case C.oneRow db ($`SELECT nextval('^(seq)')`) of
-       [id] => id
+       [id] => C.intFromSql id
       | _ => raise Fail "Bad next sequence val"
 
-fun addUser (name, rname, bal) =
+fun addUser (name, rname, bal, app, shares) =
     let
        val db = getDb ()
        val id = nextSeq (db, "WebUserSeq")
     in
-       C.dml db ($`INSERT INTO WebUser (id, name, rname, bal, joined)
-                   VALUES (^id, ^(C.stringToSql name), ^(C.stringToSql rname), ^(C.intToSql bal), CURRENT_TIMESTAMP)`);
-       C.intFromSql id
+       C.dml db ($`INSERT INTO WebUser (id, name, rname, bal, joined, app, shares, paypal, checkout)
+                   VALUES (^(C.intToSql id), ^(C.stringToSql name), ^(C.stringToSql rname), ^(C.intToSql bal),
+                           CURRENT_TIMESTAMP, ^(C.intToSql app), ^(C.intToSql shares),
+                           (SELECT paypal FROM MemberApp WHERE id = ^(C.intToSql app)),
+                           (SELECT checkout FROM MemberApp WHERE id = ^(C.intToSql app)))`);
+       id
     end
 
 fun modUser (user : user) =
@@ -80,20 +142,150 @@ fun modUser (user : user) =
     in
        ignore (C.dml db ($`UPDATE WebUser SET
                            name = ^(C.stringToSql (#name user)), rname = ^(C.stringToSql (#rname user)),
-                              bal = ^(C.intToSql (#bal user))
+                              bal = ^(C.intToSql (#bal user)), app = ^(C.intToSql (#app user)),
+                              shares = ^(C.intToSql (#shares user)),
+                              paypal = ^(nullableToSql (C.stringToSql o Util.normEmail) (#paypal user)),
+                              checkout = ^(nullableToSql (C.stringToSql o Util.normEmail) (#checkout user))
                            WHERE id = ^(C.intToSql (#id user))`))
     end
 
+fun byPledge () =
+    C.map (getDb ()) mkUserRow ($`SELECT id, name, rname, bal, joined, app, shares, paypal, checkout
+                                 FROM WebUserPaying
+                                 WHERE shares > 1
+                                 ORDER BY shares DESC, name`)
+
 fun deleteUser id =
     C.dml (getDb ()) ($`DELETE FROM WebUser WHERE id = ^(C.intToSql id)`)
 
 fun validUsername name =
-    size name <= 10
-    andalso CharVector.all Char.isAlpha name
+    size name <= 12
+    andalso size name > 0
+    andalso Char.isLower (String.sub (name, 0))
+    andalso CharVector.all Char.isAlphaNum name
 
 fun userNameToId name =
     case C.oneOrNoRows (getDb ()) ($`SELECT id FROM WebUser WHERE name = ^(C.stringToSql name)`) of
        SOME [id] => SOME (C.intFromSql id)
       | _ => NONE
 
-end
\ No newline at end of file
+fun dateString () =
+    case C.oneRow (getDb ()) "SELECT CURRENT_DATE" of
+       [d] => C.stringFromSql d
+      | r => rowError ("dateString", r)
+
+type node = {id : int, name : string, descr : string, debian : string}
+
+fun mkNodeRow [id, name, descr, debian] =
+    {id = C.intFromSql id, name = C.stringFromSql name, descr = C.stringFromSql descr,
+     debian = C.stringFromSql debian}
+  | mkNodeRow row = rowError ("node", row)
+
+fun listNodes () =
+    C.map (getDb ()) mkNodeRow ($`SELECT id, name, descr, debian
+                                 FROM WebNode
+                                  WHERE id IN (SELECT id FROM ActiveWebNode)
+                                 ORDER BY name`)
+
+fun nodeName id =
+    case C.oneRow (getDb ()) ($`SELECT name
+                               FROM WebNode
+                               WHERE id = ^(C.intToSql id)`) of
+       [name] => C.stringFromSql name
+      | row => rowError ("nodeName", row)
+
+fun nodeDebian id =
+    case C.oneRow (getDb ()) ($`SELECT debian
+                               FROM WebNode
+                               WHERE id = ^(C.intToSql id)`) of
+       [debian] => C.stringFromSql debian
+      | row => rowError ("nodeDebian", row)
+
+fun explain e =
+    case e of
+       OS.SysErr (name, sop) =>
+       "System error: " ^ name ^
+       (case sop of
+            NONE => ""
+          | SOME syserr => ": " ^ OS.errorName syserr ^ ": " ^ OS.errorMsg syserr)
+      | _ => "Unknown"
+
+fun tokens () =
+    let
+       val proc = Unix.execute ("/usr/bin/tokens", [])
+       val inf = Unix.textInstreamOf proc
+
+       fun reader acc =
+           case TextIO.inputLine inf of
+               NONE => String.concat (rev acc)
+             | SOME s => reader (s :: acc)
+    in
+       reader []
+       before (TextIO.closeIn inf;
+               ignore (Unix.reap proc))
+    end
+
+fun tokensForked () =
+    case Posix.Process.fork () of
+       NONE => (OS.Process.system "/usr/bin/tokens >/tmp/tokens.child";
+                OS.Process.exit OS.Process.success)
+      | _ => ignore (OS.Process.system "/usr/bin/tokens >/tmp/tokens.parent")
+
+fun usersDiff (ls1, ls2) =
+    {onlyInFirst = List.filter (fn x => not (Util.mem (x, ls2))) ls1,
+     onlyInSecond = List.filter (fn x => not (Util.mem (x, ls1))) ls2}
+
+fun listUsernames () = C.map (getDb ())
+                            (fn [name] => C.stringFromSql name
+                              | row => rowError ("listUsernames", row))
+                            "SELECT name FROM WebUserActive ORDER BY name"
+fun usersInAfs () =
+    let
+       fun explore (dir, level, acc) =
+           if level = 3 then
+               dir :: acc
+           else
+               let
+                   val dr = Posix.FileSys.opendir dir
+
+                   fun loop acc =
+                       case Posix.FileSys.readdir dr of
+                           NONE => acc
+                         | SOME name =>
+                           let
+                               val dir' = OS.Path.joinDirFile {dir = dir,
+                                                               file = name}
+
+                               val acc = explore (dir', level+1, acc)
+                           in
+                               loop acc
+                           end
+               in
+                   loop acc
+                   before Posix.FileSys.closedir dr
+               end
+
+       val acc = explore ("/afs/hcoop.net/user", 0, [])
+    in
+       List.map OS.Path.file acc
+    end
+
+fun searchPaypal paypal =
+    C.map (getDb ()) mkUserRow ($`SELECT id, name, rname, bal, joined, app, shares, paypal, checkout
+                                 FROM WebUser
+                                 WHERE paypal = ^(C.stringToSql (normEmail paypal))
+                                 ORDER BY name`)
+    
+fun searchCheckout checkout =
+    C.map (getDb ()) mkUserRow ($`SELECT id, name, rname, bal, joined, app, shares, paypal, checkout
+                                 FROM WebUser
+                                 WHERE checkout = ^(C.stringToSql (normEmail checkout))
+                                 ORDER BY name`)
+
+fun searchRealName realname =
+    C.map (getDb ()) mkUserRow ($`SELECT id, name, rname, bal, joined, app, shares, paypal, checkout
+                                 FROM WebUser
+                                 WHERE rname ILIKE (^(C.stringToSql "%") || trim (both ^(C.stringToSql " ") from ^(C.stringToSql realname)) || ^(C.stringToSql "%")) 
+                                 ORDER BY name`)
+
+end