forked from ITwrxOrg/Forget-Me-Not
212 lines
7.3 KiB
Nim
212 lines
7.3 KiB
Nim
|
#[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.
|
||
|
See COPYING or <https://www.gnu.org/licenses/> for details.]#
|
||
|
|
||
|
import std/[cgi, strtabs, strutils, cookies, uri, tables]
|
||
|
import jsony, guildenstern/httpserver, sqliteral
|
||
|
import "auth", "global"
|
||
|
|
||
|
type
|
||
|
FormError* = object
|
||
|
fieldName*, fieldMessage*: string
|
||
|
type
|
||
|
FormOldInput* = object
|
||
|
fieldName*, fieldOldInput*: string
|
||
|
type
|
||
|
FormResult* = object
|
||
|
id*: int
|
||
|
message*, messageClass*, errors*, oldInputs*: string
|
||
|
|
||
|
var formError*: FormError
|
||
|
var formErrors*: seq[FormError]
|
||
|
var formOldInput*: FormOldInput
|
||
|
var formOldInputs*: seq[FormOldInput]
|
||
|
var formResult*: FormResult
|
||
|
|
||
|
#might still be in use by cookie FR type. too lazy to investigate now.
|
||
|
proc clearFormResult*() =
|
||
|
formResult = FormResult(message : "",
|
||
|
messageClass : "",
|
||
|
errors : "",
|
||
|
oldInputs : "")
|
||
|
|
||
|
proc assignErrorFR*(formErrors: seq[FormError], formOldInputs: seq[FormOldInput]): FormResult =
|
||
|
{.gcsafe.}:
|
||
|
formResult.message = "Error(s) encountered while validating form input(s)."
|
||
|
formResult.messageClass = "form-error"
|
||
|
formResult.errors = toJson(formErrors)
|
||
|
formResult.oldInputs = toJson(formOldInputs)
|
||
|
|
||
|
proc assignGeneralErrorFR*(errorMsg: string): FormResult =
|
||
|
{.gcsafe.}:
|
||
|
formResult.message = errorMsg
|
||
|
formResult.messageClass = "form-error"
|
||
|
|
||
|
proc assignGeneralSuccessFR*(successMsg: string): FormResult =
|
||
|
{.gcsafe.}:
|
||
|
formResult.message = successMsg
|
||
|
formResult.messageClass = "form-success"
|
||
|
|
||
|
proc assignLoginSuccessFR*(): FormResult =
|
||
|
{.gcsafe.}:
|
||
|
formResult.message = "You have logged in successfully."
|
||
|
formResult.messageClass = "form-success"
|
||
|
|
||
|
proc assignCECreateSuccessFR*(): FormResult =
|
||
|
{.gcsafe.}:
|
||
|
formResult.message = "Content Entity successfully created."
|
||
|
formResult.messageClass = "form-success"
|
||
|
|
||
|
proc assignCEEditSuccessFR*(): FormResult =
|
||
|
{.gcsafe.}:
|
||
|
formResult.message = "Content Entity successfully edited."
|
||
|
formResult.messageClass = "form-success"
|
||
|
|
||
|
proc assignCEDeleteSuccessFR*(): FormResult =
|
||
|
{.gcsafe.}:
|
||
|
formResult.message = "Content Entity successfully deleted."
|
||
|
formResult.messageClass = "form-success"
|
||
|
|
||
|
#using cookie to store login page formResult, because there's no session to use as formResult id yet.
|
||
|
proc getCookieFormResult*(): FormResult =
|
||
|
let cookieString = http.headers.getOrDefault("cookie")
|
||
|
let cookiesTable = parseCookies(cookieString)
|
||
|
var frJsonString: string
|
||
|
if cookiesTable.hasKey("form_result"):
|
||
|
frJsonString = cookiesTable["form_result"]
|
||
|
var formResult: FormResult
|
||
|
if frJsonString.len > 0:
|
||
|
formResult = frJsonString.fromJson(FormResult)
|
||
|
formErrors = @[]
|
||
|
clearFormResult()
|
||
|
if not formResult.message.len > 0:
|
||
|
formResult.id = 0
|
||
|
formResult.message = ""
|
||
|
formResult.messageClass = ""
|
||
|
formResult.errors = ""
|
||
|
formResult.oldInputs = ""
|
||
|
return formResult
|
||
|
|
||
|
#using strtabs for storing all other formResults.
|
||
|
proc getFormResult*(): FormResult =
|
||
|
let sessionId = getSessionIdFromCookies()
|
||
|
var frJsonString, strTabSessionId: string
|
||
|
if frStrTab.hasKey("sessionId"):
|
||
|
strTabSessionId = frStrTab["sessionId"]
|
||
|
if strTabSessionId == sessionId:
|
||
|
if frStrTab.hasKey("frJson"):
|
||
|
frJsonString = frStrTab["frJson"]
|
||
|
var newFormResult: FormResult
|
||
|
if frJsonString.len > 0:
|
||
|
newFormResult = frJsonString.fromJson(FormResult)
|
||
|
#reset formErrors seq variable used in form handlers.
|
||
|
formErrors = @[]
|
||
|
#clear the existing formResult so it won't show on next page's GET (without new POST).
|
||
|
clear(frStrTab, modeCaseSensitive)
|
||
|
if not newFormResult.message.len > 0:
|
||
|
newFormResult.id = 0
|
||
|
newFormResult.message = ""
|
||
|
newFormResult.messageClass = ""
|
||
|
newFormResult.errors = ""
|
||
|
newFormResult.oldInputs = ""
|
||
|
return newFormResult
|
||
|
|
||
|
proc setFR*() =
|
||
|
let sessionId = getSessionIdFromCookies()
|
||
|
let frJson = formResult.toJson()
|
||
|
frStrTab = {"sessionId": sessionId, "frJson": frJson}.newStringTable
|
||
|
|
||
|
proc formInput*(input: string): string =
|
||
|
{.gcsafe.}:
|
||
|
if server.contenttype == Compact:
|
||
|
#readData:cgi, getBody:Guildenstern, getOrDefault:strtabs.
|
||
|
return readData(getBody()).getOrDefault(input)
|
||
|
else:
|
||
|
#getMPStringInput(input: string)
|
||
|
echo "not url-encoded"
|
||
|
|
||
|
proc formInputAll*(): Table[string, string] =
|
||
|
{.gcsafe.}:
|
||
|
if server.contenttype == Compact:
|
||
|
#getBody:Guildenstern
|
||
|
let formDataStr = getBody()
|
||
|
var formData = initTable[string, string]()
|
||
|
# Use decodeQuery from the uri module
|
||
|
for (key, value) in decodeQuery(formDataStr):
|
||
|
formData[key] = value
|
||
|
return formData
|
||
|
else:
|
||
|
echo "not url-encoded"
|
||
|
|
||
|
proc formInputInt*(input: string): int =
|
||
|
{.gcsafe.}:
|
||
|
let readInput = readData(getBody()).getOrDefault(input)
|
||
|
return parseIntIf(readInput)
|
||
|
|
||
|
template formInputSeq*(input: string): seq[string] =
|
||
|
{.gcsafe.}:
|
||
|
readData(getBody()).getOrDefault(input)
|
||
|
|
||
|
proc addFormError*(inputName: string, msgString: string) =
|
||
|
{.gcsafe.}:
|
||
|
formError.fieldName = inputName
|
||
|
formError.fieldMessage = msgString
|
||
|
formErrors.add(formError)
|
||
|
|
||
|
proc addFormOldInput*(inputName: string, inputData: string) =
|
||
|
{.gcsafe.}:
|
||
|
formOldInput.fieldName = inputName
|
||
|
formOldInput.fieldOldInput = inputData
|
||
|
formOldInputs.add(formOldInput)
|
||
|
|
||
|
#takes a fieldName and returns the fieldMessage.
|
||
|
proc fFieldMsg*(fr: FormResult, fieldName: string): string =
|
||
|
{.gcsafe.}:
|
||
|
if fr.errors.len() > 0:
|
||
|
let errors = fromJson(fr.errors, seq[FormError])
|
||
|
for error in errors:
|
||
|
if error.fieldName == fieldName:
|
||
|
return error.fieldMessage
|
||
|
|
||
|
#this may need to handle more than one message per form field at some point, but not for this app (yet).
|
||
|
proc fErrorMsg*(fr: FormResult, fieldName:string): string =
|
||
|
{.gcsafe.}:
|
||
|
if fFieldMsg(fr, fieldName).len > 0:
|
||
|
return """<span class="text-red-500">""" & fFieldMsg(fr, fieldName) & "</span><br>"
|
||
|
|
||
|
#takes a fieldName and returns the fieldOldInput.
|
||
|
proc fOldInput*(fr: FormResult, fieldName: string, defaultValue = ""): string =
|
||
|
{.gcsafe.}:
|
||
|
var fieldOldInput: string
|
||
|
if fr.oldInputs.len() > 0:
|
||
|
let oldInputs = fromJson(fr.oldInputs, seq[FormOldInput])
|
||
|
for oldInput in oldInputs:
|
||
|
if oldInput.fieldName == fieldName:
|
||
|
fieldOldInput = oldInput.fieldOldInput
|
||
|
return fieldOldInput
|
||
|
else:
|
||
|
return defaultValue
|
||
|
|
||
|
proc getOldInputJson*(): string =
|
||
|
{.gcsafe.}:
|
||
|
let postData = readData(getBody())
|
||
|
#pairs is a strtabs iterator.
|
||
|
for key,value in pairs(postData):
|
||
|
if key.len() > 0:
|
||
|
if key != "password" and key != "csrf_token":
|
||
|
addFormOldInput(key, value)
|
||
|
return toJson(formOldInputs)
|
||
|
|
||
|
#the empty string defaultValue makes it possible to optionally supply a value from the DB: as needed in edit forms.
|
||
|
#checked proc works on radios and checboxes.
|
||
|
proc checked*(fr: FormResult, groupName: string, targetValue: string, defaultValue = ""): string =
|
||
|
{.gcsafe.}:
|
||
|
if fOldInput(fr, groupName, defaultValue) == targetValue:
|
||
|
return "checked"
|
||
|
|
||
|
proc selected*(fr: FormResult, selectName: string, targetValue: string, defaultValue = ""): string =
|
||
|
{.gcsafe.}:
|
||
|
if fOldInput(fr, selectName, defaultValue) == targetValue:
|
||
|
return "selected='selected'"
|