Packages

Session management library for Wisp. Manage user sessions in a simple key-value store.

Current section

Files

Jump to
kv_sessions src kv_sessions@postgres_adapter.erl
Raw

src/kv_sessions@postgres_adapter.erl

-module(kv_sessions@postgres_adapter).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([migrate_up/1, migrate_down/1, decode_session_row/1, encode_data/1, decode_data_from_string/1, new/1]).
-spec migrate_up(pog:connection()) -> {ok, pog:returned(nil)} |
{error, pog:query_error()}.
migrate_up(Db) ->
Sql = <<"
CREATE TABLE IF NOT EXISTS kv_sessions (
session_id VARCHAR PRIMARY KEY,
expires_at TIMESTAMP NOT NULL,
data JSON
);
"/utf8>>,
_pipe = pog:'query'(Sql),
pog:execute(_pipe, Db).
-spec migrate_down(pog:connection()) -> {ok, pog:returned(nil)} |
{error, pog:query_error()}.
migrate_down(Db) ->
Sql = <<"DROP TABLE IF EXISTS kv_sessions"/utf8>>,
_pipe = pog:'query'(Sql),
pog:execute(_pipe, Db).
-spec delete_session(pog:connection()) -> fun((kv_sessions@session:session_id()) -> {ok,
nil} |
{error, kv_sessions@session:session_error()}).
delete_session(Db) ->
fun(Session_id) ->
Sql = <<"
DELETE FROM kv_sessions
WHERE session_id = $1
"/utf8>>,
case begin
_pipe = pog:'query'(Sql),
_pipe@1 = pog:parameter(
_pipe,
pog_ffi:coerce(kv_sessions@session:id_to_string(Session_id))
),
pog:execute(_pipe@1, Db)
end of
{ok, _} ->
{ok, nil};
{error, Err} ->
gleam@io:debug(Err),
wisp:log_error(
<<"Could not delete session with id"/utf8,
(kv_sessions@session:id_to_string(Session_id))/binary>>
),
{error,
{db_error_delete_error,
<<"Could not delete session with id"/utf8,
(kv_sessions@session:id_to_string(Session_id))/binary>>}}
end
end.
-spec decode_session_row(gleam@dynamic:dynamic_()) -> {ok,
{binary(),
{{integer(), integer(), integer()},
{integer(), integer(), integer()}},
binary()}} |
{error, list(gleam@dynamic:decode_error())}.
decode_session_row(Data) ->
_pipe = Data,
_pipe@1 = gleam@dynamic:from(_pipe),
(gleam@dynamic:tuple3(
fun gleam@dynamic:string/1,
gleam@dynamic:tuple2(
gleam@dynamic:tuple3(
fun gleam@dynamic:int/1,
fun gleam@dynamic:int/1,
fun gleam@dynamic:int/1
),
gleam@dynamic:tuple3(
fun gleam@dynamic:int/1,
fun gleam@dynamic:int/1,
fun gleam@dynamic:int/1
)
),
fun gleam@dynamic:string/1
))(_pipe@1).
-spec encode_data(gleam@dict:dict(binary(), binary())) -> gleam@json:json().
encode_data(Data) ->
_pipe = Data,
_pipe@2 = gleam@dict:fold(_pipe, [], fun(Acc, Key, Val) -> _pipe@1 = Acc,
lists:append(_pipe@1, [{Key, gleam@json:string(Val)}]) end),
gleam@json:object(_pipe@2).
-spec save_session(pog:connection()) -> fun((kv_sessions@session:session()) -> {ok,
kv_sessions@session:session()} |
{error, kv_sessions@session:session_error()}).
save_session(Db) ->
fun(New_session) ->
Sql = <<"
INSERT INTO kv_sessions (session_id, expires_at, data)
VALUES ($1, $2, $3)
ON CONFLICT (session_id)
DO UPDATE SET
session_id = EXCLUDED.session_id,
data = EXCLUDED.data,
expires_at = EXCLUDED.expires_at;
"/utf8>>,
{{Year, Month, Day}, {Hour, Minute, Seconds}} = erlang:element(
3,
New_session
),
Insert = begin
_pipe = pog:'query'(Sql),
_pipe@1 = pog:parameter(
_pipe,
pog_ffi:coerce(
kv_sessions@session:id_to_string(
erlang:element(2, New_session)
)
)
),
_pipe@2 = pog:parameter(
_pipe@1,
pog:timestamp(
{timestamp,
{date, Year, Month, Day},
{time, Hour, Minute, Seconds, 0}}
)
),
_pipe@3 = pog:parameter(
_pipe@2,
pog_ffi:coerce(
gleam@json:to_string(
encode_data(erlang:element(4, New_session))
)
)
),
pog:execute(_pipe@3, Db)
end,
case Insert of
{ok, _} ->
{ok, New_session};
{error, Err} ->
gleam@io:debug(Err),
wisp:log_error(<<"Could not insert new session"/utf8>>),
{error,
{db_error_insert_error,
<<"Could not insert new session"/utf8>>}}
end
end.
-spec decode_data(gleam@dynamic:dynamic_()) -> {ok,
gleam@dict:dict(binary(), binary())} |
{error, list(gleam@dynamic:decode_error())}.
decode_data(Data) ->
_pipe = gleam@dynamic:from(Data),
(gleam@dynamic:dict(fun gleam@dynamic:string/1, fun gleam@dynamic:string/1))(
_pipe
).
-spec decode_data_from_string(binary()) -> {ok,
gleam@dict:dict(binary(), binary())} |
{error, gleam@json:decode_error()}.
decode_data_from_string(Str) ->
gleam@json:decode(Str, fun decode_data/1).
-spec get_session(pog:connection()) -> fun((kv_sessions@session:session_id()) -> {ok,
gleam@option:option(kv_sessions@session:session())} |
{error, kv_sessions@session:session_error()}).
get_session(Db) ->
fun(Session_id) ->
Sql = <<"
SELECT session_id, expires_at, data from kv_sessions
WHERE session_id = $1;
"/utf8>>,
gleam@result:'try'(
begin
_pipe = pog:'query'(Sql),
_pipe@1 = pog:parameter(
_pipe,
pog_ffi:coerce(kv_sessions@session:id_to_string(Session_id))
),
_pipe@2 = pog:returning(_pipe@1, fun decode_session_row/1),
_pipe@3 = pog:execute(_pipe@2, Db),
gleam@result:map_error(
_pipe@3,
fun(Err) ->
gleam@io:debug(Err),
wisp:log_error(
<<"Could not get session"/utf8,
(kv_sessions@session:id_to_string(Session_id))/binary>>
),
{db_error_get_error,
<<<<"Could not get session with id"/utf8,
(kv_sessions@session:id_to_string(
Session_id
))/binary>>/binary,
" from database"/utf8>>}
end
)
end,
fun(Returned) -> _pipe@4 = erlang:element(3, Returned),
_pipe@5 = gleam@list:first(_pipe@4),
_pipe@6 = gleam@option:from_result(_pipe@5),
_pipe@8 = gleam@option:map(
_pipe@6,
fun(Row) ->
gleam@result:map(
begin
_pipe@7 = decode_data_from_string(
erlang:element(3, Row)
),
gleam@result:map_error(
_pipe@7,
fun(Err@1) ->
gleam@io:debug(Err@1),
{deserialize_error,
<<"Could not deserialize data"/utf8>>}
end
)
end,
fun(Data) ->
{session,
kv_sessions@session:id_from_string(
erlang:element(1, Row)
),
erlang:element(2, Row),
Data}
end
)
end
),
(fun(R) -> case R of
{some, {ok, V}} ->
{ok, {some, V}};
{some, {error, E}} ->
{error, E};
none ->
{ok, none}
end end)(_pipe@8) end
)
end.
-spec new(pog:connection()) -> kv_sessions@session_config:session_store().
new(Db) ->
{session_store, get_session(Db), save_session(Db), delete_session(Db)}.