diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | hybring.cr | 24 | ||||
-rw-r--r-- | sqlite.cr | 29 | ||||
-rw-r--r-- | xhtml.cr | 187 |
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 |