summary refs log tree commit diff homepage
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--hybring.cr24
-rw-r--r--sqlite.cr29
-rw-r--r--xhtml.cr187
4 files changed, 137 insertions, 107 deletions
diff --git a/.gitignore b/.gitignore
index 86df8d7..66533b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
-*.atom
+static/
 *.db
-*.xhtml
+*.ini
diff --git a/hybring.cr b/hybring.cr
index f4a2f07..13ba893 100644
--- a/hybring.cr
+++ b/hybring.cr
@@ -17,6 +17,7 @@
 # along with hybring.  If not, see <https://www.gnu.org/licenses/>.
 
 require "http/server"
+require "ini"
 require "uri"
 
 require "./sqlite"
@@ -31,8 +32,16 @@ def http_error(context, status, message = nil)
   context.response.respond_with_status status, message
 end
 
-db = Database.new "hybring.db"
-File.write "index.xhtml", page
+if ARGV.empty?
+  puts "usage: #{PROGRAM_NAME} config.ini"
+  exit 1
+end
+cfg = INI.parse File.read ARGV[0]
+db = Database.new cfg["path"]["db"]
+members = db.members
+page = Page.new cfg["url"]["static"], cfg["url"]["api"]
+File.write Path[cfg["path"]["static"]] / "index.xhtml", page.build members
+
 server = HTTP::Server.new do |context|
   # Manually crafted request
   next http_error context, 405 if context.request.method != "POST"
@@ -78,16 +87,23 @@ server = HTTP::Server.new do |context|
   next http_error context, 400, "Invalid Parameter" if invalid_param
   next http_error context, 400, "Missing Parameter" unless params.size == 3
 
+  members.each do |nick, opennic, icann|
+    errors["nick"] = "Must be unique" if nick == params["nick"]
+    errors["opennic"] = "Must be unique" if opennic == params["opennic"]
+    errors["icann"] = "Must be unique" if icann == params["icann"]
+  end
+
   if errors.empty?
     # TODO: write feed
   else
     context.response.status_code = 400 unless errors.empty?
   end
   context.response.content_type = "application/xhtml+xml"
-  context.response.print page errors, params
+  context.response.print page.build members, errors, params
   # TODO: schedule dynamic check
 end
 
-address = server.bind_tcp 8080
+# TODO: support Unix socket
+address = server.bind_tcp cfg["listen"]["port"].to_i
 puts "Listening on http://#{address}"
 server.listen
diff --git a/sqlite.cr b/sqlite.cr
index 7df496b..4cca647 100644
--- a/sqlite.cr
+++ b/sqlite.cr
@@ -50,18 +50,18 @@ class Database
   class Statement
     def initialize(db, query)
       bytes = query.to_slice
-      Database.check SQLite.prepare db.ref, bytes, bytes.size,
+      Database.check SQLite.prepare db, bytes, bytes.size,
                                     out @ref, out @tail
     end
 
-    def ref
-      @ref
-    end
-
     def step : LibC::Int
       SQLite.step @ref
     end
 
+    def row
+      Row.new @ref
+    end
+
     def finalize
       Database.check SQLite.finalize @ref
     end
@@ -102,20 +102,19 @@ class Database
         self.exec SCHEMA do end
         self.exec "PRAGMA user_version = #{MIGRATIONS.size}" do end
       end
+    rescue ex
+      self.finalize
+      raise ex
     end
   end
 
-  def ref
-    @ref
-  end
-
   def exec(query : String)
-    stmt = Statement.new self, query
+    stmt = Statement.new @ref, query
     loop do
       rc = stmt.step
       case rc
       when SQLite::ROW
-        yield Row.new stmt.ref
+        yield stmt.row
       when SQLite::DONE
         break
       else
@@ -124,6 +123,14 @@ class Database
     end
   end
 
+  def members
+    result = [] of Tuple(String, String, String)
+    self.exec "SELECT nick, opennic, icann FROM member" do |row|
+      result << {row[0].text, row[1].text, row[2].text}
+    end
+    result
+  end
+
   def finalize
     Database.check SQLite.close @ref
   end
diff --git a/xhtml.cr b/xhtml.cr
index 4f7eca4..7e21309 100644
--- a/xhtml.cr
+++ b/xhtml.cr
@@ -34,99 +34,103 @@ CSS = "
       .error { color: red }
     "
 
-def criteria(xml)
-  xml.element "h2" do xml.text "criteria" end
-  xml.element "p" do
-    xml.text "We accept pretty much any site"
-    xml.text " served under an OpenNIC domain"
-    xml.text " meeting the following requirements."
+class Page
+  def initialize(static_url : String, api_url : String)
+    @static_url = static_url
+    @api_url = api_url
   end
-  xml.element "ul" do
-    xml.element "li" do xml.text "Family-friendly content" end
-    xml.element "li" do xml.text "No violence incitation or doxing" end
-    xml.element "li" do xml.text "No user tracking" end
-    xml.element "li" do
-      xml.text "ICANN counterpart accessible via HTTPS"
+
+  def criteria(xml)
+    xml.element "h2" do xml.text "criteria" end
+    xml.element "p" do
+      xml.text "We accept pretty much any site"
+      xml.text " served under an OpenNIC domain"
+      xml.text " meeting the following requirements."
     end
-    xml.element "li" do
-      xml.text "Both linking to neighboring members"
+    xml.element "ul" do
+      xml.element "li" do xml.text "Family-friendly content" end
+      xml.element "li" do xml.text "No violence incitation or doxing" end
+      xml.element "li" do xml.text "No user tracking" end
+      xml.element "li" do
+        xml.text "ICANN counterpart accessible via HTTPS"
+      end
+      xml.element "li" do
+        xml.text "Both linking to neighboring members"
+      end
+    end
+    xml.element "p" do
+      xml.text "Note that the two sites needn't be the same, as long as"
+      xml.text " they represent the same entity, e.g. person or organization."
+      xml.text "  The clear net site is for serving visitors"
+      xml.text " without OpenNIC access or preferring secure connections."
     end
   end
-  xml.element "p" do
-    xml.text "Note that the two sites needn't be the same, as long as"
-    xml.text " they represent the same entity, e.g. person or organization."
-    xml.text "  The clear net site is for serving visitors"
-    xml.text " without OpenNIC access or preferring secure connections."
-  end
-end
-
-def input(xml, name, label, hint, error, value)
-  if error
-    xml.element "span"
-    xml.element "span", class: "error" do xml.text error end
-  end
-  xml.element "label", for: name do xml.text label end
-  xml.element "input", name: name, placeholder: hint, value: value,
-              required: "required"
-end
 
-def form(xml, errors = {} of String => String,
-         params = {} of String => String)
-  # FIXME: get URL from configuration
-  xml.element "form", action: "http://127.0.0.1:8080", method: "POST" do
-    input xml, "nick", "Nickname", "digits and lowercase letters",
-      errors.fetch("nick", nil), params.fetch("nick", nil)
-    input xml, "opennic", "OpenNIC URL", "e.g. http://example.null",
-      errors.fetch("opennic", nil), params.fetch("opennic", nil)
-    input xml, "icann", "ICANN URL", "e.g. https://example.net",
-      errors.fetch("icann", nil), params.fetch("icann", nil)
-    xml.element "span"
-    xml.element "input", type: "submit", value: "Let me in!"
+  def input(xml, name, label, hint, error, value)
+    if error
+      xml.element "span"
+      xml.element "span", class: "error" do xml.text error end
+    end
+    xml.element "label", for: name do xml.text label end
+    xml.element "input", name: name, placeholder: hint, value: value,
+                required: "required"
+    xml.element "br"
   end
-end
 
-def member(xml, nick, opennic, icann, feed? = false)
-  xml.element "p" do
-    xml.text "The following membership is pending for approval."
-    xml.text "  Please subscribe to the "
-    # FIXME: get base URL from configuration
-    xml.element "a", href: "http://127.0.0.1:42069/#{nick}.atom" do
-        xml.text "web feed"
+  def form(xml, errors = {} of String => String,
+           params = {} of String => String)
+    xml.element "p" do xml.text "Then, please fill out the form below." end
+    xml.element "form", action: @api_url, method: "POST" do
+      input xml, "nick", "Nickname", "digits and lowercase letters",
+        errors.fetch("nick", nil), params.fetch("nick", nil)
+      input xml, "opennic", "OpenNIC URL", "e.g. http://example.null",
+        errors.fetch("opennic", nil), params.fetch("opennic", nil)
+      input xml, "icann", "ICANN URL", "e.g. https://example.net",
+        errors.fetch("icann", nil), params.fetch("icann", nil)
+      xml.element "span"
+      xml.element "input", type: "submit", value: "Let me in!"
+      xml.element "br"
     end
-    xml.text " for further instructions."
-  end if feed?
-  xml.element "h3" do xml.text nick end
-  xml.text "OpenNIC: "
-  xml.element "a", href: opennic do xml.text opennic end
-  xml.element "br"
-  xml.text "ICANN: "
-  xml.element "a", href: icann do xml.text icann end
-  xml.element "br"
-end
+  end
 
-def page(errors = {} of String => String,
-         params = {} of String => String) : String
-  XML.build encoding: "UTF-8", indent: "  " do |xml|
-    xml.element "html", xmlns: "http://www.w3.org/1999/xhtml", lang: "en" do
-      xml.element "head" do
-        xml.element "meta", name: "viewport",
-                    content: "width=device-width,initial-scale=1.0"
-        xml.element "link", rel: "icon", href: "data:,"
-        xml.element "style" do xml.text CSS end
-        xml.element "title" do xml.text "le cercle libre" end
+  def member(xml, nick, opennic, icann, feed? = false)
+    xml.element "p" do
+      xml.text "The following membership is pending for approval."
+      xml.text "  Please subscribe to this "
+      xml.element "a", href: "#{@static_url}/#{nick}.atom" do
+          xml.text "web feed"
       end
-      xml.element "body" do
-        xml.element "h1" do xml.text "le cercle libre" end
-        xml.element "p" do
-          xml.text "The Free Circle is a web ring for sites"
-          xml.text " with an OpenNIC domain."
+      xml.text " for further instructions."
+    end if feed?
+    xml.element "h3" do xml.text nick end
+    xml.text "OpenNIC: "
+    xml.element "a", href: opennic do xml.text opennic end
+    xml.element "br"
+    xml.text "ICANN: "
+    xml.element "a", href: icann do xml.text icann end
+    xml.element "br"
+  end
+
+  def build(members, errors = {} of String => String,
+            params = {} of String => String) : String
+    XML.build encoding: "UTF-8", indent: "  " do |xml|
+      xml.element "html", xmlns: "http://www.w3.org/1999/xhtml", lang: "en" do
+        xml.element "head" do
+          xml.element "meta", name: "viewport",
+                      content: "width=device-width,initial-scale=1.0"
+          xml.element "link", rel: "icon", href: "data:,"
+          xml.element "style" do xml.text CSS end
+          xml.element "title" do xml.text "le cercle libre" end
         end
-        criteria xml
+        xml.element "body" do
+          xml.element "h1" do xml.text "le cercle libre" end
+          xml.element "p" do
+            xml.text "The Free Circle is a web ring for sites"
+            xml.text " with an OpenNIC domain."
+          end
+          criteria xml
 
-        xml.element "h2" do xml.text "joining" end
-        # FIXME: query database for this
-        left = right = "http://cercle.libre"
-        if params.empty? # static page
+          xml.element "h2" do xml.text "joining" end
           xml.element "p" do
             xml.text "First, add "
             xml.element "a",
@@ -142,25 +146,28 @@ def page(errors = {} of String => String,
           xml.element "ul" do
             xml.element "li" do
               xml.text "Left: "
+              _, left, _ = members[-1]
               xml.element "a", href: left do xml.text left end
             end
             xml.element "li" do
               xml.text "Right: "
+              _, right, _ = members[0]
               xml.element "a", href: right do xml.text right end
             end
           end
 
-          xml.element "p" do
-            xml.text "Then, please fill out the form below."
+          if params.empty? # static page
+            form xml
+          elsif errors.empty?
+            member xml, params["nick"], params["opennic"], params["icann"], true
+          else
+            form xml, errors, params
           end
-          form xml
+
           xml.element "h2" do xml.text "members" end
-          # FIXME: query database for this
-          member xml, "self", "http://cercle.libre", "https://khoanh.loang.net"
-        elsif errors.empty?
-          member xml, params["nick"], params["opennic"], params["icann"], true
-        else
-          form xml, errors, params
+          members.each do |nick, opennic, icann|
+            member xml, nick, opennic, icann
+          end
         end
       end
     end