diff --git a/fmn_gs.nim b/fmn_gs.nim
index 3570432..d442f74 100644
--- a/fmn_gs.nim
+++ b/fmn_gs.nim
@@ -1,6 +1,6 @@
#[Copyright 2025 ITwrx.
-This file is part of Simple Site Manager.
-Simple Site Manager is released under the GNU Affero General Public License 3.0.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
See COPYING or for details.]#
import guildenstern/[epolldispatcher, httpserver], sqliteral
@@ -54,7 +54,6 @@ proc handlePost() =
if uri == "/send-reminders":
sendRemindersPostHandler()
let fr = getFormResult()
- #let csrfTokenInput = formInput("csrf_token")
if uri == "/login":
if isValidVisitorCsrfToken(formInput("csrf_token")):
loginPostHandler()
diff --git a/helpers/auth.nim b/helpers/auth.nim
index 1610b19..09575b5 100644
--- a/helpers/auth.nim
+++ b/helpers/auth.nim
@@ -1,16 +1,14 @@
#[Copyright 2025 ITwrx.
-This file is part of Simple Site Manager.
-Simple Site Manager is released under the GNU Affero General Public License 3.0.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
See COPYING or for details.]#
+#TODO: remove code not being used in this application. Some of this is only needed in Simple Site Manager.
+
import std/[cgi, strtabs, cookies, sysrand, base64]
import guildenstern/[httpserver], sqliteral
import "../models/session", "db", "global"
-#[type
- Session* = object
- sessionId*, csrfToken*: string
- id*, userId*: int]#
type
User* = object
email*, password*: string
@@ -54,9 +52,6 @@ proc setVisitorCsrfToken*(): string =
#create csrf token.
var csrfToken = $urandom(32)
csrfToken = base64.encode(csrfToken)
- #delete old VisitorSessions, as they are just the one time use CSRF Tokens.
- #may need to be redesigned for better multiuser robustness if it's deleting other users' unused tokens.
- #deleteVisitorSessions()
#create session in db.
var visitorSession: Session
visitorSession.csrfToken = csrfToken
@@ -77,12 +72,6 @@ proc newCsrfToken*(): string =
var csrfToken = $urandom(32)
csrfToken = base64.encode(csrfToken)
return csrfToken
-
-#[proc fCsrfToken*(): string =
- {.gcsafe.}:
- let sessionId = getSessionIdFromCookies()
- let userSession = getUserSessionBySessionId(sessionId)
- result = userSession.csrfToken]#
proc isValidVisitorCsrfToken*(csrfToken: string): bool =
#our previoulsy self-generated, valid csrfToken from the DB.
diff --git a/helpers/datetime.nim b/helpers/datetime.nim
index 398d5b9..554016e 100644
--- a/helpers/datetime.nim
+++ b/helpers/datetime.nim
@@ -1,3 +1,8 @@
+#[Copyright 2025 ITwrx.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
+See COPYING or for details.]#
+
import std/[times, strutils]
proc weekdayFromString(dayStr: string): Weekday =
@@ -41,7 +46,6 @@ proc nextWeekday*(targetWeekdayString: string): DateTime =
proc nthWeekdayInMonth*(year: int, month: Month, weekdayString: string, nth: range[1..3]): DateTime =
# Start from the first day of the month
- #var currentDate = dateTime(year, month, 1, 0, 0, 0, 0)
var currentDate = dateTime(year, month, 1)
let weekday = weekdayFromString(weekdayString)
# Find the first occurrence of the specified weekday
@@ -53,7 +57,6 @@ proc nthWeekdayInMonth*(year: int, month: Month, weekdayString: string, nth: ran
proc lastWeekdayInMonth(year: int, month: Month, weekdayString: string): DateTime =
# Create a DateTime for the last day of the given month
- #var lastDay = dateTime(year, month, getDaysInMonth(month, year), 0, 0, 0, 0)
var lastDay = dateTime(year, month, getDaysInMonth(month, year))
let weekday = weekdayFromString(weekdayString)
# Work backwards until we find the last occurrence of the specified weekday
@@ -100,53 +103,19 @@ proc nextYearlyOnWeekdayOfWeekOfMonth*(weekdayString, ocurrence, monthString: st
nextSendDate = lastWeekdayInMonth(year(startingDate), month(startingDate), weekdayString)
return nextSendDate
-#[proc nextYearDate*(monthString: string, day: int): DateTime =
- ## Aalways returns a date in the next year,
- ## regardless of whether the target date has passed in the current year
- #var nextSendDate = dateTime(year(now()) + 1, monthFromString(monthString), day, 0, 0, 0, 0)
- #var nextSendDate = dateTime(year(now()) + 1, monthFromString(monthString), day)
- #let nextSendDateString =
- #echo nextSendDate
- #return parse($nextSendDate, "yyyy-MM-dd")
- var
- let nextYear = year(now()) + 1
- let dt = dateTime(nextYear, monthFromString(monthString), day, 00, 00, 00, 00)
- #echo dt
- let nextSendDateString = format(dt, "yyyy-MM-dd")
- #echo nextSendDateString
- #let nextSendDateString = $nextYear & "-" & formattedMonthString & "-" & $day
- let nextSendDate = parse(nextSendDateString, "yyyy-MM-dd")
- #echo nextSendDateString
- return nextSendDate]#
-
-proc nextYearlyDate*(monthString: string, targetDay: int): DateTime =
- ## Calculates a date for the next occurrence of a specific month and day
- ##
- ## Parameters:
- ## - baseDate: The starting date to calculate from
- ## - targetMonth: The month (Month enum) for the target date
- ## - targetDay: The day of month for the target date
- ##
- ## Returns the next occurrence of the specified month and day, which could be:
- ## - Later this year if the target date hasn't occurred yet
- ## - Next year if the target date has already passed this year
-
+#[ Returns the next occurrence of the specified month and day, which could be:
+ - Later this year if the target date hasn't occurred yet
+ - Next year if the target date has already passed this year]#
+proc nextYearlyDate*(monthString: string, targetDay: int): DateTime =
let baseDate = now()
- let targetMonth = monthFromString(monthString)
-
+ let targetMonth = monthFromString(monthString)
# Get the current year
- let currentYear = baseDate.year
-
+ let currentYear = baseDate.year
# Create a DateTime for the target date in the current year
- var nextSendDate = dateTime(currentYear, targetMonth, targetDay, 00, 00, 00, 00)
-
+ var nextSendDate = dateTime(currentYear, targetMonth, targetDay, 00, 00, 00, 00)
# If the target date has already passed this year, move to next year
if nextSendDate <= baseDate:
- nextSendDate = dateTime(currentYear + 1, targetMonth, targetDay, 00, 00, 00, 00)
-
+ nextSendDate = dateTime(currentYear + 1, targetMonth, targetDay, 00, 00, 00, 00)
let nextSendDateString = format(nextSendDate, "yyyy-MM-dd")
- #echo nextSendDateString
- #let nextSendDateString = $nextYear & "-" & formattedMonthString & "-" & $day
- nextSendDate = parse(nextSendDateString, "yyyy-MM-dd")
-
+ nextSendDate = parse(nextSendDateString, "yyyy-MM-dd")
return nextSendDate
diff --git a/helpers/db.nim b/helpers/db.nim
index f524a9a..f306c2d 100644
--- a/helpers/db.nim
+++ b/helpers/db.nim
@@ -1,6 +1,6 @@
#[Copyright 2024 ITwrx.
-This file is part of Simple Site Manager.
-Simple Site Manager is released under the GNU Affero General Public License 3.0.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
See COPYING or for details.]#
import sqliteral
diff --git a/helpers/form.nim b/helpers/form.nim
index 3d8c99a..7376ee2 100644
--- a/helpers/form.nim
+++ b/helpers/form.nim
@@ -1,8 +1,10 @@
#[Copyright 2025 ITwrx.
-This file is part of Simple Site Manager.
-Simple Site Manager is released under the GNU Affero General Public License 3.0.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
See COPYING or for details.]#
+#TODO: remove anything not being used by Forget-Me-Not.
+
import std/[cgi, strtabs, strutils, cookies, uri, tables]
import jsony, guildenstern/httpserver, sqliteral
import "auth", "global"
diff --git a/helpers/global.nim b/helpers/global.nim
index 505b9db..1a8113d 100644
--- a/helpers/global.nim
+++ b/helpers/global.nim
@@ -1,10 +1,15 @@
-#import std/[times, logging]
+#[Copyright 2025 ITwrx.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
+See COPYING or for details.]#
+
import std/[times, strutils, re, uri, paths, random, strtabs]
#universal
const APP_PATH* = "/var/www/forget-me-not-gs"
const ASSETS_PATH* = "/var/www/forget-me-not-gs/app/assets"
const APP_NAME* = "Forget-Me-Not"
+#set to 'dev' or 'prod'
const APP_MODE* = "dev"
#dev
const APP_URL* = "http://fmn-gs"
@@ -17,6 +22,8 @@ const ASSETS_URL* = "http://assets.fmn-gs"
var frStrTab* = newStringTable()
+#TODO: implement logging or remove logging code.
+
#Guildensterns logger is conflicting with my, evidently incorrect, usage of the std lib logger so i'll just write some lines to a file for now.
#var logger* = newFileLogger("errors.log")
@@ -29,7 +36,6 @@ proc writeLogLine*(errorMsg: string) =
defer: logFile.close()
logFile.writeLine(errorMsg)
-#template location*(slug: string, csrfToken: string, fr: FormResult): untyped =
template location*(slug: string): untyped =
"location: " & APP_URL & slug
diff --git a/helpers/reminder.nim b/helpers/reminder.nim
index e71a22f..fccd928 100644
--- a/helpers/reminder.nim
+++ b/helpers/reminder.nim
@@ -1,3 +1,8 @@
+#[Copyright 2025 ITwrx.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
+See COPYING or for details.]#
+
import std/[times, osproc], smtp
import ../models/reminder, ../models/user, datetime
diff --git a/helpers/validation.nim b/helpers/validation.nim
index dc03452..63d7964 100644
--- a/helpers/validation.nim
+++ b/helpers/validation.nim
@@ -1,6 +1,6 @@
-#[Copyright 2024 ITwrx.
-This file is part of ITwrxorg-SiteUpdata.
-ITwrxorg-SiteUpdata is released under the GNU Affero General Public License 3.0.
+#[Copyright 2025 ITwrx.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
See COPYING or for details.]#
import std/[strutils, re, typetraits, times, tables]
diff --git a/models/human_checker.nim b/models/human_checker.nim
index fab210c..11a6aed 100644
--- a/models/human_checker.nim
+++ b/models/human_checker.nim
@@ -1,10 +1,11 @@
-#[Copyright 2024 ITwrx.
-This file is part of Simple Site Manager.
-Simple Site Manager is released under the GNU Affero General Public License 3.0.
+#[Copyright 2025 ITwrx.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
See COPYING or for details.]#
+#TODO: Use a human checker in login form or remove from project.
+
import std/random
-#import sqliteral, "../helpers/db"
type
HumanChecker* = object
@@ -35,32 +36,3 @@ proc getHCById*(id: int): HumanChecker =
for hc in humanCheckers:
if hc.id == id:
return hc
-
-#for some reason we are creating the humanCheckers here and then checking them from the DB, instead of one data location or the other, like we probably should have.
-#the below was probably supposed to be a test, but then it just stayed like that.
-#[proc getHumanChecker*(): HumanChecker =
- {.gcsafe.}:
- randomize()
- var humanCheckers{.threadvar.}: seq[HumanChecker]
- humanCheckers.add(HumanChecker(id: 1, question: "26 + four, minus 10", answer: 20))
- humanCheckers.add(HumanChecker(id: 2, question: "10 minus 2, + 14", answer: 22))
- humanCheckers.add(HumanChecker(id: 3, question: "15 + five, minus 3", answer: 17))
- humanCheckers.add(HumanChecker(id: 4, question: "9 + nine, minus 6", answer: 12))
- humanCheckers.add(HumanChecker(id: 5, question: "13 - three, plus 5", answer: 15))
- humanCheckers.add(HumanChecker(id: 6, question: "7 + six, plus one", answer: 14))
- humanCheckers.add(HumanChecker(id: 7, question: "22 + 8, - 2", answer: 28))
- humanCheckers.add(HumanChecker(id: 8, question: "4 - four, + ten", answer: 10))
- humanCheckers.add(HumanChecker(id: 9, question: "16 + four, minus three", answer: 17))
- humanCheckers.add(HumanChecker(id: 10, question: "twelve minus four, plus 8", answer: 16))
- return sample(humancheckers)
-
-proc getHCById*(id: int): HumanChecker =
- {.gcsafe.}:
- var hc {.threadvar.}: HumanChecker
- #prepareDb2SQL()
- for row in db2.rows(SelectHumanCheckerById, id):
- hc.id = row.getInt(0)
- hc.question = row.getString(1)
- hc.answer = row.getInt(2)
- return hc]#
-
diff --git a/models/reminder.nim b/models/reminder.nim
index d786514..dbdfd11 100644
--- a/models/reminder.nim
+++ b/models/reminder.nim
@@ -1,9 +1,8 @@
-#[Copyright 2024 ITwrx.
-This file is part of Simple Site Manager.
-Simple Site Manager is released under the GNU Affero General Public License 3.0.
+#[Copyright 2025 ITwrx.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
See COPYING or for details.]#
-#import sqliteral, "../helpers/db", "../helpers/global.nim", sequtils, algorithm, times
import sqliteral, "../helpers/db", algorithm, times
type
@@ -13,9 +12,6 @@ type
var allReminders: seq[Reminder]
-#proc myCmp(x, y: Reminder): int =
-# cmp(x.sendDate, y.sendDate)
-
proc getReminderSendDatetime*(reminder: Reminder): DateTime =
let sendDateDTString = $reminder.sendDate & " " & $reminder.sendTimeHr & ":" & $reminder.sendTimeMin & ":" & $reminder.sendTimeAmPm
#single digits for minutes, as db send_time_min is integer and won't use "00", which results in runtime parse error.
diff --git a/models/session.nim b/models/session.nim
index 4f8c06f..08054fc 100644
--- a/models/session.nim
+++ b/models/session.nim
@@ -1,6 +1,6 @@
-#[Copyright 2024 ITwrx.
-This file is part of Simple Site Manager.
-Simple Site Manager is released under the GNU Affero General Public License 3.0.
+#[Copyright 2025 ITwrx.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
See COPYING or for details.]#
import sqliteral, "../helpers/db"
diff --git a/models/user.nim b/models/user.nim
index 5edd177..e41ef27 100644
--- a/models/user.nim
+++ b/models/user.nim
@@ -1,6 +1,6 @@
#[Copyright 2025 ITwrx.
-This file is part of Simple Site Manager.
-Simple Site Manager is released under the GNU Affero General Public License 3.0.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
See COPYING or for details.]#
import sqliteral, "../helpers/db"
diff --git a/post_handlers/login_post_handler.nim b/post_handlers/login_post_handler.nim
index 8500174..6bfb53f 100644
--- a/post_handlers/login_post_handler.nim
+++ b/post_handlers/login_post_handler.nim
@@ -1,6 +1,6 @@
#[Copyright 2025 ITwrx.
-This file is part of Simple Site Manager.
-Simple Site Manager is released under the GNU Affero General Public License 3.0.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
See COPYING or for details.]#
import guildenstern/httpserver
@@ -23,7 +23,6 @@ proc loginPostHandler*() =
#discard assignErrorFR(formErrors, formOldInputs, csrfTokenInput)
discard assignErrorFR(formErrors, formOldInputs)
let frJson = formResult.toJson()
- #let cookieHeader = "Set-Cookie: form_result=" & frJson & ";" & "HttpOnly;" & "path=/;"
if APP_MODE == "dev":
cookieHeader1 = "Set-Cookie: form_result=" & frJson & ";" & "HttpOnly;" & "path=/;" & "SameSite=Lax;"
else:
@@ -46,14 +45,6 @@ proc loginPostHandler*() =
discard assignLoginSuccessFR()
let frJson = formResult.toJson()
#redirect, and set session cookie.
- #will probably need to detect requested url for redirect (with static fallback) instead of just static location.
- #Set-Cookie: =; Domain=; Secure; HttpOnly
- #dev
- #let cookieHeader1 = "Set-Cookie: " & APP_NAME & "_session=" & sessionId & ";" & "HttpOnly;" & "SameSite=Lax;"
- #let cookieHeader2 = "Set-Cookie: form_result=" & frJson & ";" & "HttpOnly;" & "path=/;" & "SameSite=Lax;"
- #prod
- #let cookieHeader1 = "Set-Cookie: " & APP_NAME & "_session=" & sessionId & ";" & "HttpOnly;" & "Secure=true;" & "SameSite=Lax;"
- #let cookieHeader2 = "Set-Cookie: form_result=" & frJson & ";" & "HttpOnly;" & "Secure=true;" & "path=/;" & "SameSite=Lax;"
if APP_MODE == "dev":
cookieHeader1 = "Set-Cookie: " & APP_NAME & "_session=" & sessionId & ";" & "HttpOnly;" & "SameSite=Lax;"
cookieHeader2 = "Set-Cookie: form_result=" & frJson & ";" & "HttpOnly;" & "path=/;" & "SameSite=Lax;"
@@ -74,8 +65,6 @@ proc loginPostHandler*() =
formResult.errors = toJson(formErrors)
formResult.oldInputs = getOldInputJson()
let frJson = formResult.toJson()
- #let cookieHeader = "Set-Cookie: form_result=" & frJson & ";" & "HttpOnly;"
- #let cookieHeader = "Set-Cookie: form_result=" & frJson & ";" & "HttpOnly;" & "path=/;"
if APP_MODE == "dev":
cookieHeader1 = "Set-Cookie: form_result=" & frJson & ";" & "HttpOnly;" & "path=/;" & "SameSite=Lax;"
else:
@@ -91,7 +80,6 @@ proc loginPostHandler*() =
formResult.errors = toJson(formErrors)
formResult.oldInputs = getOldInputJson()
let frJson = formResult.toJson()
- #let cookieHeader = "Set-Cookie: form_result=" & frJson & ";" & "HttpOnly;" & "path=/;"
if APP_MODE == "dev":
cookieHeader1 = "Set-Cookie: form_result=" & frJson & ";" & "HttpOnly;" & "path=/;" & "SameSite=Lax;"
else:
@@ -100,7 +88,6 @@ proc loginPostHandler*() =
except Exception as e:
echo e.msg
- #reply(Http500)
discard assignGeneralErrorFR(e.msg)
setFR()
reply(Http303, [location("/500")])
diff --git a/post_handlers/reminder_post_handler.nim b/post_handlers/reminder_post_handler.nim
index 96b6b37..4e3db4e 100644
--- a/post_handlers/reminder_post_handler.nim
+++ b/post_handlers/reminder_post_handler.nim
@@ -1,6 +1,6 @@
-#[Copyright 2024 ITwrx.
-This file is part of Simple Site Manager.
-Simple Site Manager is released under the GNU Affero General Public License 3.0.
+#[Copyright 2025 ITwrx.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
See COPYING or for details.]#
import guildenstern/httpserver
@@ -12,7 +12,6 @@ proc reminderCreatePostHandler*() =
try:
#attempt validation first.
vInput("title", @["required", "string", "max:150"])
- #vInput("subject", @["string", "max:175"])
vInput("message", @["required", "string", "max:255"])
vInput("notify_via", @["required", "string", "max:5"])
vInput("repeats", @["integer", "max:1"])
@@ -33,7 +32,6 @@ proc reminderCreatePostHandler*() =
#create formResult and redirect on validation errors.
if formErrors.len > 0:
addFormOldInput("title", formInput("title"))
- #addFormOldInput("subject", formInput("subject"))
addFormOldInput("message", formInput("message"))
addFormOldInput("notify_via", formInput("notify_via"))
addFormOldInput("repeats", formInput("repeats"))
@@ -58,7 +56,6 @@ proc reminderCreatePostHandler*() =
else:
var reminder: Reminder
reminder.title = formInput("title")
- #reminder.subject = formInput("subject")
reminder.message = formInput("message")
reminder.notifyVia = formInput("notify_via")
reminder.repeats = formInputInt("repeats")
@@ -83,7 +80,6 @@ proc reminderCreatePostHandler*() =
reply(Http303, [location("/")])
except CatchableError as e:
echo e.msg
- #reply(Http500)
discard assignGeneralErrorFR(e.msg)
setFR()
reply(Http303, [location("/500")])
@@ -93,7 +89,6 @@ proc reminderUpdatePostHandler*() =
#var origin = http.headers.getOrDefault("origin")
#attempt validation first.
vInput("title", @["required", "string", "max:150"])
- #vInput("subject", @["string", "max:175"])
vInput("message", @["required", "string", "max:255"])
vInput("notify_via", @["required", "string", "max:5"])
vInput("repeats", @["integer", "max:1"])
@@ -115,7 +110,6 @@ proc reminderUpdatePostHandler*() =
if formErrors.len > 0:
#since validation failed we better keep add the old inputs.
addFormOldInput("title", formInput("title"))
- #addFormOldInput("subject", formInput("subject"))
addFormOldInput("message", formInput("message"))
addFormOldInput("notify_via", formInput("notify_via"))
addFormOldInput("repeats", formInput("repeats"))
@@ -141,7 +135,6 @@ proc reminderUpdatePostHandler*() =
var reminder: Reminder
reminder.id = formInputInt("reminder_id")
reminder.title = formInput("title")
- #reminder.subject = formInput("subject")
reminder.message = formInput("message")
reminder.notifyVia = formInput("notify_via")
reminder.repeats = formInputInt("repeats")
@@ -166,7 +159,6 @@ proc reminderUpdatePostHandler*() =
reply(Http303, [location("/")])
except CatchableError as e:
echo e.msg
- #reply(Http500)
discard assignGeneralErrorFR(e.msg)
setFR()
reply(Http303, [location("/500")])
@@ -190,7 +182,6 @@ proc reminderDeletePostHandler*() =
reply(Http303, [location("/")])
except CatchableError as e:
echo e.msg
- #reply(Http500)
discard assignGeneralErrorFR(e.msg)
setFR()
reply(Http303, [location("/500")])
diff --git a/post_handlers/send_reminders_post_handler.nim b/post_handlers/send_reminders_post_handler.nim
index 0ddf441..1970e62 100644
--- a/post_handlers/send_reminders_post_handler.nim
+++ b/post_handlers/send_reminders_post_handler.nim
@@ -1,6 +1,6 @@
#[Copyright 2025 ITwrx.
-This file is part of Simple Site Manager.
-Simple Site Manager is released under the GNU Affero General Public License 3.0.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
See COPYING or for details.]#
import guildenstern/httpserver, "../helpers/reminder", "../helpers/form"
diff --git a/post_handlers/user_session_post_handler.nim b/post_handlers/user_session_post_handler.nim
index 2448e90..718aa78 100644
--- a/post_handlers/user_session_post_handler.nim
+++ b/post_handlers/user_session_post_handler.nim
@@ -1,6 +1,6 @@
#[Copyright 2025 ITwrx.
-This file is part of Simple Site Manager.
-Simple Site Manager is released under the GNU Affero General Public License 3.0.
+This file is part of Forget-Me-Not.
+Forget-Me-Not is released under the GNU Affero General Public License 3.0.
See COPYING or for details.]#
import guildenstern/httpserver, "../models/user_session", "../helpers/form"