-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)}.