Current section
Files
Jump to
Current section
Files
src/models/m_server_storage.erl
%% @author Marc Worrell <marc@worrell.nl>
%% @copyright 2020-2026 Marc Worrell
%% @doc Model for server side data storage.
%% @end
%% Copyright 2020-2026 Marc Worrell
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
-module(m_server_storage).
-moduledoc("
Model to access the server side storage of data.
See the module documentation of [mod_server_storage](/id/doc_module_mod_server_storage) for information.
Available Model API Paths
-------------------------
| Method | Path pattern | Description |
| --- | --- | --- |
| `get` | `/` | Return all non-secure server-storage key/value pairs for the current session id via `z_server_storage:lookup/2`. No further lookups. |
| `get` | `/+key/...` | Return value for session key `+key` from non-secure server storage (or `not_found/no_session` error). |
| `post` | `/+key` | Store payload under session key `+key` in non-secure server storage; starts storage for the session when missing. No further lookups. |
| `delete` | `/+key` | Delete key `+key` from non-secure server storage for the current session. No further lookups. |
| `delete` | `/` | Delete all non-secure server-storage keys for the current session. No further lookups. |
`/+name` marks a variable path segment. A trailing `/...` means extra path segments are accepted for further lookups.
").
-behaviour(zotonic_model).
-export([
m_get/3,
m_post/3,
m_delete/3,
ping/1,
lookup/1,
lookup/2,
store/3,
delete/2,
delete/1,
secure_lookup/1,
secure_lookup/2,
secure_store/3,
secure_delete/2,
secure_delete/1
]).
-spec m_get( list(), zotonic_model:opt_msg(), z:context()) -> zotonic_model:return().
m_get([], _Msg, Context) ->
case lookup(Context) of
{ok, Values} ->
{ok, {Values, []}};
{error, _} = Error ->
Error
end;
m_get([ Key | Rest ], _Msg, Context) ->
case lookup(z_convert:to_binary(Key), Context) of
{ok, Value} ->
{ok, {Value, Rest}};
{error, _} = Error ->
Error
end.
-spec m_post( list( binary() ), zotonic_model:opt_msg(), z:context() ) -> zotonic_model:post_return().
m_post([ Key ], #{ payload := Payload }, Context) ->
store(Key, Payload, Context);
m_post(_Path, _Msg, _Context) ->
{error, unknown_path}.
-spec m_delete( list( binary() ), zotonic_model:opt_msg(), z:context() ) -> zotonic_model:delete_return().
m_delete([ Key ], _Msg, Context) ->
delete(Key, Context);
m_delete([], _Msg, Context) ->
delete(Context);
m_delete(_Path, _Msg, _Context) ->
{error, unknown_path}.
%% @doc Ping the session to keep it alive.
-spec ping( z:context() ) -> ok | {error, no_session | term()}.
ping(Context) ->
case z_context:session_id(Context) of
{ok, Sid} ->
z_server_storage:ping(Sid, Context);
{error, _} = Error ->
Error
end.
%% @doc Store a key in the session.
-spec store( term(), term(), z:context() ) -> ok | {error, no_session | not_found | full | term()}.
store(Key, Value, Context) ->
case z_context:session_id(Context) of
{ok, Sid} ->
case z_server_storage:store(Sid, Key, Value, Context) of
ok ->
event(Sid, #{ op => store, key => Key, value => Value }, Context);
{error, no_session} ->
_ = mod_server_storage:start_session(Sid, Context),
case z_server_storage:store(Sid, Key, Value, Context) of
ok ->
event(Sid, #{ op => store, key => Key, value => Value }, Context);
R ->
R
end;
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end.
%% @doc Find all keys in the session.
-spec lookup( z:context() ) -> {ok, map()}| {error, no_session | not_found | term()}.
lookup(Context) ->
case z_context:session_id(Context) of
{ok, Sid} ->
z_server_storage:lookup(Sid, Context);
{error, _} = Error ->
Error
end.
%% @doc Find a key in the session.
-spec lookup( term(), z:context() ) -> {ok, term()} | {error, no_session | not_found | term()}.
lookup(Key, Context) ->
case z_context:session_id(Context) of
{ok, Sid} ->
z_server_storage:lookup(Sid, Key, Context);
{error, _} = Error ->
Error
end.
%% @doc Delete a key from the session.
-spec delete( term(), z:context() ) -> ok | {error, no_session | not_found | term()}.
delete(Key, Context) ->
case z_context:session_id(Context) of
{ok, Sid} ->
case z_server_storage:delete(Sid, Key, Context) of
ok ->
event(Sid, #{ op => delete }, Context);
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end.
%% @doc Delete all keys from the session.
-spec delete( z:context() ) -> ok | {error, no_session | not_found | term()}.
delete(Context) ->
case z_context:session_id(Context) of
{ok, Sid} ->
case z_server_storage:delete(Sid, Context) of
ok ->
event(Sid, #{ op => delete }, Context);
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end.
%% @doc Store a key/valye in the session, without access via the model access functions.
-spec secure_store( term(), term(), z:context() ) -> ok | {error, no_session | not_found | term()}.
secure_store(Key, Value, Context) ->
case z_context:session_id(Context) of
{ok, Sid} ->
case z_server_storage:secure_store(Sid, Key, Value, Context) of
ok ->
event(Sid, #{ op => secure_store }, Context);
{error, no_session} ->
_ = mod_server_storage:start_session(Sid, Context),
case z_server_storage:secure_store(Sid, Key, Value, Context) of
ok ->
event(Sid, #{ op => secure_store }, Context);
{error, _} = Error ->
Error
end
end;
{error, _} = Error ->
Error
end.
%% @doc Find all keys in the secure session, without access via the model access functions.
-spec secure_lookup( z:context() ) -> {ok, term()} | {error, no_session | not_found | term()}.
secure_lookup(Context) ->
case z_context:session_id(Context) of
{ok, Sid} ->
z_server_storage:secure_lookup(Sid, Context);
{error, _} = Error ->
Error
end.
%% @doc Find a key in the secure session, without access via the model access functions.
-spec secure_lookup( term(), z:context() ) -> {ok, term()} | {error, no_session | not_found | term()}.
secure_lookup(Key, Context) ->
case z_context:session_id(Context) of
{ok, Sid} ->
z_server_storage:secure_lookup(Sid, Key, Context);
{error, _} = Error ->
Error
end.
%% @doc Delete a key from the session, without access via the model access functions.
-spec secure_delete( term(), z:context() ) -> ok | {error, no_session | not_found | term()}.
secure_delete(Key, Context) ->
case z_context:session_id(Context) of
{ok, Sid} ->
case z_server_storage:secure_delete(Sid, Key, Context) of
ok ->
event(Sid, #{ op => secure_delete }, Context);
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end.
%% @doc Delete all keys from the session, without access via the model access functions.
-spec secure_delete( z:context() ) -> ok | {error, no_session | not_found | term()}.
secure_delete(Context) ->
case z_context:session_id(Context) of
{ok, Sid} ->
case z_server_storage:secure_delete(Sid, Context) of
ok ->
event(Sid, #{ op => secure_delete }, Context);
{error, _} = Error ->
Error
end;
{error, _} = Error ->
Error
end.
event(_Sid, _Payload, _Context) ->
% @todo first add some security checks to not leak the Sid, or find a method
% to publish to a client routing topic wihout using the Sid
% z_mqtt:publish([ <<"model">>, <<"server_storage">>, <<"event">>, Sid ], Payload, Context).
ok.