• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 1269

24 Dec 2025 07:53AM UTC coverage: 34.384% (+15.8%) from 18.633%
1269

Pull #4493

github

web-flow
Merge 9ad6f051e into d2fb99575
Pull Request #4493: Great invitations

0 of 564 new or added lines in 7 files covered. (0.0%)

4055 existing lines in 83 files now uncovered.

16097 of 46816 relevant lines covered (34.38%)

1102.16 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

100.0
/src/mod_invites_sql.erl
1
%%%----------------------------------------------------------------------
2
%%% File    : mod_invites_sql.erl
3
%%% Author  : Stefan Strigler <stefan@strigler.de>
4
%%% Created : Mon Sep 15 2025 by Stefan Strigler <stefan@strigler.de>
5
%%%
6
%%%
7
%%% ejabberd, Copyright (C) 2025 ProcessOne
8
%%%
9
%%% This program is free software; you can redistribute it and/or
10
%%% modify it under the terms of the GNU General Public License as
11
%%% published by the Free Software Foundation; either version 2 of the
12
%%% License, or (at your option) any later version.
13
%%%
14
%%% This program is distributed in the hope that it will be useful,
15
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
16
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
%%% General Public License for more details.
18
%%%
19
%%% You should have received a copy of the GNU General Public License along
20
%%% with this program; if not, write to the Free Software Foundation, Inc.,
21
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
%%%
23
%%%----------------------------------------------------------------------
24
-module(mod_invites_sql).
25

26
-author('stefan@strigler.de').
27

28
-behaviour(mod_invites).
29

30
-export([cleanup_expired/1, create_invite/1, expire_tokens/2, get_invite/2, get_invites/2, init/2,
31
         is_reserved/3, is_token_valid/3, list_invites/1, remove_user/2,
32
         set_invitee/5]).
33

34
-export([sql_schemas/0]).
35

36
-include("mod_invites.hrl").
37
-include("ejabberd_sql_pt.hrl").
38

39
%-define(I(B), binary_to_integer(B)).
40

41
%% @format-begin
42

43
%%--------------------------------------------------------------------
44
%%| mod_invite callbacks
45
init(Host, _Opts) ->
NEW
46
    ejabberd_sql_schema:update_schema(Host, ?MODULE, sql_schemas()).
6✔
47

48
sql_schemas() ->
NEW
49
    [#sql_schema{version = 1,
6✔
50
                 tables =
51
                     [#sql_table{name = <<"invite_token">>,
52
                                 columns =
53
                                     [#sql_column{name = <<"token">>, type = text},
54
                                      #sql_column{name = <<"username">>, type = text},
55
                                      #sql_column{name = <<"server_host">>, type = text},
56
                                      #sql_column{name = <<"invitee">>,
57
                                                  type = text,
58
                                                  default = true},
59
                                      #sql_column{name = <<"created_at">>,
60
                                                  type = timestamp,
61
                                                  default = true},
62
                                      #sql_column{name = <<"expires">>, type = timestamp},
63
                                      #sql_column{name = <<"type">>, type = {char, 1}},
64
                                      #sql_column{name = <<"account_name">>, type = text}],
65
                                 indices =
66
                                     [#sql_index{columns = [<<"token">>], unique = true},
67
                                      #sql_index{columns =
68
                                                     [<<"username">>, <<"server_host">>]}]}]}].
69

70
cleanup_expired(Host) ->
NEW
71
    NOW = sql_now(),
78✔
NEW
72
    {updated, Count} =
78✔
NEW
73
        ejabberd_sql:sql_query(Host, ?SQL("DELETE FROM invite_token WHERE expires < %(NOW)t")),
28✔
NEW
74
    Count.
78✔
75

76
create_invite(Invite) ->
NEW
77
    #invite_token{inviter = {User, Host},
222✔
78
                  token = Token,
79
                  account_name = AccountName,
80
                  created_at = CreatedAt0,
81
                  expires = Expires0,
82
                  type = Type0} =
83
        Invite,
NEW
84
    Type = enc_type(Type0),
222✔
NEW
85
    CreatedAt = CreatedAt0,
222✔
NEW
86
    Expires = Expires0,
222✔
87

NEW
88
    Query =
222✔
NEW
89
        ?SQL_INSERT("invite_token",
222✔
90
                    ["token=%(Token)s",
91
                     "username=%(User)s",
92
                     "server_host=%(Host)s",
93
                     "type=%(Type)s",
94
                     "created_at=%(CreatedAt)t",
95
                     "expires=%(Expires)t",
96
                     "account_name=%(AccountName)s"]),
NEW
97
    {updated, 1} = ejabberd_sql:sql_query(Host, Query),
222✔
NEW
98
    Invite.
222✔
99

100
expire_tokens(User, Server) ->
NEW
101
    NOW = sql_now(),
78✔
NEW
102
    {updated, Count} =
78✔
103
        ejabberd_sql:sql_query(Server,
NEW
104
                               ?SQL("UPDATE invite_token SET expires = '1970-01-01 00:00:01' WHERE "
78✔
105
                                    "username = %(User)s AND %(Server)H AND expires > %(NOW)t AND "
106
                                    "type != 'R'")),
NEW
107
    Count.
78✔
108

109
get_invite(Host, Token) ->
NEW
110
    case ejabberd_sql:sql_query(Host,
336✔
NEW
111
                                ?SQL("SELECT @(username)s, @(invitee)s, @(type)s, @(account_name)s, "
336✔
112
                                     "@(expires)t, @(created_at)t FROM invite_token WHERE token = "
113
                                     "%(Token)s AND %(Host)H"))
114
    of
115
        {selected, [{User, Invitee, Type, AccountName, Expires, CreatedAt}]} ->
NEW
116
            #invite_token{token = Token,
324✔
117
                          inviter = {User, Host},
118
                          invitee = Invitee,
119
                          type = dec_type(Type),
120
                          account_name = AccountName,
121
                          expires = Expires,
122
                          created_at = CreatedAt};
123
        {selected, []} ->
NEW
124
            {error, not_found}
12✔
125
    end.
126

127
get_invites(Host, {User, _Host}) ->
NEW
128
    {selected, Invites} =
42✔
129
        ejabberd_sql:sql_query(Host,
NEW
130
                               ?SQL("SELECT @(token)s, @(invitee)s, @(type)s, @(account_name)s, "
42✔
131
                                    "@(expires)t, @(created_at)t FROM invite_token WHERE %(Host)H "
132
                                    "AND username = %(User)s")),
NEW
133
    lists:map(fun({Token, Invitee, Type, AccountName, Expires, CreatedAt}) ->
42✔
NEW
134
                 #invite_token{token = Token,
66✔
135
                               inviter = {User, Host},
136
                               invitee = Invitee,
137
                               type = dec_type(Type),
138
                               account_name = AccountName,
139
                               expires = Expires,
140
                               created_at = CreatedAt}
141
              end,
142
              Invites).
143

144
is_reserved(Host, Token, User) ->
NEW
145
    NOW = sql_now(),
210✔
NEW
146
    {selected, [{Count}]} =
210✔
147
        ejabberd_sql:sql_query(Host,
NEW
148
                               ?SQL("SELECT @(COUNT(*))d FROM invite_token WHERE %(Host)H AND token != %(Token)s AND "
210✔
149
                                    "account_name = %(User)s AND invitee = '' AND expires > %(NOW)t")),
NEW
150
    Count > 0.
210✔
151

152
is_token_valid(Host, Token, {User, Host}) ->
NEW
153
    NOW = sql_now(),
366✔
NEW
154
    {selected, Rows} =
366✔
155
        ejabberd_sql:sql_query(Host,
NEW
156
                               ?SQL("SELECT @(token)s FROM invite_token WHERE %(Host)H AND token = %(Token)s AND "
366✔
157
                                    "invitee = '' AND expires > %(NOW)t AND (%(User)s = '' OR username = %(User)s)")),
NEW
158
    case Rows /= [] of
366✔
159
        true ->
NEW
160
            true;
246✔
161
        false ->
NEW
162
            case get_invite(Host, Token) of
120✔
163
                {error, not_found} ->
NEW
164
                    throw(not_found);
12✔
165
                _ ->
NEW
166
                    false
108✔
167
            end
168
    end.
169

170
list_invites(Host) ->
NEW
171
    {selected, Rows} =
6✔
172
        ejabberd_sql:sql_query(Host,
NEW
173
                               ?SQL("SELECT @(token)s, @(username)s, @(type)s, @(account_name)s, "
6✔
174
                                    "@(expires)t, @(created_at)t FROM invite_token WHERE %(Host)H")),
NEW
175
    lists:map(fun({Token, User, Type, AccountName, Expires, CreatedAt}) ->
6✔
NEW
176
                 #invite_token{token = Token,
12✔
177
                               inviter = {User, Host},
178
                               type = dec_type(Type),
179
                               account_name = AccountName,
180
                               expires = Expires,
181
                               created_at = CreatedAt}
182
              end,
183
              Rows).
184

185
remove_user(User, Server) ->
NEW
186
    ejabberd_sql:sql_query(Server,
120✔
NEW
187
                           ?SQL("DELETE FROM invite_token WHERE username=%(User)s AND %(Server)H")).
120✔
188

189
set_invitee(Fun, Host, Token, Invitee, AccountName) ->
NEW
190
    Trans =
90✔
191
        fun() ->
NEW
192
           {updated, 1} =
90✔
NEW
193
               ejabberd_sql:sql_query_t(?SQL("UPDATE invite_token SET invitee = %(Invitee)s, account_name = %(AccountName)s WHERE "
90✔
194
                                             "%(Host)H AND token = %(Token)s AND invitee = '' AND (type != 'R' OR account_name = '' OR %(AccountName)s = '')")),
NEW
195
           ok = Fun()
72✔
196
        end,
NEW
197
    case ejabberd_sql:sql_transaction(Host, Trans) of
90✔
198
        {atomic, Res} ->
NEW
199
            Res;
66✔
200
        {aborted, {badmatch, {updated, 0}}} ->
NEW
201
            {error, conflict};
18✔
202
        {aborted, {badmatch, {error, _Res} = Error}} ->
NEW
203
            Error
6✔
204
    end.
205

206
%%--------------------------------------------------------------------
207
%%| helpers
208
sql_now() ->
NEW
209
    calendar:local_time().
732✔
210

211
enc_type(roster_only) ->
NEW
212
    <<"R">>;
42✔
213
enc_type(account_subscription) ->
NEW
214
    <<"S">>;
18✔
215
enc_type(account_only) ->
NEW
216
    <<"A">>.
162✔
217

218
dec_type(<<"R">>) ->
NEW
219
    roster_only;
54✔
220
dec_type(<<"S">>) ->
NEW
221
    account_subscription;
18✔
222
dec_type(<<"A">>) ->
NEW
223
    account_only.
330✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc