Compare commits
5 Commits
b16930b8f4
...
aab338ce17
Author | SHA1 | Date |
---|---|---|
TEC | aab338ce17 | |
TEC | f58858fea7 | |
TEC | 9f2deffb53 | |
TEC | 9da51d0ad3 | |
TEC | 7fb590d527 |
|
@ -17,7 +17,7 @@ const RESPONSES = Dict{SurveyID, Dict{ResponseID, Union{Response, Nothing}}}()
|
||||||
|
|
||||||
function surveys(;cache::Bool=true)
|
function surveys(;cache::Bool=true)
|
||||||
if !cache || isempty(SURVEYS)
|
if !cache || isempty(SURVEYS)
|
||||||
foreach(SearchLight.query("SELECT * from surveys") |> eachrow) do s
|
foreach(SearchLight.query("SELECT * FROM surveys") |> eachrow) do s
|
||||||
SURVEYS[SurveyID(s.id)] = s.name
|
SURVEYS[SurveyID(s.id)] = s.name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -27,7 +27,8 @@ surveys(id::SurveyID; cache::Bool=true) = surveys(;cache)[id]
|
||||||
|
|
||||||
function questions(survey::SurveyID; cache::Bool=true)
|
function questions(survey::SurveyID; cache::Bool=true)
|
||||||
if !haskey(QUESTIONS, survey) || !cache
|
if !haskey(QUESTIONS, survey) || !cache
|
||||||
qns = SearchLight.query("SELECT id, type, prompt, input FROM questions WHERE survey=$survey")
|
qns = SearchLight.query(
|
||||||
|
"SELECT id, type, prompt, input FROM questions WHERE survey=$survey")
|
||||||
qns.id = Symbol.(qns.id)
|
qns.id = Symbol.(qns.id)
|
||||||
qns.type = Vector{Type}(@. Surveys.eval(Meta.parse(qns.type)))
|
qns.type = Vector{Type}(@. Surveys.eval(Meta.parse(qns.type)))
|
||||||
qns.input = Vector{Type}(@. Surveys.eval(Meta.parse.(qns.input)))
|
qns.input = Vector{Type}(@. Surveys.eval(Meta.parse.(qns.input)))
|
||||||
|
@ -38,7 +39,8 @@ end
|
||||||
|
|
||||||
function responseids(survey::SurveyID; cache::Bool=true)
|
function responseids(survey::SurveyID; cache::Bool=true)
|
||||||
if !haskey(RESPONSES, survey) || !cache
|
if !haskey(RESPONSES, survey) || !cache
|
||||||
ids = SearchLight.query("SELECT DISTINCT response FROM results WHERE survey=$survey") |>
|
ids = SearchLight.query(
|
||||||
|
"SELECT DISTINCT response FROM results WHERE survey=$survey") |>
|
||||||
r -> ResponseID.(r.response)
|
r -> ResponseID.(r.response)
|
||||||
if !haskey(RESPONSES, survey)
|
if !haskey(RESPONSES, survey)
|
||||||
RESPONSES[survey] =
|
RESPONSES[survey] =
|
||||||
|
@ -58,10 +60,12 @@ function responseids(survey::SurveyID, type::Symbol; cache::Bool=true)
|
||||||
if type == :all
|
if type == :all
|
||||||
responseids(surveyid; cache)
|
responseids(surveyid; cache)
|
||||||
elseif type == :complete
|
elseif type == :complete
|
||||||
SearchLight.query("SELECT id FROM responses WHERE survey=$survey AND completed IS NOT NULL") |>
|
SearchLight.query("SELECT id FROM responses \
|
||||||
|
WHERE survey=$survey AND completed IS NOT NULL") |>
|
||||||
r -> ResponseID.(r.id)
|
r -> ResponseID.(r.id)
|
||||||
elseif type == :incomplete
|
elseif type == :incomplete
|
||||||
SearchLight.query("SELECT id FROM responses WHERE survey=$survey AND completed IS NULL") |>
|
SearchLight.query("SELECT id FROM responses \
|
||||||
|
WHERE survey=$survey AND completed IS NULL") |>
|
||||||
r -> ResponseID.(r.id)
|
r -> ResponseID.(r.id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -72,14 +76,18 @@ function response(survey::SurveyID, response::ResponseID; cache::Bool=true)
|
||||||
end
|
end
|
||||||
@assert response in responseids(survey; cache)
|
@assert response in responseids(survey; cache)
|
||||||
if RESPONSES[survey][response] === nothing
|
if RESPONSES[survey][response] === nothing
|
||||||
responsedata = SearchLight.query("SELECT question, value FROM results WHERE survey=$survey AND response=$response")
|
responsedata = SearchLight.query(
|
||||||
|
"SELECT question, value FROM results \
|
||||||
|
WHERE survey=$survey AND response=$response")
|
||||||
answers = Dict(map(eachrow(responsedata)) do ans
|
answers = Dict(map(eachrow(responsedata)) do ans
|
||||||
qid = Symbol(ans.question)
|
qid = Symbol(ans.question)
|
||||||
anstype = questions(survey)[qid][:type]
|
anstype = questions(survey)[qid][:type]
|
||||||
value = eval(Meta.parse(ans.value))
|
value = eval(Meta.parse(ans.value))
|
||||||
qid => Answer{anstype}(value, nothing)
|
qid => Answer{anstype}(value, nothing)
|
||||||
end)
|
end)
|
||||||
metadata = SearchLight.query("SELECT started, completed, page FROM responses WHERE survey=$survey AND id=$response")
|
metadata = SearchLight.query(
|
||||||
|
"SELECT started, completed, page \
|
||||||
|
FROM responses WHERE survey=$survey AND id=$response")
|
||||||
started = parse(DateTime, metadata.started[1])
|
started = parse(DateTime, metadata.started[1])
|
||||||
completed = if !ismissing(metadata.completed[1])
|
completed = if !ismissing(metadata.completed[1])
|
||||||
parse(DateTime, metadata.completed[1]) end
|
parse(DateTime, metadata.completed[1]) end
|
||||||
|
@ -140,51 +148,66 @@ function register!(survey::Survey)
|
||||||
name = if isnothing(survey.name) "NULL" else
|
name = if isnothing(survey.name) "NULL" else
|
||||||
SearchLight.escape_value(survey.name) end
|
SearchLight.escape_value(survey.name) end
|
||||||
srepr = repr(survey, context = :module => Surveys) |> SearchLight.escape_value
|
srepr = repr(survey, context = :module => Surveys) |> SearchLight.escape_value
|
||||||
SearchLight.query("INSERT INTO surveys (id, name, repr) VALUES ($(survey.id), $name, $srepr)")
|
SearchLight.query("INSERT INTO surveys (id, name, repr) \
|
||||||
|
VALUES ($(survey.id), $name, $srepr)")
|
||||||
for (qid, q) in survey.questions
|
for (qid, q) in survey.questions
|
||||||
qid_s = string(qid) |> SearchLight.escape_value
|
qid_s = string(qid) |> SearchLight.escape_value
|
||||||
prompt = q.prompt |> SearchLight.escape_value
|
prompt = q.prompt |> SearchLight.escape_value
|
||||||
type = string(qvaltype(q)) |> SearchLight.escape_value
|
type = string(qvaltype(q)) |> SearchLight.escape_value
|
||||||
field = sprint(print, typeof(q.field), context = :module => Surveys) |> SearchLight.escape_value
|
field = sprint(print, typeof(q.field), context = :module => Surveys) |> SearchLight.escape_value
|
||||||
field = sprint(print, typeof(q.field), context = :module => Surveys) |> SearchLight.escape_value
|
field = sprint(print, typeof(q.field), context = :module => Surveys) |> SearchLight.escape_value
|
||||||
SearchLight.query("INSERT INTO questions (survey, id, type, prompt, input) VALUES ($(survey.id), $qid_s, $type, $prompt, $field)")
|
SearchLight.query(
|
||||||
|
"INSERT INTO questions (survey, id, type, prompt, input) \
|
||||||
|
VALUES ($(survey.id), $qid_s, $type, $prompt, $field)")
|
||||||
end
|
end
|
||||||
SURVEYS[survey.id] = survey.name
|
SURVEYS[survey.id] = survey.name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function register!(response::Response; cache::Bool=true)
|
function register!(response::Response, exip::Integer=0; cache::Bool=true)
|
||||||
if response.survey ∉ keys(surveys(;cache))
|
if response.survey ∉ keys(surveys(;cache))
|
||||||
register!(response.survey)
|
register!(response.survey)
|
||||||
end
|
end
|
||||||
@assert response.id ∉ responseids(response.survey; cache)
|
@assert response.id ∉ responseids(response.survey; cache)
|
||||||
SearchLight.query("INSERT INTO responses (survey, id, started, page) VALUES ($(response.survey), $(response.id), '$(string(response.started))', $(response.page))")
|
SearchLight.query(
|
||||||
|
"INSERT INTO responses (survey, id, exip, started, page) \
|
||||||
|
VALUES ($(response.survey), $(response.id), $exip, \
|
||||||
|
'$(string(response.started))', $(response.page))")
|
||||||
for (qid, ans) in response.answers
|
for (qid, ans) in response.answers
|
||||||
qid_s = SearchLight.escape_value(string(qid))
|
qid_s = SearchLight.escape_value(string(qid))
|
||||||
value_s = SearchLight.escape_value(repr(ans.value))
|
value_s = SearchLight.escape_value(repr(ans.value))
|
||||||
SearchLight.query("INSERT INTO results (survey, response, question, value) VALUES ($(response.survey), $(response.id), $qid_s, $value_s)")
|
SearchLight.query(
|
||||||
|
"INSERT INTO results (survey, response, question, value) \
|
||||||
|
VALUES ($(response.survey), $(response.id), $qid_s, $value_s)")
|
||||||
end
|
end
|
||||||
RESPONSES[response.survey][response.id] = nothing
|
RESPONSES[response.survey][response.id] = nothing
|
||||||
end
|
end
|
||||||
|
|
||||||
function deregister!(response::Response; cache::Bool=true)
|
function deregister!(response::Response; cache::Bool=true)
|
||||||
@assert response.id ∈ responseids(response.survey; cache)
|
@assert response.id ∈ responseids(response.survey; cache)
|
||||||
SearchLight.query("DELETE FROM responses WHERE survey = $(response.survey) AND id = $(response.id)")
|
SearchLight.query("DELETE FROM responses WHERE \
|
||||||
SearchLight.query("DELETE FROM results WHERE survey = $(response.survey) AND response = $(response.id)")
|
survey = $(response.survey) AND id = $(response.id)")
|
||||||
|
SearchLight.query("DELETE FROM results WHERE \
|
||||||
|
survey = $(response.survey) AND response = $(response.id)")
|
||||||
delete!(RESPONSES[response.survey], response.id)
|
delete!(RESPONSES[response.survey], response.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
function save!(response::Response, questionsids::Vector{Symbol}=keys(response.answers); cache::Bool=true)
|
function save!(response::Response, questionsids::Vector{Symbol}=keys(response.answers); cache::Bool=true)
|
||||||
@assert response.id ∈ responseids(response.survey; cache)
|
@assert response.id ∈ responseids(response.survey; cache)
|
||||||
if !isnothing(response.completed)
|
if !isnothing(response.completed)
|
||||||
SearchLight.query("UPDATE responses SET completed = '$(string(response.completed))' WHERE survey = $(response.survey) AND id = $(response.id)")
|
SearchLight.query(
|
||||||
|
"UPDATE responses SET completed = '$(string(response.completed))' \
|
||||||
|
WHERE survey = $(response.survey) AND id = $(response.id)")
|
||||||
end
|
end
|
||||||
SearchLight.query("UPDATE responses SET page = $(response.page) WHERE survey = $(response.survey) AND id = $(response.id)")
|
SearchLight.query("UPDATE responses SET page = $(response.page) \
|
||||||
|
WHERE survey = $(response.survey) AND id = $(response.id)")
|
||||||
for qid in questionsids
|
for qid in questionsids
|
||||||
qid_s = SearchLight.escape_value(string(qid))
|
qid_s = SearchLight.escape_value(string(qid))
|
||||||
ans = response.answers[qid]
|
ans = response.answers[qid]
|
||||||
value_s = SearchLight.escape_value(repr(ans.value))
|
value_s = SearchLight.escape_value(repr(ans.value))
|
||||||
SearchLight.query("UPDATE results SET value = $value_s WHERE survey = $(response.survey) AND response = $(response.id) AND question = $qid_s")
|
SearchLight.query(
|
||||||
|
"UPDATE results SET value = $value_s WHERE survey = $(response.survey) \
|
||||||
|
AND response = $(response.id) AND question = $qid_s")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -208,8 +231,12 @@ function clear!(survey::Survey)
|
||||||
end
|
end
|
||||||
|
|
||||||
function clear!(response::Response)
|
function clear!(response::Response)
|
||||||
SearchLight.query("DELETE FROM responses WHERE survey = $(response.survey), id = $(response.id)")
|
SearchLight.query(
|
||||||
SearchLight.query("DELETE FROM results WHERE survey = $(response.survey), response = $(response.id)")
|
"DELETE FROM responses \
|
||||||
|
WHERE survey = $(response.survey), id = $(response.id)")
|
||||||
|
SearchLight.query(
|
||||||
|
"DELETE FROM results \
|
||||||
|
WHERE survey = $(response.survey), response = $(response.id)")
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -34,10 +34,16 @@ function index()
|
||||||
end
|
end
|
||||||
|
|
||||||
function new()
|
function new()
|
||||||
|
function encrypted_xord_ip()
|
||||||
|
ip_str = get(Dict(payload()[:REQUEST].headers), "X-Forwarded-For", "0.0.0.0")
|
||||||
|
ip = parse.(UInt8, split(ip_str, '.'))
|
||||||
|
@info "client ip: $ip"
|
||||||
|
xor(reinterpret(UInt32, Genie.Encryption.encrypt(rand(UInt8, 4)) |> hex2bytes)...)
|
||||||
|
end
|
||||||
r = Surveys.Response(SURVEY, vcat(responseids(SURVEY),
|
r = Surveys.Response(SURVEY, vcat(responseids(SURVEY),
|
||||||
Vector{Surveys.ResponseID}(keys(INPROGRESS) |> collect)))
|
Vector{Surveys.ResponseID}(keys(INPROGRESS) |> collect)))
|
||||||
INPROGRESS[r.id] = r
|
INPROGRESS[r.id] = r
|
||||||
register!(r)
|
register!(r, encrypted_xord_ip())
|
||||||
uid_str = string(r.id, base=UID_ENCBASE)
|
uid_str = string(r.id, base=UID_ENCBASE)
|
||||||
Genie.Renderer.redirect(HTTP.URIs.URI(currenturl()).path * "?uid=$uid_str&page=1")
|
Genie.Renderer.redirect(HTTP.URIs.URI(currenturl()).path * "?uid=$uid_str&page=1")
|
||||||
end
|
end
|
||||||
|
|
|
@ -14,7 +14,7 @@ the questions you see now will not necessarily be in the final survey.",
|
||||||
MultiSelect(:emacs_tasks,
|
MultiSelect(:emacs_tasks,
|
||||||
"Which of the following activities do you use Emacs for?",
|
"Which of the following activities do you use Emacs for?",
|
||||||
["Work", "Hobby projects", :other]),
|
["Work", "Hobby projects", :other]),
|
||||||
NumberInput(:emacs_years,
|
IntegerInput(:emacs_years,
|
||||||
"How many years have you been using Emacs for?",
|
"How many years have you been using Emacs for?",
|
||||||
validators = v -> if v < 0
|
validators = v -> if v < 0
|
||||||
"Seriously? Are we supposed to believe you're just planning ahead?"
|
"Seriously? Are we supposed to believe you're just planning ahead?"
|
||||||
|
@ -258,9 +258,10 @@ the questions you see now will not necessarily be in the final survey.",
|
||||||
"I would like to, but cannot" => "Cannot",
|
"I would like to, but cannot" => "Cannot",
|
||||||
:other])),
|
:other])),
|
||||||
SurveyPart("Demographics (all questions are optional)",
|
SurveyPart("Demographics (all questions are optional)",
|
||||||
NumberInput(:respondent_age,
|
IntegerInput(:respondent_age,
|
||||||
"How old are you?",
|
"How old are you?",
|
||||||
validators = n -> if n < 8
|
validators = n -> if ismissing(n) # This is fine, the question is optional.
|
||||||
|
elseif n < 8
|
||||||
"My, you're advanced for you're age. <i>Suspiciously</i> advanced…"
|
"My, you're advanced for you're age. <i>Suspiciously</i> advanced…"
|
||||||
elseif n > 99
|
elseif n > 99
|
||||||
"Congratulations on becoming a centenarian! How about you get one of your grandchildren to do this survey instead?"
|
"Congratulations on becoming a centenarian! How about you get one of your grandchildren to do this survey instead?"
|
||||||
|
|
|
@ -25,6 +25,7 @@ function up()
|
||||||
[
|
[
|
||||||
column(:survey, :integer, not_null=true),
|
column(:survey, :integer, not_null=true),
|
||||||
column(:id, :integer, not_null=true),
|
column(:id, :integer, not_null=true),
|
||||||
|
column(:exip, :integer, not_null=true),
|
||||||
column(:started, :text, not_null=true),
|
column(:started, :text, not_null=true),
|
||||||
column(:completed, :text),
|
column(:completed, :text),
|
||||||
column(:page, :integer, not_null=true),
|
column(:page, :integer, not_null=true),
|
||||||
|
|
|
@ -257,12 +257,21 @@ function Response(s::Survey, oldids::Vector{ResponseID})
|
||||||
end
|
end
|
||||||
|
|
||||||
interpret(::FormField{<:AbstractString}, value::AbstractString) = value
|
interpret(::FormField{<:AbstractString}, value::AbstractString) = value
|
||||||
interpret(::FormField{Integer}, value::AbstractString) = parse(Int64, value)
|
interpret(::FormField{Integer}, value::AbstractString) =
|
||||||
|
if isempty(value)
|
||||||
|
missing
|
||||||
|
else
|
||||||
|
something(tryparse(Int64, value), value)
|
||||||
|
end
|
||||||
default_validators(::FormField{Integer}) = function(unparseable::String)
|
default_validators(::FormField{Integer}) = function(unparseable::String)
|
||||||
"Integer required. \"$unparseable\" could not be parsed as an integer."
|
"Integer required. \"$unparseable\" could not be parsed as an integer."
|
||||||
end
|
end
|
||||||
interpret(::FormField{Number}, value::AbstractString) =
|
interpret(::FormField{Number}, value::AbstractString) =
|
||||||
something(tryparse(Int64, value), parse(Float64, value))
|
if isempty(value)
|
||||||
|
missing
|
||||||
|
else
|
||||||
|
something(tryparse(Int64, value), tryparse(Float64, value), value)
|
||||||
|
end
|
||||||
default_validators(::FormField{Number}) = function(unparseable::String)
|
default_validators(::FormField{Number}) = function(unparseable::String)
|
||||||
"Number required. \"$unparseable\" could not be parsed as a number."
|
"Number required. \"$unparseable\" could not be parsed as a number."
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue