generated from ITwrxOrg/EZ-Bkup
initial commit
This commit is contained in:
148
helpers/auth.nim
Normal file
148
helpers/auth.nim
Normal file
@@ -0,0 +1,148 @@
|
||||
#[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/[strutils, 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
|
||||
id*: int
|
||||
|
||||
proc getSessionBySessionId*(sessionId: string): Session =
|
||||
{.gcsafe.}:
|
||||
var session: Session
|
||||
for row in db1.rows(SelectSessionBySessionId, sessionId):
|
||||
session.id = row.getInt(0)
|
||||
session.sessionId = row.getString(1)
|
||||
session.userId = row.getInt(2)
|
||||
session.csrfToken = row.getString(3)
|
||||
return session
|
||||
|
||||
proc createUserSession*(userSession: Session) =
|
||||
{.gcsafe.}:
|
||||
db1.transaction:
|
||||
discard db1.insert(InsertUserSession, userSession.sessionId, userSession.userId, userSession.csrfToken)
|
||||
|
||||
proc deleteSession*(sessionId: string) =
|
||||
{.gcsafe.}:
|
||||
db1.transaction:
|
||||
db1.exec(DeleteSessionBySessionId, sessionId)
|
||||
|
||||
#Both form and auth have this template. The form.nim copy is exported.
|
||||
template formInput(input: string): untyped =
|
||||
readData(getBody()).getOrDefault(input)
|
||||
|
||||
proc getSessionIdFromCookies*(): string =
|
||||
{.gcsafe.}:
|
||||
let cookieString = http.headers.getOrDefault("cookie")
|
||||
let allCookies = parseCookies(cookieString)
|
||||
if allCookies.hasKey(APP_NAME & "_session"):
|
||||
return allCookies[APP_NAME & "_session"]
|
||||
else:
|
||||
return ""
|
||||
|
||||
proc setVisitorCsrfToken*(): string =
|
||||
{.gcsafe.}:
|
||||
#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
|
||||
discard createVisitorSession(visitorSession)
|
||||
return csrfToken
|
||||
|
||||
proc getUserPageCsrfToken*(): string =
|
||||
{.gcsafe.}:
|
||||
var sessionId: string
|
||||
var userSession: Session
|
||||
sessionId = getSessionIdFromCookies()
|
||||
userSession = getSessionBySessionId(sessionId)
|
||||
return userSession.csrfToken
|
||||
|
||||
proc newCsrfToken*(): string =
|
||||
{.gcsafe.}:
|
||||
#create csrf token.
|
||||
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.
|
||||
let visitorSessionCsrfToken = getSessionByCsrfToken(csrfToken).csrfToken
|
||||
if visitorSessionCsrfToken.len > 0 and csrfToken == visitorSessionCsrfToken:
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
proc isValidUserCsrfToken*(csrfToken: string): bool =
|
||||
var sessionId: string
|
||||
var userSession: Session
|
||||
sessionId = getSessionIdFromCookies()
|
||||
userSession = getSessionBySessionId(sessionId)
|
||||
if userSession.csrfToken == csrfToken:
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
proc isAuthdAdmin*(): bool =
|
||||
{.gcsafe.}:
|
||||
#get sessionId from request's cookie and see if it exists in session DB.
|
||||
var sessionId: string
|
||||
sessionId = getSessionIdFromCookies()
|
||||
if sessionId.len() > 0:
|
||||
var userSession: Session
|
||||
try:
|
||||
userSession = getSessionBySessionId(sessionId)
|
||||
if userSession.id > 0:
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
except Exception as e:
|
||||
echo e.msg
|
||||
return false
|
||||
else:
|
||||
return false
|
||||
|
||||
#adds CSRF checking for POST requests.
|
||||
proc isAuthdAdminPost*(): bool =
|
||||
{.gcsafe.}:
|
||||
#get sessionId from request's cookie and see if it exists in session DB.
|
||||
#var sessionId{.threadvar.}: string
|
||||
var sessionId: string
|
||||
sessionId = getSessionIdFromCookies()
|
||||
if sessionId.len() > 0:
|
||||
try:
|
||||
let userSession = getSessionBySessionId(sessionId)
|
||||
if userSession.id > 0:
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
except Exception as e:
|
||||
echo e.msg
|
||||
return false
|
||||
else:
|
||||
return false
|
||||
|
||||
template ifAuthAdminPost*(procName: untyped): untyped =
|
||||
if isAuthdAdminPost() == true:
|
||||
procName
|
||||
else:
|
||||
reply(Http302, [location("/login")])
|
||||
152
helpers/datetime.nim
Normal file
152
helpers/datetime.nim
Normal file
@@ -0,0 +1,152 @@
|
||||
import std/[times, strutils]
|
||||
|
||||
proc weekdayFromString(dayStr: string): Weekday =
|
||||
## Convert a string representation of a weekday to Weekday enum
|
||||
case dayStr
|
||||
of "Monday": return dMon
|
||||
of "Tuesday": return dTue
|
||||
of "Wednesday": return dWed
|
||||
of "Thursday": return dThu
|
||||
of "Friday": return dFri
|
||||
of "Saturday": return dSat
|
||||
of "Sunday": return dSun
|
||||
|
||||
proc monthFromString(monthStr: string): Month =
|
||||
## Convert a string representation of a weekday to Weekday enum
|
||||
case monthStr
|
||||
of "January": return mJan
|
||||
of "Febuary": return mFeb
|
||||
of "March": return mMar
|
||||
of "April": return mApr
|
||||
of "May": return mMay
|
||||
of "June": return mJun
|
||||
of "July": return mJul
|
||||
of "August": return mAug
|
||||
of "September": return mSep
|
||||
of "October": return mOct
|
||||
of "November": return mNov
|
||||
of "December": return mDec
|
||||
|
||||
proc nextWeekday*(targetWeekdayString: string): DateTime =
|
||||
## Calculates the next occurrence of a specific weekday
|
||||
var nextDate = now()
|
||||
# We're not including current day, so we move to the next day
|
||||
nextDate = nextDate + 1.days
|
||||
#convert passed weekday string to nim Weekday.
|
||||
let targetWeekday = weekdayFromString(targetWeekdayString)
|
||||
# Find the next occurrence of the target weekday
|
||||
while nextDate.weekday != targetWeekday:
|
||||
nextDate = nextDate + 1.days
|
||||
return nextDate
|
||||
|
||||
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
|
||||
while currentDate.weekday != weekday:
|
||||
currentDate = currentDate + 1.days
|
||||
# Move to the nth occurrence
|
||||
currentDate = currentDate + days((nth - 1) * 7)
|
||||
return currentDate
|
||||
|
||||
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
|
||||
while lastDay.weekday != weekday:
|
||||
lastDay = lastDay - 1.days
|
||||
return lastDay
|
||||
|
||||
proc nextMonthlyOnWeekdayOfWeek*(weekdayString: string, ocurrence: string): DateTime =
|
||||
var nextSendDate: DateTime
|
||||
let nextMonthsDate = now() + 1.months
|
||||
let weekNumStrings = @["1", "2", "3"]
|
||||
if ocurrence in weekNumStrings:
|
||||
nextSendDate = nthWeekdayInMonth(year(nextMonthsDate), month(nextMonthsDate), weekdayString, parseInt(ocurrence))
|
||||
#when ocurrence == "last".
|
||||
else:
|
||||
nextSendDate = lastWeekdayInMonth(year(nextMonthsDate), month(nextMonthsDate), weekdayString)
|
||||
#only use next month if that day has already occured this month, otherwise adjust it for this month instead.
|
||||
if monthDay(nextSendDate - 1.months) > monthDay(now()):
|
||||
if ocurrence in weekNumStrings:
|
||||
nextSendDate = nthWeekdayInMonth(year(nextMonthsDate), month(now()), weekdayString, parseInt(ocurrence))
|
||||
else:
|
||||
nextSendDate = lastWeekdayInMonth(year(nextMonthsDate), month(now()), weekdayString)
|
||||
return nextSendDate
|
||||
|
||||
proc nextYearlyOnWeekdayOfWeekOfMonth*(weekdayString, ocurrence, monthString: string): DateTime =
|
||||
var nextSendDate, startingDate: DateTime
|
||||
let nextYearsDate = now() + 1.years
|
||||
#let startingDate = dateTime(year(nextYearsDate), monthFromString(monthString), 01, 0, 0, 0, 0)
|
||||
startingDate = dateTime(year(nextYearsDate), monthFromString(monthString), 01)
|
||||
let weekNumStrings = @["1", "2", "3"]
|
||||
if ocurrence in weekNumStrings:
|
||||
nextSendDate = nthWeekdayInMonth(year(startingDate), month(startingDate), weekdayString, parseInt(ocurrence))
|
||||
#when ocurrence == "last".
|
||||
else:
|
||||
nextSendDate = lastWeekdayInMonth(year(startingDate), month(startingDate), weekdayString)
|
||||
#only use next year if that month and day has already occured this year, otherwise adjust it for this year instead.
|
||||
if nextSendDate - 1.years > now():
|
||||
startingDate = dateTime(year(now()), monthFromString(monthString), 01)
|
||||
let weekNumStrings = @["1", "2", "3"]
|
||||
if ocurrence in weekNumStrings:
|
||||
nextSendDate = nthWeekdayInMonth(year(startingDate), month(startingDate), weekdayString, parseInt(ocurrence))
|
||||
#when ocurrence == "last".
|
||||
else:
|
||||
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
|
||||
|
||||
let baseDate = now()
|
||||
let targetMonth = monthFromString(monthString)
|
||||
|
||||
# Get the current 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)
|
||||
|
||||
# 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)
|
||||
|
||||
let nextSendDateString = format(nextSendDate, "yyyy-MM-dd")
|
||||
#echo nextSendDateString
|
||||
#let nextSendDateString = $nextYear & "-" & formattedMonthString & "-" & $day
|
||||
nextSendDate = parse(nextSendDateString, "yyyy-MM-dd")
|
||||
|
||||
return nextSendDate
|
||||
35
helpers/db.nim
Normal file
35
helpers/db.nim
Normal file
@@ -0,0 +1,35 @@
|
||||
#[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.
|
||||
See COPYING or <https://www.gnu.org/licenses/> for details.]#
|
||||
|
||||
import sqliteral
|
||||
|
||||
#### Db1 ###
|
||||
const RemindersSchema* = "CREATE TABLE IF NOT EXISTS Reminders(id INTEGER PRIMARY KEY, title TEXT, message TEXT, notify_via TEXT, repeats INTEGER, repeat_freq TEXT, weekly_on TEXT, monthly_on_day INTEGER, monthly_on_weekday TEXT, monthly_on_week TEXT, yearly_on_month TEXT, yearly_on_day INTEGER, yearly_on_week TEXT, yearly_on_weekday TEXT, yearly_on_month2 TEXT, send_date TEXT, send_time_hr INTEGER, send_time_min INTEGER, send_time_am_pm TEXT)"
|
||||
const UsersSchema* = "CREATE TABLE IF NOT EXISTS Users(id INTEGER PRIMARY KEY, email TEXT NOT NULL, password TEXT NOT NULL)"
|
||||
const SessionsSchema* = "CREATE TABLE IF NOT EXISTS Sessions(id INTEGER PRIMARY KEY, session_id TEXT, user_id INTEGER, csrf_token TEXT NOT NULL)"
|
||||
|
||||
type
|
||||
Db1Sql* = enum
|
||||
#Reminders
|
||||
SelectAllReminders = "SELECT * FROM Reminders"
|
||||
InsertReminder = """INSERT INTO Reminders (title, message, notify_via, repeats, repeat_freq, weekly_on, monthly_on_day, monthly_on_weekday, monthly_on_week, yearly_on_month, yearly_on_day, yearly_on_week, yearly_on_weekday, yearly_on_month2, send_date, send_time_hr, send_time_min, send_time_am_pm) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"""
|
||||
UpdateReminder = """UPDATE Reminders SET title = ?, message = ?, notify_via = ?, repeats = ?, repeat_freq = ?, weekly_on = ?, monthly_on_day = ?, monthly_on_weekday = ?, monthly_on_week = ?, yearly_on_month = ?, yearly_on_day = ?, yearly_on_week = ?, yearly_on_weekday = ?, yearly_on_month2 = ?, send_date = ?, send_time_hr = ?, send_time_min = ?, send_time_am_pm = ? WHERE id = ?"""
|
||||
UpdateReminderSendDate = """UPDATE Reminders SET send_date = ? WHERE id = ?"""
|
||||
DeleteReminder = """DELETE FROM Reminders WHERE id = ?"""
|
||||
#Users
|
||||
SelectUsersByEmail = """SELECT * FROM Users WHERE email = ?"""
|
||||
SelectUserById = """SELECT * FROM Users WHERE id = ? LIMIT 1"""
|
||||
InsertUser = """INSERT INTO Users (email, password) VALUES (?,?)"""
|
||||
#Sessions
|
||||
SelectSessions = "SELECT * FROM Sessions"
|
||||
SelectSessionBySessionId = "SELECT * FROM Sessions WHERE session_id = ?"
|
||||
InsertUserSession = """INSERT INTO Sessions (session_id, user_id, csrf_token) VALUES (?,?,?)"""
|
||||
DeleteSessionBySessionId = """DELETE FROM Sessions WHERE session_id = ?"""
|
||||
DeleteSessions = """DELETE FROM Sessions"""
|
||||
SelectSessionByCsrfToken = """SELECT * FROM Sessions WHERE csrf_token = ?"""
|
||||
InsertVisitorSession = """INSERT INTO Sessions (csrf_token) VALUES (?)"""
|
||||
|
||||
var
|
||||
db1*: SQLiteral
|
||||
211
helpers/form.nim
Normal file
211
helpers/form.nim
Normal file
@@ -0,0 +1,211 @@
|
||||
#[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'"
|
||||
90
helpers/global.nim
Normal file
90
helpers/global.nim
Normal file
@@ -0,0 +1,90 @@
|
||||
#import std/[times, logging]
|
||||
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"
|
||||
const APP_MODE* = "dev"
|
||||
#dev
|
||||
const APP_URL* = "http://fmn-gs"
|
||||
#const SITE_URL* = "http://"
|
||||
const ASSETS_URL* = "http://assets.fmn-gs"
|
||||
#prod
|
||||
#const APP_URL* = "https://ssm.itwrx.org"
|
||||
#const SITE_URL* = "https://itwrx.org"
|
||||
#const ASSETS_URL* = "https://assets.itwrx.org"
|
||||
|
||||
var frStrTab* = newStringTable()
|
||||
|
||||
#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")
|
||||
|
||||
let dt = now()
|
||||
let nowDT* = dt.format("M-d-YYYY h:mm:ss tt")
|
||||
|
||||
proc writeLogLine*(errorMsg: string) =
|
||||
{.gcsafe.}:
|
||||
let logFile = open("errors.log", fmAppend)
|
||||
defer: logFile.close()
|
||||
logFile.writeLine(errorMsg)
|
||||
|
||||
#template location*(slug: string, csrfToken: string, fr: FormResult): untyped =
|
||||
template location*(slug: string): untyped =
|
||||
"location: " & APP_URL & slug
|
||||
|
||||
template locationBack*(): string =
|
||||
"location: " & http.headers.getOrDefault("referer")
|
||||
|
||||
template locationOrigin*(origin: string): untyped =
|
||||
"location: " & origin
|
||||
|
||||
proc filenameToSentence*(filename: string): string =
|
||||
#remove file extension.
|
||||
let filePath = Path filename
|
||||
let filePathEnum = splitFile(filePath)
|
||||
var name = filePathEnum[1].string
|
||||
#replace dashes and underscores with spaces.
|
||||
name = name.replace(re"_", " ")
|
||||
name = name.replace(re"-", " ")
|
||||
#strip numbers.
|
||||
name = name.replace(re"[0-9]", "")
|
||||
return name
|
||||
|
||||
proc titleToSlug*(title: string): string =
|
||||
#replace one or more spaces with dash
|
||||
var dataString = title.replace(re" +", "-")
|
||||
#replace anything that is not a letter, number or underscore with nothing.
|
||||
dataString = dataString.replace(re"[^a-zA-Z0-9-]", "")
|
||||
#convert to all lowercase.
|
||||
dataString = dataString.toLowerAscii()
|
||||
return dataString
|
||||
|
||||
proc getIdFromURI*(uri: string): int =
|
||||
#let parsedUri = parseUri(uri)
|
||||
let pathSeq = parseUri(uri).path.split('/')
|
||||
result = strutils.parseInt(pathSeq[2])
|
||||
|
||||
proc parseIntIf*(input: string): int =
|
||||
if input.len > 0:
|
||||
return parseInt(input)
|
||||
else:
|
||||
return 0
|
||||
|
||||
proc parseFloatIf*(input: string): float =
|
||||
if input.len > 0:
|
||||
return parseFloat(input)
|
||||
else:
|
||||
return 0.0
|
||||
|
||||
#not cryptographically secure.
|
||||
proc rndStr20*(): string =
|
||||
for _ in 0..20:
|
||||
add(result, char(rand(int('A') .. int('z'))))
|
||||
|
||||
proc boolToInt*(myBool: bool): int =
|
||||
if myBool == true:
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
|
||||
90
helpers/reminder.nim
Normal file
90
helpers/reminder.nim
Normal file
@@ -0,0 +1,90 @@
|
||||
import std/[times, osproc, strutils], smtp
|
||||
import ../models/reminder, ../models/user, datetime
|
||||
|
||||
proc setFutureSendDate(reminderId: int) =
|
||||
var reminder: Reminder
|
||||
reminder = getReminderById(reminderId)
|
||||
var newSendDate: DateTime
|
||||
case reminder.repeatFreq:
|
||||
of "day":
|
||||
newSendDate = now() + 1.days
|
||||
reminder.sendDate = $format(newSendDate, "yyyy-MM-dd")
|
||||
updateReminderSendDate(reminder)
|
||||
of "week":
|
||||
newSendDate = nextWeekday(reminder.weeklyOn)
|
||||
reminder.sendDate = $format(newSendDate, "yyyy-MM-dd")
|
||||
updateReminderSendDate(reminder)
|
||||
of "month":
|
||||
if reminder.monthlyOnDay > 0:
|
||||
#create DateTime with current month and year and reminder.monthlyOnDay
|
||||
newSendDate = dateTime(year(now()), month(now()), reminder.monthlyOnDay)
|
||||
#add 1 month only if monthlyOnDay hasn't occured in current month yet.
|
||||
if reminder.monthlyOnDay < monthDay(now()):
|
||||
newSendDate = newSendDate + 1.months
|
||||
reminder.sendDate = $format(newSendDate, "yyyy-MM-dd")
|
||||
updateReminderSendDate(reminder)
|
||||
else:
|
||||
#monthly on week number and weekday. e.g. "third thursday of every month".
|
||||
newSendDate = nextMonthlyOnWeekdayOfWeek($reminder.monthlyOnWeekday, $reminder.monthlyOnWeek)
|
||||
reminder.sendDate = $format(newSendDate, "yyyy-MM-dd")
|
||||
updateReminderSendDate(reminder)
|
||||
of "year":
|
||||
#yearly on month and day.
|
||||
#string zeros (db artifacts) have a length of 1...
|
||||
if reminder.yearlyOnMonth.len() > 1:
|
||||
newSendDate = nextYearlyDate(reminder.yearlyOnMonth, reminder.yearlyOnDay)
|
||||
reminder.sendDate = $format(newSendDate, "yyyy-MM-dd")
|
||||
updateReminderSendDate(reminder)
|
||||
#yearly on weekday of week of month. e.g. third thursday of november of each year.
|
||||
else:
|
||||
newSendDate = nextYearlyOnWeekdayOfWeekOfMonth($reminder.yearlyOnWeekday, $reminder.yearlyOnWeek, $reminder.yearlyOnMonth2)
|
||||
reminder.sendDate = $format(newSendDate, "yyyy-MM-dd")
|
||||
updateReminderSendDate(reminder)
|
||||
else:
|
||||
echo "Invalid value for reminder.repeatFreq in setFutureSendDate()"
|
||||
clearAllReminders()
|
||||
|
||||
proc sendEmail(reminderMsg: string) =
|
||||
let userEmailAddress = getEmailByUserId("1")
|
||||
let headers = @[("From", "mailer@itwrx.org")]
|
||||
let msg = createMessage("Reminder from Forget-Me-Not", reminderMsg, @[userEmailAddress], mCc = @[""], otherHeaders = headers)
|
||||
{.cast(raises: []).}:
|
||||
let smtpConn = newSmtp(debug=false)
|
||||
smtpConn.connect("email.itwrx.org", Port 587)
|
||||
smtpConn.startTls()
|
||||
smtpConn.auth("mailer@itwrx.org", ".AQ8u((xB(AgZh^a`jEJ~W~{0Eq?fd$")
|
||||
#loop through email messages and send to reuse connection?
|
||||
smtpConn.sendmail("mailer@itwrx.org", @[userEmailAddress], $msg)
|
||||
#manually closing not necessary?
|
||||
smtpConn.close()
|
||||
|
||||
proc sendXMPP(reminderMsg: string) =
|
||||
var output: string
|
||||
var status: int
|
||||
(output, status) = execCmdEx("xmppc -m message chat itwrx@sec-chat.itwrx.org \"" & reminderMsg & "\"")
|
||||
if status != 0:
|
||||
#log this later instead.
|
||||
echo output
|
||||
echo "error sending XMPP msg"
|
||||
|
||||
#send reminders that haven't been sent yet and set new sendDate (if repeating).
|
||||
proc sendReminders*() =
|
||||
let nowDT = now()
|
||||
let reminders = getAllReminders()
|
||||
for reminder in reminders:
|
||||
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.
|
||||
let sendDateDT = parse(sendDateDTString, "yyyy-M-d h:m:tt")
|
||||
#if sendDate is not in the future, it hasn't been sent yet (we are changing sendDate to future date during each send) and needs to be sent.
|
||||
if sendDateDT <= nowDT:
|
||||
#send reminder notification
|
||||
case reminder.notifyVia:
|
||||
of "email":
|
||||
sendEmail(reminder.message)
|
||||
of "xmpp":
|
||||
sendXMPP(reminder.message)
|
||||
else:
|
||||
sendXMPP(reminder.message)
|
||||
sendEmail(reminder.message)
|
||||
if reminder.repeats == 1:
|
||||
setFutureSendDate(reminder.id)
|
||||
226
helpers/validation.nim
Normal file
226
helpers/validation.nim
Normal file
@@ -0,0 +1,226 @@
|
||||
#[Copyright 2024 ITwrx.
|
||||
This file is part of ITwrxorg-SiteUpdata.
|
||||
ITwrxorg-SiteUpdata is released under the GNU Affero General Public License 3.0.
|
||||
See COPYING or <https://www.gnu.org/licenses/> for details.]#
|
||||
|
||||
import std/[strutils, re, typetraits, times, tables]
|
||||
import valido/[email, password]
|
||||
import "form", "global"
|
||||
|
||||
var msgString{.threadvar.}: string
|
||||
|
||||
proc vSize(sizeName: string, sizeValue: int, inputName: string, inputData: string, inputType: string) =
|
||||
case sizeName:
|
||||
of "min":
|
||||
#how we determine size depends on input type.
|
||||
case inputType:
|
||||
of "string":
|
||||
if not (inputData.len >= sizeValue):
|
||||
msgString = inputName & " must have a character count of at least " & $sizeValue & " ."
|
||||
addFormError(inputName, msgString)
|
||||
of "integer":
|
||||
if not (parseIntIf(inputData) >= sizeValue):
|
||||
msgString = inputName & " must have a value of at least " & $sizeValue & " ."
|
||||
addFormError(inputName, msgString)
|
||||
of "float":
|
||||
if not (parseFloat(inputData) >= sizeValue.float):
|
||||
msgString = inputName & " must have a value of at least " & $sizeValue & " ."
|
||||
addFormError(inputName, msgString)
|
||||
of "max":
|
||||
#how we determine size depends on input type.
|
||||
case inputType:
|
||||
of "string":
|
||||
if inputData.len > sizeValue:
|
||||
msgString = inputName & " must have a character count that is no greater than " & $sizeValue & " ."
|
||||
addFormError(inputName, msgString)
|
||||
of "integer":
|
||||
if parseIntIf(inputData) > sizeValue:
|
||||
msgString = inputName & " must have a value that is no greater than " & $sizeValue & " ."
|
||||
addFormError(inputName, msgString)
|
||||
of "float":
|
||||
if parseFloat(inputData) > sizeValue.float:
|
||||
msgString = inputName & " must have a value that is no greater than " & $sizeValue & " ."
|
||||
addFormError(inputName, msgString)
|
||||
|
||||
proc vType(inputName: string, inputData: string, validators: seq[string]): string =
|
||||
var inputType: string
|
||||
if "integer" in validators:
|
||||
try:
|
||||
#don't try to parse empty string as int.
|
||||
if inputData.len > 0:
|
||||
discard parseIntIf(inputData)
|
||||
inputType = "integer"
|
||||
except:
|
||||
msgString = inputName & " must be a whole number."
|
||||
addFormError(inputName, msgString)
|
||||
elif "float" in validators:
|
||||
try:
|
||||
discard parseFloat(inputData)
|
||||
inputType = "float"
|
||||
except:
|
||||
msgString = inputName & " must be a floating point number; a number with a decimal point. i.e. '1.5'"
|
||||
addFormError(inputName, msgString)
|
||||
elif "boolean" in validators:
|
||||
if (inputData == "true") or not (inputData == "false"):
|
||||
msgString = inputName & " must be interpretable as a boolean. i.e. true, or false."
|
||||
addFormError(inputName, msgString)
|
||||
inputType = "boolean"
|
||||
else:
|
||||
inputType = "string"
|
||||
return inputType
|
||||
|
||||
proc vRegex(inputName: string, inputData: string, inputType: string, validators: seq[string]) =
|
||||
#check for regex validators.
|
||||
for v in validators:
|
||||
if match(v, re"^(min):([0-9]+)$"):
|
||||
#get size's value from the string.
|
||||
let vNameSeq = v.split(':')
|
||||
let sizeName = vNameSeq[0]
|
||||
let sizeValue = parseIntIf(vNameSeq[1])
|
||||
vSize(sizeName, sizeValue, inputName, inputData, inputType)
|
||||
if match(v, re"^(max):([0-9]+)$"):
|
||||
let vNameSeq = v.split(':')
|
||||
let sizeName = vNameSeq[0]
|
||||
let sizeValue = parseIntIf(vNameSeq[1])
|
||||
vSize(sizeName, sizeValue, inputName, inputData, inputType)
|
||||
#ex. matches: "required_without:parent_id"
|
||||
if match(v, re"^(required_without):([a-z_]+)$"):
|
||||
let vNameSeq = v.split(':')
|
||||
#skip 0 index, as that's just the name of the validator.
|
||||
let conditionField = vNameSeq[1]
|
||||
var conditionFieldInputData{.threadvar.}: string
|
||||
conditionFieldInputData = formInput(conditionField)
|
||||
if conditionFieldInputData.len == 0:
|
||||
if inputData.len == 0:
|
||||
msgString = inputName & " is required when " & conditionField & " isn't set."
|
||||
addFormError(inputName, msgString)
|
||||
if match(v, re"^(must_unset_with):([a-z_]+)$"):
|
||||
let vNameSeq = v.split(':')
|
||||
#skip 0 index, as that's just the name of the validator.
|
||||
let conditionField = vNameSeq[1]
|
||||
var conditionFieldInputData: string
|
||||
conditionFieldInputData = formInput(conditionField)
|
||||
if conditionFieldInputData.len > 0:
|
||||
if inputData.len > 0:
|
||||
msgString = inputName & " must be empty if " & conditionField & " is not."
|
||||
addFormError(inputName, msgString)
|
||||
if match(v, re"^(required_with):([a-z_]+)$"):
|
||||
let vNameSeq = v.split(':')
|
||||
#skip 0 index, as that's just the name of the validator.
|
||||
let conditionField = vNameSeq[1]
|
||||
var conditionFieldInputData: string
|
||||
conditionFieldInputData = formInput(conditionField)
|
||||
if conditionFieldInputData.len > 0:
|
||||
if inputData.len == 0:
|
||||
msgString = inputName & " is also required when " & conditionField & " is set."
|
||||
addFormError(inputName, msgString)
|
||||
if match(v, re"^(required_with):([a-z_]+):(without):([a-z_]+)$"):
|
||||
let vNameSeq = v.split(':')
|
||||
#skip 0 index, as that's just the name of the validator.
|
||||
let conditionField1 = vNameSeq[1]
|
||||
var conditionField1InputData: string
|
||||
conditionField1InputData = formInput(conditionField1)
|
||||
let conditionField2 = vNameSeq[3]
|
||||
var conditionField2InputData: string
|
||||
conditionField2InputData = formInput(conditionField2)
|
||||
if conditionField1InputData.len > 0 and not conditionField2InputData.len > 0:
|
||||
if inputData.len == 0:
|
||||
msgString = inputName & " is required when " & conditionField1 & " is set and " & conditionField2 & " isn't set."
|
||||
addFormError(inputName, msgString)
|
||||
if match(v, re"^(required_with):([a-z_]+):(without):([a-z_]+):(and_without):([a-z_]+)$"):
|
||||
let vNameSeq = v.split(':')
|
||||
#skip 0 index, as that's just the name of the validator.
|
||||
let conditionField1 = vNameSeq[1]
|
||||
var conditionField1InputData: string
|
||||
conditionField1InputData = formInput(conditionField1)
|
||||
let conditionField2 = vNameSeq[3]
|
||||
var conditionField2InputData: string
|
||||
conditionField2InputData = formInput(conditionField2)
|
||||
let conditionField3 = vNameSeq[5]
|
||||
var conditionField3InputData: string
|
||||
conditionField3InputData = formInput(conditionField3)
|
||||
if conditionField1InputData.len > 0 and not conditionField2InputData.len > 0 and not conditionField3InputData.len > 0:
|
||||
if inputData.len == 0:
|
||||
msgString = inputName & " is required when " & conditionField1 & " is set and " & conditionField2 & " and " & conditionField3 & " aren't set."
|
||||
addFormError(inputName, msgString)
|
||||
if match(v, re"^(required_when):([a-z_]+):(equals):([a-z_]+)$"):
|
||||
let vNameSeq = v.split(':')
|
||||
#skip 0 index, as that's just the name of the validator.
|
||||
let conditionField1 = vNameSeq[1]
|
||||
var conditionField1InputData: string
|
||||
conditionField1InputData = formInput(conditionField1)
|
||||
let conditionField2 = vNameSeq[3]
|
||||
if conditionField1InputData.len > 0 and conditionField1InputData == conditionField2:
|
||||
if inputData.len == 0:
|
||||
msgString = inputName & " is required when " & conditionField1 & " equals " & conditionField2
|
||||
addFormError(inputName, msgString)
|
||||
if match(v, re"^(required_when):([a-z_]+):(equals):([a-z_]+):(without):([a-z_]+)$"):
|
||||
let vNameSeq = v.split(':')
|
||||
#skip 0 index, as that's just the name of the validator.
|
||||
let conditionField1 = vNameSeq[1]
|
||||
var conditionField1InputData: string
|
||||
conditionField1InputData = formInput(conditionField1)
|
||||
let conditionField2 = vNameSeq[3]
|
||||
let conditionField3 = vNameSeq[5]
|
||||
var conditionField3InputData: string
|
||||
conditionField3InputData = formInput(conditionField3)
|
||||
if conditionField1InputData.len > 0 and conditionField1InputData == conditionField2 and conditionField3InputData.len == 0:
|
||||
if inputData.len == 0:
|
||||
msgString = inputName & " is required when " & conditionField1 & " equals " & conditionField2 & " and " & conditionField3 & " is not set."
|
||||
addFormError(inputName, msgString)
|
||||
|
||||
proc vStandard(inputName: string, inputData: string, inputType: string, validators: seq[string]) =
|
||||
if "email" in validators:
|
||||
if not isEmail(inputData):
|
||||
msgString = inputName & " is not recognized as a valid email address."
|
||||
addFormError(inputName, msgString)
|
||||
if "min_complexity" in validators:
|
||||
if not isStrongPassword(inputData):
|
||||
msgString = inputName & " is not random/complex enough. Try to make your " & inputName & " more unpredictable by adding random numbers, random letter casing, unrelated words, special characters, etc."
|
||||
addFormError(inputName, msgString)
|
||||
|
||||
proc vHardcoded*(inputDataAll: Table[string, string], validators: seq[string]) =
|
||||
##Warning: this validator requires specific (hardcoded) input names to exist in the posted form data.
|
||||
if "future_datetime" in validators:
|
||||
let nowDT = now()
|
||||
var inputDT: DateTime
|
||||
let inputDateString = inputDataAll.getOrDefault("send_date", "")
|
||||
let inputTimeHrString = inputDataAll.getOrDefault("send_time_hr", "")
|
||||
let inputTimeMinString = inputDataAll.getOrDefault("send_time_min", "")
|
||||
let inputTimeAmPmString = inputDataAll.getOrDefault("send_time_am_pm", "")
|
||||
if inputDateString.len > 0 and inputTimeHrString.len > 0 and inputTimeMinString.len > 0 and inputTimeAmPmString.len > 0:
|
||||
let inputDatetimeString = inputDateString & " " & inputTimeHrString & ":" & inputTimeMinString & ":" & inputTimeAmPmString
|
||||
inputDT = parse(inputDatetimeString, "yyyy-M-d h:m:tt")
|
||||
else:
|
||||
inputDT = now()
|
||||
if inputDT <= nowDT:
|
||||
msgString = "send_date and send_time combined must be a DateTime in the future (compared to the DateTime at form submit)."
|
||||
addFormError("send_date", msgString)
|
||||
|
||||
proc vInput*(inputName: string, validators: seq[string]) =
|
||||
var inputType: string
|
||||
var inputData: string
|
||||
var inputDataAll: Table[string, string]
|
||||
inputData = formInput(inputName)
|
||||
inputDataAll = formInputAll()
|
||||
#check if 'required' is set and validate input if it exists. otherwise return error.
|
||||
if "required" in validators:
|
||||
if (inputData.len == 0):
|
||||
msgString = inputName & " is required."
|
||||
addFormError(inputName, msgString)
|
||||
else:
|
||||
#proceed with validation.
|
||||
#get inputType.
|
||||
inputType = vType(inputName, inputData, validators)
|
||||
#check for other validators and run if they exist.
|
||||
vStandard(inputName, inputData, inputType, validators)
|
||||
vHardcoded(inputDataAll, validators)
|
||||
vRegex(inputName, inputData, inputType, validators)
|
||||
#required is not set, but input could still exist. validate it.
|
||||
else:
|
||||
#get inputType.
|
||||
inputType = vType(inputName, inputData, validators)
|
||||
#check for other validators and run if they exist.
|
||||
vStandard(inputName, inputData, inputType, validators)
|
||||
vHardcoded(inputDataAll, validators)
|
||||
vRegex(inputName, inputData, inputType, validators)
|
||||
Reference in New Issue
Block a user