Maxim Sokhatsky 12 years ago
commit
16a3f7c185

+ 25 - 0
include/accounts.hrl

@@ -0,0 +1,25 @@
+-type currency()         :: internal | quota | game_points | money | bonus.
+-type account_id()       :: {string(), currency()}. %% {username, currency}.
+-type transaction_id()   :: string().
+-type transaction_info() :: #tx_payment{} | #tx_admin_change{} | #tx_default_assignment{}.
+
+-record(account, {
+        id :: account_id(),
+        debet       :: integer(),
+        credit      :: integer(),
+        last_change :: integer() }).
+
+-record(tx_payment,{ id :: integer() }).
+-record(tx_admin_change,{ reason :: binary() }).
+-record(tx_default_assignment,{ }).
+-record(user_transaction, {user,top}).
+-record(transaction, {
+        id :: transaction_id(),
+        commit_time :: erlang:now(),
+        amount :: integer(),    %% amount to move between accounts
+        remitter :: account_id(), %% accout that gives money/points
+        acceptor :: account_id(), %% account receive money/points
+        currency :: currency(),   %% some of the points or money
+        info :: transaction_info(),
+        next,
+        prev }).

+ 13 - 0
include/acls.hrl

@@ -0,0 +1,13 @@
+-record(acl, {id,
+        resource,
+        top}).
+
+-record(acl_entry, {id,
+        entry_id,
+        acl_id,
+        accessor,
+        action,
+        next,
+        prev}).
+
+-record(feature, {name, id, aclver}).

+ 9 - 0
include/attachment.hrl

@@ -0,0 +1,9 @@
+-record(uploads, {key, counter}).
+-record(attachment, {
+        id, 
+        name,
+        type,
+        file,
+        thumb,
+        owner,
+        create_data}).

+ 93 - 0
include/feeds.hrl

@@ -0,0 +1,93 @@
+-record(feed, {
+        id,
+        top,
+        aclver}).
+
+-record(entry, {
+        id, %% {entry_id, feed_id}
+        entry_id, %% taken from id_seq
+        feed_id, %% owner's feed_id, indexed field
+        from, %% author
+        to,
+        description,
+        raw_description,
+        created_time,
+        hidden,
+        access,
+        shared,
+        starred,
+        deleted,
+        likes,
+        likes_count,
+        comments,
+        comments_rear,
+        comments_count,
+        media = [], %% for oembed
+        etc,        %% field to link additional info
+        type = {user, normal},
+        next,
+        prev}).
+
+-record(id_seq, {thing, %% feed, user, group, ...
+                 id = 100000}).
+
+-record(media, {
+        id,
+        title :: iolist(),
+        width,
+        height,
+        html :: iolist(),
+        url :: iolist(),
+        version,
+        thumbnail_url :: iolist(),
+        type :: {atom(), atom() | string()},
+        thumbnail_height}).
+
+-record(comment, {
+        id,          %% {comment_id, entry_id}
+        comment_id,  %% generowane przez id_seq
+        entry_id,    %% index
+        raw_content, %% raw text of comment
+        content,     %% text of comment
+        author_id,
+        create_time,
+        media = [],  %% for oembed
+        parent,
+        comments,
+        comments_rear,
+        next,
+        prev }).
+
+
+-record(entry_likes, {
+        entry_id,       % this is a general entry_id. Every same entry in different feeds has the same id
+        one_like_head,  % this is a head for linked list of {user, time} tupples
+        total_count     % it's easier to keep it than count
+        }).
+
+-record(user_likes, {
+        user_id,
+        one_like_head,
+        total_count
+        }).
+
+-record(one_like, {
+        id,              % just a number
+        user_id,        % who likes
+        entry_id,       % what
+        feed_id,        % where
+        created_time,   % when
+        next            
+        }).
+
+-record(hidden_feed, {id}).
+
+% Statistics. We have to keep count of user entries and comments. 
+% Gathering it the old way will work very ineffective with more users to come.
+% And comments from user record are somehow always undefined. Either it fails, or it is used somewhere else
+
+-record(user_etries_count, {
+        user_id,    % user id
+        entries = 0,    % number of entries
+        comments = 0   % number of comments
+    }).

+ 24 - 0
include/groups.hrl

@@ -0,0 +1,24 @@
+-include("types.hrl").
+
+-record(group,{
+        username,  % this is an id, has nothing to do with users or name
+        name,
+        description,
+        publicity,
+        creator,
+        created,
+        owner,
+        feed,
+        users_count = 0 :: integer(),   % we have to store this, counting would be very expensive and this number is sufficient for sorting and stuff
+        entries_count = 0 :: integer()  
+        }).
+
+-record(group_subscriptions, {
+        user_id,
+        group_id,
+        user_type,
+        user_posts_count = 0 :: integer() % we need this for sorting and counting is expensive
+        }).
+
+define(GROUP_EXCHANGE(GroupId), list_to_binary("group_exchange."++GroupId++".fanout")).
+

+ 25 - 0
include/invites.hrl

@@ -0,0 +1,25 @@
+-include("types.hrl").
+
+-record(invite_code, {
+        code,
+        create_date,
+        issuer :: username_type() | '_', %% Dialyzer and record MatchSpec warnings http://j.mp/vZ8670
+        recipient,
+        next,
+        prev,
+        created_user :: username_type() | '_'}).
+
+-record(invite_by_issuer, {
+        user,
+        top}).
+
+-record(invitation_tree, {
+        user,                 % user id
+        parent,               % parent user id
+        next_sibling = none,  % user id of next child of the same parent
+        first_child  = none,  % link to the children list
+        invite_code,          % invite code, for this user
+        children = empty      % can be filled in traversal
+        }).
+
+-define(INVITATION_TREE_ROOT, {root}).

+ 33 - 0
include/log.hrl

@@ -0,0 +1,33 @@
+-define(LOG(Format, Args, Level, Tags), error_logger:info_msg("~p:~p " ++ Format,[?MODULE,?LINE] ++ Args)).
+
+-define(DBG(Format, Args, Tag), ?LOG(Format, Args, ?debug, Tag)).
+-define(DBG(Format, Args),      ?DBG(Format, Args, [])).
+-define(DBG(Format),            ?DBG(Format, [])).
+
+-define(INFO(Format, Args, Tag), ?LOG(Format, Args, ?info, Tag)).
+-define(INFO(Format, Args),      ?INFO(Format, Args, [])).
+-define(INFO(Format),            ?INFO(Format, [])).
+
+-define(NOTICE(Format, Args, Tag), ?LOG(Format, Args, ?notice, Tag)).
+-define(NOTICE(Format, Args),      ?NOTICE(Format, Args, [])).
+-define(NOTICE(Format),            ?NOTICE(Format, [])).
+
+-define(WARNING(Format, Args, Tag), ?LOG(Format, Args, ?warning, Tag)).
+-define(WARNING(Format, Args),      ?WARNING(Format, Args, [])).
+-define(WARNING(Format),            ?WARNING(Format, [])).
+
+-define(ERROR(Format, Args, Tag), ?LOG(Format, Args, ?error, Tag)).
+-define(ERROR(Format, Args),      ?ERROR(Format, Args, [])).
+-define(ERROR(Format),            ?ERROR(Format, [])).
+
+-define(CRITICAL(Format, Args, Tag), ?LOG(Format, Args, ?critical, Tag)).
+-define(CRITICAL(Format, Args),      ?CRITICAL(Format, Args, [])).
+-define(CRITICAL(Format),            ?CRITICAL(Format, [])).
+
+-define(ALERT(Format, Args, Tag), ?LOG(Format, Args, ?alert, Tag)).
+-define(ALERT(Format, Args),      ?ALERT(Format, Args, [])).
+-define(ALERT(Format),            ?ALERT(Format, [])).
+
+-define(EMERGENCY(Format, Args, Tag), ?LOG(Format, Args, ?emergency, Tag)).
+-define(EMERGENCY(Format, Args),      ?EMERGENCY(Format, Args, [])).
+-define(EMERGENCY(Format),            ?EMERGENCY(Format, [])).

+ 38 - 0
include/meetings.hrl

@@ -0,0 +1,38 @@
+-record(team, {
+        name, % { team name for now will bu just first player username }
+        id,
+        play_record, % { linked list history of played users under that ticket }
+        type }).
+
+-record(meeting, {
+        name, % { tournament name }
+        id,
+        game_type,
+        description,
+        creator,
+        created,
+        start_date,
+        start_time,
+        end_date,
+        status, % { activated, ongoing, finished, canceled }
+        quota,
+        tours,
+        awards, 
+        winners :: list(), % [{UserId, Position, GiftId}]
+        waiting_queue, % { play_record, added here when user wants to join tournament }
+        avatar,
+        owner,
+        players_count,
+        speed,
+        type,
+        game_mode }). % { eliminatin, pointing, etc }
+
+-record(play_record, { % { tournament_player, game_record, tournament_info, choose your name :) }
+        who, % { user }
+        tournament, % { tournament in which user played }
+        team, % { team under which user player tournament }
+        game_id, % { game id that user played under that team }
+        realname,
+        points, % [{money,Point},{bonus,Points}]
+        other }).
+

+ 62 - 0
include/membership_packages.hrl

@@ -0,0 +1,62 @@
+-type payment_type():: credit_card | mobile | paypal | wire_transfer | facebook.
+-type purchase_state() :: added | done | cancelled | pending | untracked.
+
+-record(basket, {
+        user_id                    ::any(),
+        package_id                 ::any()}).
+
+-record(membership_package, {
+        id                         ::any(),      % package id
+        payment_type               ::payment_type(),
+        no                         ::integer(),  % package number (need to display in pricelist)
+        amount                     ::integer(),  % price
+        deducted_for_gifts         ::integer(),  % kakush currency charge
+        net_membership             ::integer(),  % net membership fee
+        available_for_sale = false ::boolean(),  % not used yet
+        quota::integer()                         % game quota
+        }).
+
+-record(state_change, {
+        time     :: erlang:now(),
+        state    :: any(),
+        info     :: any()}).
+
+-record(membership_purchase, {
+        id                    :: any(),
+        external_id           :: any(),     % id of the purchase in external payment system if any
+        user_id               :: any(),
+        state                 :: purchase_state(),
+        membership_package    :: #membership_package{},
+        next                  :: any(),
+        prev                  :: any(),
+        start_time            :: erlang:now(),
+        end_time              :: erlang:now(),
+        state_log = []        :: [#state_change{}],
+        info                  :: any()      % payment-specific info about purchase if any
+        }).
+
+-record(user_purchase, {
+        user :: any(),
+        top           :: any()
+        }).
+
+-define(MP_STATE_ADDED,     added).
+-define(MP_STATE_DONE,      done).
+-define(MP_STATE_CANCELLED, cancelled).
+-define(MP_STATE_UNTRACKED, untracked).
+-define(MP_STATE_PENDING,   pending).
+-define(MP_STATE_FAILED,    failed).
+-define(MP_STATE_UNKNOWN,   unknown).
+-define(MP_STATE_CONFIRMED, confirmed).
+-define(MP_STATE_UNEXPECTED, unexpected).
+-define(MP_STATE_DISCARDED, discarded).
+-define(MP_MONTHLY_LIMIT_MULTIPLIER, 3).
+
+-record(pi_credit_card, {
+        cardholder_name,
+        cardholder_surname,
+        cardnumber_masked,
+        retref_num,
+        prov_date,
+        auth_code
+        }).

+ 10 - 0
include/types.hrl

@@ -0,0 +1,10 @@
+-ifndef(TYPES_HRL).
+-define(TYPES_HRL, true).
+
+-type username_type() :: string().
+-type id_type() :: integer().
+-type user_state() :: 'ok' | 'not_verified' | 'banned'.
+-type proplist() :: [proplists:property()].
+
+
+-endif.

+ 106 - 0
include/users.hrl

@@ -0,0 +1,106 @@
+-include("types.hrl").
+
+-record(user, {
+        username :: username_type() | '_', %% Dialyzer and record MatchSpec warnings http://j.mp/vZ8670
+        password,
+        facebook_id,
+        twitter_id,
+        email,
+        avatar,
+        name = undefined,
+        surname = undefined,
+        age,
+        sex,
+        location,
+        education,
+        register_date,
+        status = 'not_verified' :: user_state() | '_',
+        verification_code :: string() | '_',
+        type,
+        feed,
+        direct,
+        starred,
+        pinned,
+        comments,
+        discussions,
+        team,
+        aclver}).
+
+-record(user_status,{
+        username :: username_type(),
+        last_login,
+        show_splash = true :: boolean()
+        }).
+
+-record(user_info,{
+        username :: username_type(),
+        name,
+        surname,
+        age,
+        avatar_url,
+        sex,
+        skill = 0 :: integer(),
+        score = 0 :: integer()}).
+
+-record(user_address, {
+        username :: username_type(),
+        address = "",
+        city = "",
+        district = "",
+        postal_code = "",
+        phone = "",
+        personal_id = ""
+        }).
+
+-record(user_type,{
+        id,
+        aclver}).
+
+-record(subsctiptioins,{
+        {who,
+         whom}).
+
+-record(forget_password, {
+        token :: string(),
+        uid   :: string(),
+        create :: {integer(), integer(), integer()}}).
+
+-record(prohibited,{
+        ip        :: {string(), atom()},
+        activity  :: any(),
+        time      :: {integer(),integer(),integer()},
+        uid = undefined :: 'undefined' | string()}).
+
+-record(avatar,{
+        big :: string(),
+        small :: string(),
+        tiny :: string()}).
+
+-record(user_game_status,{
+        user,
+        status  %% strings: online|offline|busy|free_for_game|invisible
+        }).
+
+-record(user_ignores, {who, whom}).
+-record(user_ignores_rev, {whom, who}).
+
+-record(user_bought_gifts, {
+        username,
+        timestamp,
+        gift_id
+    }).
+
+-record(user_count, {count}).
+-record(twitter_oauth, {user_id, token, secret}).
+-record(facebook_oauth, {user_id, access_token}).
+
+-define(ACTIVE_USERS_TOP_N, 12).
+
+-record(active_users_top, {
+        no,
+        user_id,
+        entries_count,
+        last_one_timestamp
+        }).
+
+-define(USER_EXCHANGE(UserId), list_to_binary("user_exchange."++UserId++".fanout")).

+ 5 - 0
rebar.config

@@ -0,0 +1,5 @@
+{lib_dirs, ["..","/mnt/glusterfs/apps/riak_bin"]}.
+{deps_dir, ["deps"]}.
+{deps,[
+    {mqs,        ".*",    {git, "git@github.com:synrc/white_rabbit.git", "master"}}
+]}.

+ 165 - 0
src/accounts.erl

@@ -0,0 +1,165 @@
+-module(accounts).
+-include("accounts.hrl").
+-include("membership_packages.hrl").
+-include("log.hrl").
+
+-export([debet/2, credit/2, balance/2, create_account/1, create_account/2]).
+-export([transaction/4, transaction/5]).
+-export([check_quota/1, check_quota/2]).
+-export([user_paid/1]).
+
+-spec transaction(string(), currency(), integer(), transaction_info()) -> {ok, transaction_id()} | {error, term()}.
+transaction(Account, Currency, 0, TransactionInfo) -> 
+    ?WARNING("zero transaction: Account=~p, Currency=~p,TransactionInfo=~p", [Account, Currency, TransactionInfo]), 
+    ok;
+transaction(Account, Currency, Amount, TransactionInfo) when Amount /= 0->
+    {Remitter, Acceptor} = if Amount > 0 -> {?SYSTEM_ACCOUNT_ID, Account};
+                                    true -> {Account, ?SYSTEM_ACCOUNT_ID} end,
+    transaction(Remitter, Acceptor, Currency, abs(Amount), TransactionInfo).
+
+%% @doc Add new transaction, will change state of accouts. All transactions
+%%      are between user account and system account. So  moving of the funds
+%%      (money, kaush, game_points, etc.) should be performed with transactions.
+-spec transaction(Remitter::string(), Acceptor::string(), Currency::currency(), Amount::integer(), 
+                  TransactionInfo::transaction_info()) -> {ok, transaction_id()} | {error, any()}.
+transaction(Remitter, Acceptor, Currency, Amount, TransactionInfo) ->
+    TX = #transaction{id = generate_id(), acceptor = Acceptor, remitter = Remitter,
+                      amount = Amount, commit_time = now(), currency = Currency, info = TransactionInfo},
+    commit_transaction(TX).
+
+%% @doc How many funds got from the start of the time.
+-spec debet(account_id(), currency()) -> {ok, integer()} | {error, term()}.
+debet(Account, Currency) ->
+    case get_account(Account, Currency) of
+         #account{debet = Debet} -> {ok, Debet};
+         Error -> Error
+    end.
+
+%% @doc How many funds spent  from the start of the time.
+-spec credit(account_id(), currency()) -> {ok, integer()} | {error, term()}.
+credit(AccountId, Currency) ->
+    case get_account(AccountId, Currency) of
+         #account{credit = Credit} -> {ok, Credit};
+         Error -> Error
+    end.
+
+%% @doc balance of the given account by given currency
+-spec balance(account_id(), currency()) -> {ok, integer()} | {error, term()}.
+balance(AccountId, Currency) ->
+    case get_account(AccountId, Currency) of
+         #account{debet = Debet, credit = Credit} -> {ok, Debet - Credit};
+         Error -> Error
+    end.
+
+%% @doc Create account with for all supported currencies.
+-spec create_account(Username::string()|system) -> ok | {error, term()}.
+create_account(AccountId) ->
+    Currencies = get_currencies(),
+    try [{ok, Currency} = {create_account(AccountId, Currency), Currency} || Currency <- Currencies]
+    catch _:_ -> {error, unable_create_account} end.
+
+%% @doc Create account for specified currency.
+create_account(AccountId, Currency) ->
+    Account = #account{id = ?ACC_ID(AccountId, Currency),
+                       credit = 0, debet = 0, last_change = 0},
+
+    case store:put(Account) of
+         ok -> ok;
+         Error -> ?ERROR("create_account: put to db error: ~p", [Error]),
+                  {error, unable_to_store_account}
+    end.
+
+%% @doc Check quota balance against hard and soft limits
+-spec check_quota(Username::string()) -> ok | {error, soft_limit} | {error, hard_limit}.
+
+check_quota(User) ->
+    check_quota(User, 0).
+
+%% @doc Check quota balance against hard and soft limit. Second argument is
+%%      Amount planning to be cherged from user.
+-spec check_quota(User::string(), Amount::integer()) -> ok | {error, soft_limit} | {error, hard_limit}.
+
+check_quota(User, Amount) ->
+    SoftLimit = store:get_config("accounts/quota_limit/soft",  -20),
+    {ok, Balance} = balance(User, quota),
+    BalanceAfterChange = Balance - Amount,
+    if
+        BalanceAfterChange > SoftLimit ->
+            ok;
+        true ->
+            HardLimit = store:get(config, "accounts/quota_limit/hard",  -100),
+            if
+                BalanceAfterChange =< HardLimit ->
+                    {error, hard_limit};
+                true ->
+                    {error, soft_limit}
+            end
+    end.
+
+commit_transaction(#transaction{remitter = R, acceptor = A,  currency = Currency, amount = Amount} = TX) ->
+    case change_accounts(R, A, Currency, Amount) of
+         ok -> nsx_msg:notify_transaction(R,TX),
+               nsx_msg:notify_transaction(A,TX);
+         Error -> 
+            case TX#transaction.info of
+                #ti_game_event{} ->
+                    nsx_msg:notify_transaction(R,TX),
+                    nsx_msg:notify_transaction(A,TX);
+                _ ->
+                    ?ERROR("commit transaction error: change accounts ~p", [Error]),
+                    Error
+            end
+    end.
+
+change_accounts(Remitter, Acceptor, Currency, Amount) ->
+    case {get_account(Remitter, Currency), get_account(Acceptor, Currency)} of
+        {RA = #account{}, AA = #account{}}  ->
+            ?INFO("transacrion: RemitterAccount ~p, AcceptorAccount: ~p", [RA, AA]),
+            %% check balance for remitter according to currency and amount
+            case check_remitter_balance(RA, Amount) of
+                %% all ok write changes
+                ok -> %% increase credit of remmitter, last change is less then zero
+                    RA1 = RA#account{credit = RA#account.credit + Amount,
+                    last_change = -Amount },
+                    %% increase debet of acceptor, last change is positive
+                    AA1 = AA#account{debet = AA#account.debet + Amount,
+                    last_change = Amount},
+                    store:put([AA1, RA1]);
+                {error, Reason} ->
+                    {error, {remitter_balance, Reason}}
+               end;
+        {{error, Reason}, #account{}} ->
+            {error, {remitter_account_unavailable, Reason}};
+        {#account{}, {error, Reason}} ->
+            {error, {acceptor_account_unavailable, Reason}};
+        {RE, AE} ->
+            {error, both_accounts_unavailable}
+    end.
+
+check_remitter_balance(#account{id = ?ACC_ID(?SYSTEM_ACCOUNT_ID, _)}, _) -> ok;
+check_remitter_balance(_Account, _Amount) -> ok.
+
+get_account(Account, Currency) ->
+    case store:get(account, ?ACC_ID(Account, Currency)) of
+         {ok, #account{} = AR} -> AR;
+         _ -> {error, account_not_found}
+    end.
+
+get_currencies() -> [?CURRENCY_GAME_POINTS,
+                     ?CURRENCY_KAKUSH_CURRENCY,
+                     ?CURRENCY_KAKUSH,
+                     ?CURRENCY_QUOTA,
+                     ?CURRENCY_MONEY].
+
+generate_id() ->
+    {MegSec, Sec, MicroSec} = now(),
+    H = erlang:phash2(make_ref()),
+    lists:concat([MegSec*1000000000000, Sec*1000000, MicroSec, "-", H]).
+
+user_paid(UId) ->
+    {_, UP} = store:get(user_purchase, UId),
+    case UP of
+        notfound -> false;
+        #user_purchase{top = undefined} -> false;
+        _ -> true
+    end.

+ 94 - 0
src/acls.erl

@@ -0,0 +1,94 @@
+-module(acls).
+-author('Vladimir Baranov <baranoff.vladimir@gmail.com>').
+-compile(export_all).
+
+-include("acls.hrl").
+-include("users.hrl").
+-include("groups.hrl").
+-include("feeds.hrl").
+
+define_access(default  = Accessor, Resource, Action) -> do_define_access(Accessor, Resource, Action);
+define_access({user, _Username} = Accessor, Resource, Action) -> do_define_access(Accessor, Resource, Action);
+define_access({user_type, _Usertype} = Accessor, Resource, Action) -> do_define_access(Accessor, Resource, Action);
+define_access({ip, _Ip} = Accessor, Resource, Action) -> do_define_access(Accessor, Resource, Action).
+
+do_define_access(Accessor, Resource, Action) -> store:acl_add_entry(select_type(Resource), Accessor, Action).
+
+check(Keys) ->
+    Acls = [Acl || {ok, Acl = #acl_entry{}} <-
+                       [store:get(acl_entry, Key) || Key <- Keys]],
+
+    case Acls of
+        [] -> none;
+        [#acl_entry{action = Action} | _] ->
+            Action
+    end.
+
+check_access(#user{username = UId, type = UType}, #feed{id = FId}) ->
+    Feed = {feed, FId},
+    Query = [ {{user, UId}, Feed},
+              {{user_type, UType}, Feed},
+              {default, Feed}],
+    check(Query);
+
+check_access(#user{username = UId, type = UType}, #group{username = GId}) ->
+    Group = {group, GId},
+    Query = [ {{user, UId}, Group},
+              {{user_type, UType}, Group},
+              {default, Group}],
+    check(Query);
+
+
+check_access(#user{username = AId, type = AType}, #user{username = RId}) ->
+    User = {user, RId},
+    Query = [ {{user, AId}, User},
+              {{user_type, AType}, User},
+              {default, User} ],
+    check(Query);
+
+check_access({user_type, Type}, #user{username = RId}) ->
+    User = {user, RId},
+    Query = [ {{user_type, Type}, User},
+              {default, User} ],
+    check(Query);
+
+check_access({user_type, Type}, #feed{id = FId}) ->
+    Feed = {feed, FId},
+    Query = [ {{user_type, Type}, Feed},
+              {default, Feed} ],
+    check(Query);
+
+check_access({user_type, Type}, #group{username = GId}) ->
+    Group = {group, GId},
+    Query = [{{user_type, Type}, Group},
+             {default, Group}],
+    check(Query);
+
+check_access({ip, _Ip} = Accessor, {feature, _Feature} = Resource) ->
+    Query = [{Accessor, Resource},
+             {default, Resource}],
+    check(Query);
+
+check_access(#user{username = AId, type = AType}, {feature, _Feature} = R) ->
+    Query = [ {{user, AId}, R},
+              {{user_type, AType}, R},
+              {default, R} ],
+    check(Query);
+
+%% for testing purposes
+check_access(UId, {feature, _Feature} = Resource) ->
+    case users:get_user(UId) of
+        {ok, User} ->
+            check_access(User, Resource);
+        E ->
+            E
+    end.
+
+
+select_type(#user{username = UId}) -> {user, UId};
+select_type(#group{username = GId}) -> {group, GId};
+select_type(#feed{id = FId}) -> {feed, FId};
+select_type({user, UId}) -> {user, UId};
+select_type({group, name = GId}) -> {group, GId};
+select_type({feed, FId}) -> {feed, FId};
+select_type({feature, Feature}) ->  {feature, Feature}.

+ 15 - 0
src/comments.erl

@@ -0,0 +1,15 @@
+-module(comments).
+-include("feeds.hrl").
+-compile(export_all).
+
+add(EId, User, Value) ->  add(EId, User, Value, []).
+add(EId, User, Value, Medias) ->
+    CId = store:next_id("comment"),
+    R = #comment{id = {CId, EId}, comment_id = CId, entry_id = EId,
+                 content = Value, author_id = User, media = Medias, create_time = now() },
+    case store:put(R) of
+        ok -> {ok, R};
+        A -> A end.
+
+select_by_entry_id(EntryId) ->
+    store:comments_by_entry(EntryId).

+ 234 - 0
src/feeds.erl

@@ -0,0 +1,234 @@
+-module(feeds).
+-compile(export_all).
+-include("feeds.hrl").
+-include("users.hrl").
+-include("groups.hrl").
+-include("log.hrl").
+
+create() ->
+    FId = store:next_id("feed", 1),
+    ok = store:put(#feed{id = FId} ),
+    FId.
+
+add_direct_message(FId, User, Desc) -> add_direct_message(FId, User, utils:uuid_ex(), Desc).
+add_direct_message(FId, User, EntryId, Desc) -> add_direct_message(FId, User, undefined, EntryId, Desc, []).
+add_direct_message(FId, User, To, EntryId, Desc, Medias) -> store:feed_add_direct_message(FId, User, To, EntryId, Desc, Medias).
+add_group_entry(FId, User, EntryId, Desc, Medias) -> store:feed_add_entry(FId, User, EntryId, Desc, Medias).
+add_group_entry(FId, User, To, EntryId, Desc, Medias, Type) -> store:feed_add_entry(FId, User, To, EntryId, Desc, Medias, Type, "").
+add_entry(FId, User, EntryId, Desc) -> add_entry(FId, User, EntryId, Desc, []).
+add_entry(FId, User, EntryId, Desc, Medias) -> store:feed_add_entry(FId, User, EntryId, Desc, Medias).
+add_entry(FId, User, To, EntryId, Desc, Medias, Type) -> store:feed_add_entry(FId, User, To, EntryId, Desc, Medias, Type, "").
+add_shared_entry(FId, User, To, EntryId, Desc, Medias, Type, SharedBy) -> store:feed_add_entry(FId, User, To, EntryId, Desc, Medias, Type, SharedBy).
+
+add_like(Fid, Eid, Uid) ->
+    Write_one_like = fun(Next) ->
+        Self_id = store:next_id("one_like", 1),   
+        store:put(#one_like{    % add one like
+            id = Self_id,
+            user_id = Uid,
+            entry_id = Eid,
+            feed_id = Fid,
+            created_time = now(),
+            next = Next
+        }),
+        Self_id
+    end,
+    % add entry - like
+    case store:get(entry_likes, Eid) of
+        {ok, ELikes} -> 
+            store:put(ELikes#entry_likes{
+                one_like_head = Write_one_like(ELikes#entry_likes.one_like_head), 
+                total_count = ELikes#entry_likes.total_count + 1
+            });
+        {error, notfound} ->
+            store:put(#entry_likes{
+                entry_id = Eid,                
+                one_like_head = Write_one_like(undefined),
+                total_count = 1
+            })
+    end,
+    % add user - like
+    case store:get(user_likes, Uid) of
+        {ok, ULikes} -> 
+            store:put(ULikes#user_likes{
+                one_like_head = Write_one_like(ULikes#user_likes.one_like_head),
+                total_count = ULikes#user_likes.total_count + 1
+            });
+        {error, notfound} ->
+            store:put(#user_likes{
+                user_id = Uid,                
+                one_like_head = Write_one_like(undefined),
+                total_count = 1
+            })
+    end.
+
+% statistics
+
+get_entries_count(Uid) ->
+    case store:get(user_etries_count, Uid) of
+        {ok, UEC} -> 
+            UEC#user_etries_count.entries;
+        {error, notfound} ->
+            0
+    end.
+
+get_comments_count(Uid) ->
+    case store:get(user_etries_count, Uid) of
+        {ok, UEC} -> 
+            UEC#user_etries_count.comments;
+        {error, notfound} ->
+            0
+    end.
+
+get_feed(FId) -> store:get(feed, FId).
+get_entries_in_feed(FId) -> store:entries_in_feed(FId).
+get_entries_in_feed(FId, Count) -> store:entries_in_feed(FId, Count).
+get_entries_in_feed(FId, StartFrom, Count) -> store:entries_in_feed(FId, StartFrom, Count).
+get_direct_messages(FId, Count) -> store:entries_in_feed(FId, undefined, Count).
+get_direct_messages(FId, StartFrom, Count) -> store:entries_in_feed(FId, StartFrom, Count).
+get_entries_in_feed(FId, StartFrom, Count, FromUserId)-> Entries = store:entries_in_feed(FId, StartFrom, Count),
+    [E || #entry{from = From} = E <- Entries, From == FromUserId].
+
+create_message(Table) ->
+    EId = store:next_id("entry", 1),
+    #entry{id = {EId, system_info},
+        entry_id = EId,
+        from = system,
+        type = {system, new_table},
+        created_time = now(),
+        description = Table}.
+
+
+remove_entry(FeedId, EId) ->
+    {ok, #feed{top = TopId} = Feed} = get_feed(FeedId),
+
+    case store:get(entry, {EId, FeedId}) of
+        {ok, #entry{prev = Prev, next = Next}}->
+            ?INFO("P: ~p, N: ~p", [Prev, Next]),
+            case store:get(entry, Next) of
+                {ok, NE} -> store:put(NE#entry{prev = Prev});
+                _ -> ok
+            end,
+            case store:get(entry, Prev) of
+                {ok, PE} -> store:put(PE#entry{next = Next});
+                _ -> ok
+            end,
+
+            case TopId of
+                {EId, FeedId} -> store:put(Feed#feed{top = Prev});
+                _ -> ok
+            end;
+        {error, notfound} -> ?INFO("Not found"), ok
+    end,
+    store:delete(entry, {EId, FeedId}).
+
+edit_entry(FeedId, EId, NewDescription) ->
+    case store:entry_by_id({EId, FeedId}) of
+        {ok, OldEntry} ->
+            NewEntryRaw =  OldEntry#entry{description = NewDescription,
+                                          raw_description = NewDescription},
+            NewEntry = feedformat:format(NewEntryRaw),
+            store:put(NewEntry);
+        {error, notfound}->
+            {error, notfound}
+    end.
+
+remove_entry_comments(FId, EId) ->
+    AllComments = store:comments_by_entry(FId, EId),
+    [begin
+          store:delete(comment, ID),
+          remove_media(M)
+     end || #comment{id = ID, media = M} <- AllComments].
+
+entry_add_comment(FId, User, EntryId, ParentComment, CommentId, Content, Medias) ->
+     case store:entry_by_id({EntryId, FId}) of
+         {ok, _E} ->
+             store:add_comment(FId, User, EntryId, ParentComment, CommentId, Content, Medias);
+         _ ->
+             ok
+     end.
+
+remove_media([]) -> ok;
+remove_media([#media{url=undefined, thumbnail_url=undefined}|T]) -> remove_media(T);
+remove_media([#media{url=undefined, thumbnail_url=TUrl}|T]) -> file:delete(?ROOT ++ TUrl), remove_media(T);
+remove_media([#media{url=Url, thumbnail_url=undefined}|T]) -> file:delete(?ROOT ++ Url), remove_media(T);
+remove_media([#media{url=Url, thumbnail_url=TUrl}|T]) -> file:delete(?ROOT ++ Url),file:delete(?ROOT ++ TUrl), remove_media(T).
+
+
+get_one_like_list(undefined) -> [];
+get_one_like_list(Id) -> {ok, OneLike} = store:get(one_like, Id),
+    [OneLike] ++ get_one_like_list(OneLike#one_like.next).
+
+get_entries_likes(Entry_id) ->
+    case store:get(entry_likes, Entry_id) of
+        {ok, Likes} -> get_one_like_list(Likes#entry_likes.one_like_head);
+        {error, notfound} -> []
+    end.
+
+get_entries_likes_count(Entry_id) ->
+    case store:get(entry_likes, Entry_id) of
+        {ok, Likes} ->
+            Likes#entry_likes.total_count;
+        {error, notfound} -> 0
+    end.
+
+get_user_likes_count(UserId) ->
+    case store:get(user_likes, UserId) of
+        {ok, Likes} -> Likes#user_likes.total_count;
+        {error, notfound} -> 0
+    end.
+
+get_user_likes(UserId) ->
+    case store:get(user_likes, UserId) of
+        {ok, Likes} -> get_one_like_list(Likes#user_likes.one_like_head);
+        {error, notfound} -> []
+    end.
+
+get_one_like_list(undefined, _) -> [];
+get_one_like_list(_, 0) -> [];
+get_one_like_list(Id, N) -> {ok, OneLike} = store:get(one_like, Id),
+    [OneLike] ++ get_one_like_list(OneLike#one_like.next, N-1).
+
+get_user_likes(UserId, {Page, PageAmount}) ->
+    case store:get(user_likes, UserId) of
+        {ok, Likes} -> lists:nthtail((Page-1)*PageAmount, get_one_like_list(Likes#user_likes.one_like_head, PageAmount*Page));
+        {error, notfound} -> []
+    end.
+
+% we have same in nsm_user? Why?
+is_subscribed_user(UserUidWho, UserUidWhom) -> nsm_users:is_user_subscr(UserUidWho, UserUidWhom).
+user_subscription_count(UserUid) -> length(nsm_users:list_subscr(UserUid)).
+user_friends_count(UserUid) -> length(nsm_users:list_subscr_me(UserUid)).
+
+get_comments_entries(UserUid, _, _Page, _PageAmount) ->
+    Pids = [Eid || #comment{entry_id=Eid} <- store:select(comment,
+        fun(#comment{author_id=Who}) when Who=:=UserUid ->true;(_)->false end)],
+    %?PRINT({"GCE pids length: ", length(Pids)}),
+    lists:flatten([store:select(entry,[{where, fun(#entry{entry_id=ID})-> ID=:=Pid end},
+        {order, {1, descending}},{limit, {1,1}}]) || Pid <- Pids]).
+
+get_my_discussions(_FId, Page, PageAmount, UserUid) ->
+    _Offset= case (Page-1)*PageAmount of
+        0 -> 1
+        ;M-> M
+    end,
+    Pids = [Eid || #comment{entry_id=Eid} <- store:select(comment,
+        fun(#comment{author_id=Who}) when Who=:=UserUid ->true;(_)->false end)],
+    lists:flatten([store:select(entry,[{where, fun(#entry{entry_id=ID})-> ID=:=Pid end},
+        {order, {1, descending}},{limit, {1,1}}]) || Pid <- Pids]).
+
+test_likes() ->
+    add_like(1, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bb", "derp"),
+    add_like(1, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bb", "derpina"),
+    add_like(1, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bb", "derpington"),
+    add_like(1, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bc", "derp"),
+    add_like(1, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bc", "lederpeaux"),
+    add_like(2, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bd", "derp"),
+    add_like(2, "17a10803-f064-4718-ae46-4a6d3c88415c-0796e2be", "derp"),
+    [
+        get_entries_likes("17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bb"),
+        get_entries_likes_count("17a10803-f064-4718-ae46-4a6d3c88415c-0796e2bb") == 3,
+        get_user_likes("derp"),
+        get_user_likes("derp", {1, 2}),
+        get_user_likes_count("derp") == 4
+    ].

+ 152 - 0
src/groups.erl

@@ -0,0 +1,152 @@
+-module(groups).
+-compile(export_all).
+-include("users.hrl").
+-include("groups.hrl").
+-include("feeds.hrl").
+-include("log.hrl").
+
+retrieve_groups(User) ->
+    ?INFO("retrieve_groups: ~p",[User]),
+    case list_groups_per_user(User) of
+         [] -> [];
+         Gs -> UC_GId = lists:sublist(lists:reverse(
+                              lists:sort([{group_members_count(GId), GId} || GId <- Gs])), 
+                                    20),
+               Result = [begin case get_group(GId) of
+                                   {ok, Group} -> {Group#group.name,GId,UC};
+                                   _ -> undefined end end || {UC, GId} <- UC_GId],
+               [X||X<-Result,X/=undefined] end.
+
+create_group_directly_to_db(UId, GId, Name, Desc, Publicity) ->
+    FId = store:feed_create(),
+    CTime = erlang:now(),
+    store:put(#group{username = GId,
+                      name = Name,
+                      description = Desc,
+                      publicity = Publicity,
+                      creator = UId,
+                      created = CTime,
+                      owner = UId,
+                     feed = FId}),
+    users:init_mq_for_group(GId),
+    add_to_group_directly_to_db(UId, GId, member),
+    GId.
+
+add_to_group(Who, GId, Type, Owner) -> nsx_msg:notify(["subscription", "user", Owner, "add_to_group"], {GId, Who, Type}).
+
+add_to_group_directly_to_db(UId, GId, Type) ->
+    store:put(#group_subs{user_id=UId, group_id=GId, user_type=Type}),
+    {ok, Group} = store:get(group, GId),
+    GU = Group#group.users_count,
+    store:put(Group#group{users_count = GU+1}).
+
+delete_group(GId) ->
+    {_, Group} = get_group(GId),
+    case Group of 
+        notfound -> ok;
+        _ ->
+            nsx_msg:notify([feed, delete, GId], empty),
+            store:delete_by_index(group_subs, <<"group_subs_group_id_bin">>, GId),         
+            store:delete(feed, Group#group.feed),
+            store:delete(group, GId),
+            % unbind exchange
+            {ok, Channel} = nsm_mq:open([]),
+            Routes = users:rk_group_feed(GId),
+            nsm_users:unbind_group_exchange(Channel, GId, Routes),
+            nsm_mq_channel:close(Channel)
+    end.
+
+list_groups_per_user(UId) -> [GId || #group_subs{group_id=GId} <- store:all_by_index(group_subs, <<"group_subs_user_id_bin">>, UId) ].
+list_group_members(GId) -> [UId || #group_subs{user_id=UId, user_type=UT} <- store:all_by_index(group_subs, <<"group_subs_group_id_bin">>, GId), UT == member ].
+list_group_members_by_type(GId, Type) -> [UId || #group_subs{user_id=UId, user_type=UT} <- store:all_by_index(group_subs, <<"group_subs_group_id_bin">>, GId), UT == Type ].
+list_group_members_with_types(GId) -> [{UId, UType} || #group_subs{user_id=UId, user_type=UType} <- store:all_by_index(group_subs, <<"group_subs_group_id_bin">>, list_to_binary(GId)) ].
+
+get_group(GId) -> store:get(group, GId).
+
+user_is_owner(UId, GId) ->
+    {R, Group} = store:get(group, GId),
+    case R of
+        ok -> case Group#group.owner of
+                UId -> true;
+                _ -> false
+            end;
+        _ -> false
+    end.
+
+user_in_group(UId, GId) ->
+    case store:get(group_subs, {UId, GId}) of
+        {error, notfound} -> false;
+        _ -> true
+    end.
+
+group_user_type(UId, GId) ->
+    case store:get(group_subs, {UId, GId}) of
+        {error, notfound} -> not_in_group;
+        {ok, #group_subs{user_type=Type}} -> Type
+    end.
+
+get_all_groups() -> store:all(group).
+get_popular_groups() -> ["kakaranet", "yeniler"].   % :-)
+
+%% join if group public, or send join request to group
+join_group(GId, User) ->
+    {ok, Group} = get_group(GId),
+    case Group of
+        #group{username = GId, publicity = public} ->
+            % Join to this group
+            add_to_group(User, GId, member, Group#group.owner),
+            {ok, joined};
+        #group{username = GId, publicity = _, feed=_Feed} ->
+            case group_user_type(User, GId) of
+                member -> {ok, joined};
+                req -> {error, already_sent};
+                reqrejected -> {error, request_rejected};
+                not_in_group -> add_to_group(User, GId, req, Group#group.owner), {ok, requested};
+                _ -> {error, unknown_type}
+            end;
+        _ -> {error, notfound}
+    end.
+
+approve_request(UId, GId, Owner) -> add_to_group(UId, GId, member, Owner).
+reject_request(UId, GId, Owner) -> add_to_group(UId, GId, reqrejected, Owner).
+change_group_user_type(UId, GId, Type) -> nsx_msg:notify(["subscription", "user", UId, "add_to_group"], {GId, UId, Type}).
+
+group_exists(GId) ->
+    {R, _} = get_group(GId),
+    case R of
+        ok -> true;
+        _ -> false
+    end.
+
+group_publicity(GId) ->
+    {_, Group} = get_group(GId),
+    case Group of
+        notfound ->
+            no_such_group;
+        _ ->
+            Group#group.publicity
+    end.
+
+group_members_count(GId) ->
+    {_, Group} = get_group(GId),
+    case Group of
+        notfound ->
+            no_such_group;
+        _ ->
+            Group#group.users_count
+    end.
+
+user_has_access(UId, GId) ->
+    UType = group_user_type(UId, GId),
+    {_, Group} = get_group(GId),
+    case Group of
+        notfound ->
+            false;
+        _ ->
+            GPublicity = Group#group.publicity,
+            case {GPublicity, UType} of
+                {public, _} -> true;
+                {private, member} -> true;
+                _ -> false
+            end
+    end.

+ 92 - 0
src/invites.erl

@@ -0,0 +1,92 @@
+-module(invites).
+-compile(export_all).
+-include_lib("users.hrl").
+-include_lib("invites.hrl").
+-include_lib("log.hrl").
+
+%% use rpc call to web node to get gettext support
+-define(TXT2(Key, Lang), rpc:call(?WEBSERVER_NODE, gettext, key2str, [Key, Lang])).
+
+generate_code(User) -> generate_code(User, undefined).
+generate_code(#user{username = User}, Mail) -> generate_code(User, Mail);
+generate_code(User, Mail) when is_list(User);
+                               User == undefined ->
+    Code = code(),
+    Rec = #invite_code{code = Code,
+                       create_date = erlang:now(),
+                       recipient = Mail,
+                       issuer = User},
+    %store:put(Rec),
+    nsx_msg:notify(["invite", "user", User, "add_invite_to_issuer"], {Rec}),
+    {ok, Code}.
+
+check_code(Code) ->
+    case store:get(invite_code, Code) of
+        {ok, Invite} ->
+            case Invite of
+                Invite when Invite#invite_code.created_user =/= undefined ->
+                    error;
+                _ ->
+                    {ok, Invite}
+            end;
+        {error, _} ->
+            error
+    end.
+
+use_code(Code, #user{username = UN}) ->
+    use_code(Code, UN);
+use_code(Code, User) when is_list(User) ->
+    case store:get(invite_code, Code) of
+        {ok, Invite} ->
+            Result = store:put(Invite#invite_code{created_user = User}),
+            %% add to tree
+            Parent = Invite#invite_code.issuer,
+            Parent /= undefined andalso
+                store:put_into_invitation_tree(Parent, User, Code),
+            ?INFO("Put code: parent ~p, user: ~p, Code: ~p", [Parent, User, Code]),
+            Result;
+        {error, _} ->
+            error
+    end.
+
+get_user_code(#user{username = UN}) -> get_user_code(UN);
+get_user_code(User) when is_list(User) -> store:invite_code_by_issuer(User).
+get_code_per_created_user(#user{username = UN}) -> get_code_per_created_user(UN);
+get_code_per_created_user(User) -> store:invite_code_by_user(User).
+get_all_code() -> store:all(invite_code).
+
+code() ->
+    <<A:(16*8), _/binary>> = crypto:rand_bytes(16),
+    lists:sublist(lists:flatten(io_lib:format("~25.36.0b", [A])), 14, 9).
+
+
+send_invite_email(User, Email, UserName, Text) ->
+    %% FIXME: hardcoded tr language
+    send_invite_email(User, Email, UserName, Text, "tr").
+
+-spec send_invite_email(record(user), string(), string(), string(), string()) -> {ok, string()} | {error, atom()}.
+send_invite_email(User, Email, UserName, Text, Lang) ->
+    case rpc:call(?WEBSERVER_NODE,validator_is_email,validate,["", Email]) of
+	true ->
+	    case length(UserName)>0 of
+		true ->
+		    Code = generate_code(User, Email),
+		    ?INFO("Code: ~p",[Code]),
+		    {ok, InviteCode} = Code,
+		    Url = rpc:call(?WEBSERVER_NODE, site_utils, create_url_invite, [InviteCode]),
+		    ?INFO("Invite url: ~p, InviteCode: ~p",[Url,InviteCode]),
+		    FromUser = case User#user.username of
+				   S when is_list(S) -> S;
+				   undefined -> ?TXT2("'kakaranet robot'", Lang)
+			       end,
+		    {Subject, Content} = rpc:call(?WEBSERVER_NODE,site_utils,invite_message,[FromUser, Url, Text, UserName, Lang]),
+                    nsx_msg:notify_email(Subject, Content, Email),
+		    {ok, InviteCode};
+		_ ->
+		    {error, wrong_username}
+	    end;
+	false ->
+	    {error, wrong_email}
+    end.
+
+

+ 112 - 0
src/map_reduce.erl

@@ -0,0 +1,112 @@
+-module(map_reduce).
+-include_lib("log.hrl").
+-include_lib("stdlib/include/qlc.hrl").
+-compile(export_all).
+
+get_single_tables(Setting,UId,GameFSM,_Convert, LeftList) ->
+    GetPropList = fun(Key,Setngs) -> 
+                   case Setngs of
+                        undefined -> undefined;
+                        _Else -> proplists:get_value(Key, Setngs)
+                   end end,
+
+    Rounds = GetPropList(rounds, Setting),
+    GameMode = GetPropList(game_mode, Setting),
+    Speed = GetPropList(speed, Setting),
+    Game = GetPropList(game, Setting),
+    PaidOnly = GetPropList(paid_only, Setting),
+    Lucky = false,
+
+    MaxUsers = case GameFSM of
+                   "tavla" ->
+                       case GameMode of standard -> 2; paired -> 10; _ -> 10 end;
+                   "okey" -> 4 end,
+
+    Check = fun(Param,Value) -> 
+                   case Param of
+                        undefined -> true;
+                        _Else -> Param == Value
+                   end end,
+
+    Cursor = fun(Id,FilterFree,FilterUser) ->
+                qlc:cursor(qlc:q([V || {{_,_,_K},_,V=#game_table{creator=C,
+                                                   rounds=R, game_type=G,
+                                                   users=U, game_speed=S,
+                                                   game_mode=GM,
+                                                   paid_only = PO,
+                                                   feel_lucky = L}} <- gproc:table(props),
+                           FilterFree(MaxUsers - length(U)),
+                           FilterUser(C,Id),
+                           Check(Game,G),
+                           Check(Speed,S),
+                           Check(GameMode,GM),
+                           Check(Rounds,R),
+                           Check(Lucky, L),
+                           Check(PaidOnly, PO)])
+                )
+    end,
+    OneAvailable   = fun(N) -> N == 1 end,
+    TwoAvailable   = fun(N) -> N == 2 end,
+    ThreeAvailable = fun(N) -> N == 3 end,
+    MoreAvailable  = fun(N) -> N > 3 end,
+    NotAvailable   = fun(N) -> N == 0 end,
+    Others         = fun(IterUser,CurrentUser) -> IterUser =/= CurrentUser end,
+    Own            = fun(IterUser,CurrentUser) -> IterUser == CurrentUser end,
+
+    case LeftList of
+        one_other -> qlc:next_answers(Cursor(UId, OneAvailable, Others), 10);
+        one_own -> qlc:next_answers(Cursor(UId, OneAvailable, Own), 10);
+        two_other -> qlc:next_answers(Cursor(UId, TwoAvailable, Others), 10);
+        two_own -> qlc:next_answers(Cursor(UId, TwoAvailable, Own), 10);
+        three_other -> qlc:next_answers(Cursor(UId, ThreeAvailable, Others), 10);
+        three_own -> qlc:next_answers(Cursor(UId, ThreeAvailable, Own), 10);
+        more_other -> qlc:next_answers(Cursor(UId, MoreAvailable, Others), 10);
+        more_own -> qlc:next_answers(Cursor(UId, MoreAvailable, Own), 10);
+        nomore_other -> qlc:next_answers(Cursor(UId, NotAvailable, Others), 10);
+        nomore_own -> qlc:next_answers(Cursor(UId, NotAvailable, Own), 10)
+    end.
+
+map_reduce(Module, Fun, Args)->
+    lists:flatten([ case rpc:call(Node, Module, Fun, Args) of
+                       {badrpc, _Reason} -> [];
+                       R -> R end || Node <- nodes()]).
+
+map_call(NodeType,Module,Fun,Args=[ID|Rest],NodeHash0) ->
+
+    Node = nsx_opt:get_env(nsx_idgen,game_pool,5000000) div 1000000,
+
+    NodeHash = case NodeHash0 of
+                    string_map -> fun(X) -> string_map(X) end;
+                    long_map -> fun(X) -> long_map(X) end end,
+
+    DefaultNodeAtom = case NodeType of
+                           "app" -> app_srv_node;
+                           "game" -> game_srv_node;
+                           "public" -> web_srv_node end,
+
+    ServerNode = case Node of
+                      4 -> nsx_opt:get_env(store,DefaultNodeAtom,'maxim@synrc.com');
+                      5 -> nsx_opt:get_env(store,DefaultNodeAtom,'maxim@synrc.com');
+                      _ -> list_to_atom(NodeType ++ "@srv" ++ 
+                           integer_to_list(NodeHash(ID)) ++
+                           ".kakaranet.com") end,
+
+    ?INFO("map_call: ~p",[{ServerNode,Module,Fun,Args}]),
+    rpc:call(ServerNode,Module,Fun,Args).
+
+string_map(X) -> lists:foldl(fun(A,Sum)->A+Sum end,0,X) rem 3+1.
+long_map(X)   -> X div 1000000.
+ 
+consumer_pid(Args)       -> map_call("app", nsm_bg,pid,Args,string_map).
+cached_feed(Args)        -> map_call("app", nsm_writer,cached_feed,Args,string_map).
+cached_direct(Args)      -> map_call("app", nsm_writer,cached_direct,Args,string_map).
+cached_friends(Args)     -> map_call("app", nsm_writer,cached_friends,Args,string_map).
+cached_groups(Args)      -> map_call("app", nsm_writer,cached_groups,Args,string_map).
+start_lobby(Args)        -> map_call("game",nsm_srv_tournament_lobby_sup,start_lobby,Args,long_map).
+lobby_history(Args)      -> map_call("game",nsm_srv_tournament_lobby,chat_history,Args,long_map).
+start_tournament(Args)   -> map_call("game",game_manager,start_tournament,Args,long_map).
+tournament_started(Args) -> map_call("game",game_manager,  get_tournament,Args,long_map).
+store_token(Args)        -> map_call("game",auth_server,store_token,Args,long_map).
+delete_table(Args)       -> map_call("game",game_manager,destroy_game,Args,long_map).
+public_table(Args)       -> map_call("public",view_table,get_table,Args,long_map).
+start_worker(Args)       -> map_call("app",nsm_launcher,start_worker,Args,string_map). % nsm_queries:start_worker(["doxtop",Type,Feed,Direct]).

+ 60 - 0
src/meetings.erl

@@ -0,0 +1,60 @@
+-module(meetings).
+-include("users.hrl").
+-include("meetings.hrl").
+-compile(export_all).
+
+create_team(Name) ->
+    TID = store:next_id("team",1),
+    ok = store:put(Team = #team{id=TID,name=Name}),
+    TID.
+
+create(UID, Name) -> create(UID, Name, "", date(), time(), 100, 100, undefined, pointing, game_okey, standard, 8, slow).
+create(UID, Name, Desc, Date, Time, Players, Quota, Awards, Type, Game, Mode, Tours, Speed) ->
+    NodeAtom = nsx_opt:get_env(store,game_srv_node,'game@doxtop.cc'),
+    TID = rpc:call(NodeAtom, game_manager, gen_game_id, []),
+
+    CTime = erlang:now(),
+    ok = store:put(#tournament{name = Name,
+                                   id = TID,
+                                   description = Desc,
+                                   quota = Quota,
+                                   players_count = Players,
+                                   start_date = Date,
+                                   awards = Awards,
+                                   creator = UID,
+                                   created = CTime,
+                                   game_type = Game,
+                                   game_mode = Mode,
+                                   type = Type,
+                                   tours = Tours,
+                                   speed = Speed,
+                                   start_time = Time,
+                                   status = created,
+                                   owner = UID}),
+
+    TID.
+
+get(TID) ->
+    case store:get(tournament, TID) of
+        {ok, Tournament} -> Tournament;
+        {error, not_found} -> #tournament{};
+        {error, notfound} -> #tournament{}
+    end.
+
+start(_TID) -> ok.
+join(UID, TID) -> store:join_tournament(UID, TID).
+remove(UID, TID) -> store:leave_tournament(UID, TID).
+waiting_player(TID) -> store:tournament_pop_waiting_player(TID).
+joined_users(TID) -> store:tournament_waiting_queue(TID).
+user_tournaments(UID) -> store:user_tournaments(UID).
+user_joined(TID, UID) -> 
+    AllJoined = [UId || #play_record{who = UId} <- joined_users(TID)],
+    lists:member(UID, AllJoined).
+all() -> store:all(tournament).
+user_is_team_creator(_UID, _TID) -> true.
+list_users_per_team(_TeamID) -> [].
+destroy(TID) -> store:delete_by_index(play_record, <<"play_record_tournament_bin">>, TID),
+                          store:delete(tournament,TID).
+clear() -> [destroy(T#tournament.id) || T <- store:all(tournament)].
+lost() -> lists:usort([erlang:element(3, I) || I <- store:all(play_record)]).
+fake_join(TID) -> [nsm_tournaments:join(nsm_auth:ima_gio2(X),TID)||X<-lists:seq(1,30)].

+ 406 - 0
src/membership_packages.erl

@@ -0,0 +1,406 @@
+-module(membership_packages).
+-author('Vladimir Baranov <baranoff.vladimir@gmail.com>').
+-include("membership_packages.hrl").
+-include("log.hrl").
+-include("accounts.hrl").
+-compile(export_all).
+
+-type package_id() :: integer().
+-type list_options()::[{payment_type, payment_type()}|{available_for_sale, boolean()}].
+
+-spec add_package(#membership_package{})->{ok, Id::package_id()}|{error, Reason::any()}.
+add_package(#membership_package{}=Package)->
+    Id = generate_id(),
+    save_package(Package#membership_package{id = Id}).
+
+-spec get_package(any())-> {ok, #membership_package{}} | {error, Reason::any()}.
+get_package(PackageId)->
+    case store:get(membership_package, PackageId) of
+        {ok, #membership_package{} = Package}->
+            {ok, Package};
+        {error, Reason}->
+            {error, Reason}
+    end.
+
+-spec list_packages(SelectOptions::list_options())->[#membership_package{}].
+list_packages(Options) ->
+    Predicate =
+        fun(MP = #membership_package{}) ->
+                check_conditions(Options, MP, true)
+        end,
+
+    select(membership_package, Predicate).
+
+-spec list_packages()->[#membership_package{}].
+list_packages()->
+     store:all(membership_package).
+
+-spec available_for_sale(package_id(), boolean()) -> ok | {error, any()}.
+available_for_sale(PackageId, State) ->
+    case get_package(PackageId) of
+        {ok, Package} ->
+            case save_package(
+                   Package#membership_package{available_for_sale = State}) of
+                {ok, _} ->
+                    ok;
+                Error ->
+                    Error
+            end;
+        {error, Reason}->
+            {error, Reason}
+    end.
+
+-spec add_purchase(#membership_purchase{}) -> {ok, PurchaseId::string()}.
+add_purchase(#membership_purchase{} = MP) ->
+    add_purchase(#membership_purchase{} = MP, undefined, undefined).
+
+-spec add_purchase(#membership_purchase{}, purchase_state(), StateInfo::any()) -> {ok, PurchaseId::string()}.
+add_purchase(#membership_purchase{} = MP, State0, Info) ->
+    case store:get(membership_purchase, MP#membership_purchase.id) of
+        {ok, _} -> {error, already_bought_that_one};
+        {error, notfound} ->
+            %% fill needed fields
+            Start = now(),
+            State = default_if_undefined(State0, undefined, ?MP_STATE_ADDED),
+            %% FIXME: uniform info field if needed
+            StateLog = case Info of
+                           undefined ->
+                               [#state_change{time = Start, state = State,
+                                              info = system_change}];
+                           _ ->
+                               [#state_change{time = Start, state = State,
+                                              info = Info}]
+                       end,
+
+            %% TODO: add check for duplicate purches if id given by external module
+            Id = default_if_undefined(MP#membership_purchase.id, undefined, purchase_id()),
+
+            Purchase = MP#membership_purchase{id = Id,
+                                              state = State,
+                                              start_time = Start,
+                                              state_log = StateLog},
+
+            %% notify about purchase added
+%            nsx_msg:notify_purchase(Purchase),
+
+            ?INFO("Purchase added ~p ~p",[Purchase#membership_purchase.user_id, Purchase]),
+
+            nsm_riak:add_purchase_to_user(Purchase#membership_purchase.user_id, Purchase)
+    end.
+
+-spec get_purchase(string())-> {ok, #membership_purchase{}} | {error, Reason::any()}.
+get_purchase(PurchaseId)->
+    case store:get(membership_purchase, PurchaseId) of
+        {ok, #membership_purchase{} = Package}->
+            {ok, Package};
+        {error, Reason}->
+            {error, Reason}
+    end.
+
+-spec set_purchase_state(term(), purchase_state(), term()) -> ok.
+set_purchase_state(MPId, NewState, Info) ->
+    case store:get(membership_purchase, MPId) of 
+      {ok, MP} ->
+
+    Time = now(),
+    StateLog = MP#membership_purchase.state_log,
+    %% add new state to head of state log
+    NewStateLog = [#state_change{time = Time, state = NewState,
+                                 info = Info}|StateLog],
+    EndTime = case NewState of
+                  ?MP_STATE_DONE -> now();
+                  ?MP_STATE_CANCELLED -> now();
+                  ?MP_STATE_FAILED -> now();
+                  _ -> MP#membership_purchase.end_time
+              end,
+    Purchase = MP#membership_purchase{state = NewState,
+                                      end_time = EndTime,
+                                      state_log = NewStateLog},
+
+    %% notify aboput state change
+%    nsx_msg:notify_purchase(Purchase),
+    NewMP=MP#membership_purchase{state = NewState,
+                                         end_time = EndTime,
+                                         state_log = NewStateLog},
+    store:put(NewMP),
+
+    if
+        NewState == ?MP_STATE_DONE ->
+            charge_user_account(MP),
+            nsm_affiliates:purchase_hook(NewMP);
+        true ->
+            ok
+    end,
+    ok;
+  
+    Error -> ?INFO("Can't set purchase state, not yet in db"), Error
+    end.
+
+
+-spec set_purchase_info(term(), term()) -> ok | {error, not_found}.
+set_purchase_info(MPId, Info) ->
+    {ok, MP} = store:get(membership_purchase, MPId),
+    store:put(MP#membership_purchase{info = Info}).
+
+set_purchase_external_id(MPId, ExternalId) ->
+    {ok, MP} = store:get(membership_purchase, MPId),
+    case MP#membership_purchase.external_id of
+        ExternalId ->
+            ok;
+        _ ->
+            store:put(MP#membership_purchase{external_id = ExternalId})
+    end.
+
+-spec create_storage()-> ok.
+create_storage()->
+    %% FIXME: usage of direct mnesia calls
+    ok = nsm_mnesia:create_table(membership_package,
+                                    record_info(fields, membership_package),
+                                    [{storage, permanent}]),
+    ok = nsm_mnesia:create_table(membership_purchase,
+                                    record_info(fields, membership_purchase),
+                                    [{storage, permanent}]).
+
+-spec list_purchases() -> list(#membership_purchase{}).
+list_purchases() ->
+    store:all(membership_purchase).
+
+-spec list_purchases(SelectOptions::list()) -> list(#membership_purchase{}).
+list_purchases(SelectOptions) ->
+    Predicate =
+        fun(MP = #membership_purchase{}) ->
+                check_conditions(SelectOptions, MP, true)
+        end,
+
+    select(membership_purchase, Predicate).
+
+-spec purchase_id() -> string().
+purchase_id() ->
+    %% get next generated id for membership purchase
+    NextId = store:next_id("membership_purchase"),
+    lists:concat([timestamp(), "_", NextId]).
+
+
+%% FIXME: temporary! Delete in production.
+-spec add_sample_data()-> [ok|{error, any()}].
+add_sample_data()->
+    SamplePackages = [#membership_package{
+                         no = 1,
+                         amount = 7,
+                         deducted_for_gifts = 0,
+                         quota = 7,
+                         net_membership = 7},
+     #membership_package{
+                         no = 2,
+                         amount = 12,
+                         deducted_for_gifts = 5,
+                         quota = 15,
+                         net_membership = 7},
+     #membership_package{
+                         no = 3,
+                         amount = 12,
+                         deducted_for_gifts = 0,
+                         quota = 15,
+                         net_membership = 12},
+     #membership_package{
+                         no = 4,
+                         amount = 25,
+                         deducted_for_gifts = 10,
+                         quota = 30,
+                         net_membership = 15},
+     #membership_package{
+                         no = 5,
+                         amount = 30,
+                         deducted_for_gifts = 0,
+                         quota = 60,
+                         net_membership = 30},
+     #membership_package{
+                         no = 6,
+                         amount = 50,
+                         deducted_for_gifts = 20,
+                         quota = 60,
+                         net_membership = 30},
+     #membership_package{
+                         no = 7,
+                         amount = 50,
+                         deducted_for_gifts = 0,
+                         quota = 90,
+                         net_membership = 50},
+     #membership_package{
+                         no = 8,
+                         amount = 100,
+                         deducted_for_gifts = 40,
+                         quota = 120,
+                         net_membership = 60}],
+
+    WithPaymentTypes =
+        [Package#membership_package{id = generate_id(),
+                                    payment_type=Payment} ||
+            Payment <- [facebook, credit_card, wire_transfer, paypal, mobile],
+            Package <- SamplePackages],
+
+	%%make all packages enable for sale
+    Enabled = [P#membership_package{available_for_sale = true} ||
+                  P <- WithPaymentTypes],
+
+    store:put(Enabled).
+
+generate_id()->
+    Id = store:next_id("membership_package"),
+    integer_to_list(Id).
+
+%% return default value if value match Undefined spec
+default_if_undefined(Value, Undefined, Default) ->
+    case Value of
+        Undefined ->
+            Default;
+        _ ->
+            Value
+    end.
+
+%% charge user account with kakush and quota.
+charge_user_account(MP) ->
+    %% Charge user account
+    OrderId = MP#membership_purchase.id,
+    Package = MP#membership_purchase.membership_package,
+    Kakush = Package#membership_package.deducted_for_gifts,
+    Quota = Package#membership_package.quota,
+    UserId = MP#membership_purchase.user_id,
+
+    PaymentTransactionInfo = #ti_payment{id=MP#membership_purchase.id},
+
+    try
+        ?INFO("charge user account. OrderId: ~p, User: ~p, Kakush:~p, Quota:~p",
+              [OrderId, UserId, Kakush, Quota]),
+
+        nsm_accounts:transaction(UserId, ?CURRENCY_KAKUSH, Kakush, PaymentTransactionInfo),
+        nsm_accounts:transaction(UserId, ?CURRENCY_QUOTA, Quota, PaymentTransactionInfo)
+    catch
+        _:E ->
+            ?ERROR("unable to charge user account. User=~p, OrderId=~p. Error: ~p",
+                   [UserId, OrderId, E])
+    end.
+
+
+%% get all records from database, filter them with predicate.
+%% FIXME: temporary hack to provide mnesias select functionality with riak
+select(RecordType, Predicate) ->
+    All = store:all(RecordType),
+	lists:filter(Predicate, All).
+
+
+save_package(Package) ->
+    case store:put([Package]) of
+        ok ->
+            {ok, Package#membership_package.id};
+        {error, Reason}->
+            {error, Reason}
+    end.
+
+timestamp()->
+    {Y, Mn, D} = erlang:date(),
+    {H, M, S} = erlang:time(),
+    lists:flatten(
+      io_lib:format("~b~2..0b~2..0b_~2..0b~2..0b~2..0b", [Y, Mn, D, H, M, S])).
+
+%% Packages conditions
+check_conditions(_, _, false) -> false;
+check_conditions([{available_for_sale, AS}|T],
+                 MP = #membership_package{available_for_sale = AS1}, _) ->
+    check_conditions(T, MP, AS == AS1);
+check_conditions([{payment_type, PT}|T],
+                 MP = #membership_package{payment_type = PT1}, _) ->
+    check_conditions(T, MP, PT == PT1);
+
+%%TODO: Add purchase conditions
+check_conditions([], _, true) -> true.
+
+%% @private
+delete_package(PackageId) ->
+    store:delete(membership_package, PackageId).
+
+%%
+%% Tests
+%%
+%-ifdef(TEST).
+
+packages_test_()->
+    {setup, fun setup/0, fun cleanup/1,
+     {with, [fun list_/1,
+             fun get_/1,
+             fun change_availability_/1]}
+    }.
+
+
+setup() ->
+    % Uncomment to run tests with dbg:
+    % dbg:tracer(),
+    % dbg:p(all, call),
+    % dbg:tpl(membership_packages, []),
+    TestPackages =
+        [#membership_package{no=1,
+                             payment_type=test_payment_x,
+                             amount = 100,
+                             deducted_for_gifts = 5,
+                             quota = 300,
+                             net_membership = 7,
+                             available_for_sale = true
+                            },
+         #membership_package{no=2,
+                             payment_type=test_payment_y,
+                             amount = 100,
+                             deducted_for_gifts = 5,
+                             quota = 300,
+                             net_membership = 7
+                            },
+         #membership_package{no=3,
+                             payment_type=test_payment_y,
+                             amount = 100,
+                             deducted_for_gifts = 5,
+                             quota = 300,
+                             net_membership = 7
+                            }],
+
+     [Id || {ok, Id} <-
+                [{ok, _} = ?MODULE:add_package(Package) || Package <- TestPackages]].
+
+cleanup(Ids) ->
+    [ok = delete_package(PackageId) || PackageId <- Ids].
+
+list_(_Packages)->
+    ?assertMatch([#membership_package{no=1}], ?MODULE:list_packages([{payment_type, test_payment_x}])),
+
+    ?assertEqual([], ?MODULE:list_packages([{payment_type, test_payment_y},
+                                            {available_for_sale, true}])),
+    YPaymentTypePackages = ?MODULE:list_packages([{payment_type, test_payment_y}]),
+    YPaymentTypePackagesNumbers = lists:sort([No || #membership_package{no=No} <- YPaymentTypePackages]),
+    ?assertMatch([2,3], YPaymentTypePackagesNumbers).
+
+get_([Id|_])->
+    ?assertMatch({ok, #membership_package{id=Id}}, ?MODULE:get_package(Id)),
+    ?assertMatch({error, _}, ?MODULE:get_package(-1)).
+
+change_availability_([Id | _]) ->
+    [begin
+         ?assertEqual(ok, ?MODULE:available_for_sale(Id, State)),
+         ?assertMatch({ok, #membership_package{id=Id, available_for_sale = State}},
+                      ?MODULE:get_package(Id))
+     end || State <- [true, false]].
+
+
+get_monthly_purchase_limit() ->
+    MostExpencivePackageWorth = lists:max([P#membership_package.amount || P <- store:all(membership_package), P#membership_package.available_for_sale]),
+    ?MP_MONTHLY_LIMIT_MULTIPLIER * MostExpencivePackageWorth.
+
+check_limit_over(UId, PackageId) ->
+    Limit = ?MP_MONTHLY_LIMIT_MULTIPLIER, %get_monthly_purchase_limit(),
+    {ok, Package} = nsm_membership_packages:get_package(PackageId),
+    PackagePrice = Package#membership_package.amount,
+    {{CurYear, CurMonth, _}, _} = calendar:now_to_datetime(now()),
+    UserMonthlyPurchases = [begin
+        {{Year, Month, _}, _} = calendar:now_to_datetime(P#membership_purchase.start_time),
+        {Year, Month, P}
+    end || P <- store:purchases(UId)],
+    ThisMonthPurchases = [P || {Y, M, P} <- UserMonthlyPurchases, Y == CurYear, M == CurMonth],
+    ThisMonthTotal = lists:sum([(P#membership_purchase.membership_package)#membership_package.amount || P <- ThisMonthPurchases]),
+    (ThisMonthTotal + PackagePrice) > Limit.

+ 9 - 0
src/store.app.src

@@ -0,0 +1,9 @@
+{application, store,
+ [
+  {description, "Distributed Persistance"},
+  {vsn, "1"},
+  {registered, []},
+  {applications, [kernel,stdlib,riak_kv,mqs]},
+  {mod, { store_app, []}},
+  {env, []}
+ ]}.

+ 727 - 0
src/store.erl

@@ -0,0 +1,727 @@
+% This is database handling application that hides database access
+% and provides high-level rich API to stored data:
+%
+%    - acl
+%    - affiliates
+%    - users
+%    - groups
+%    - subscriptions
+%    - feeds
+%    - invites
+%    - translations
+%    - accounts
+%    - tournaments
+%    - payments
+%    - scoring
+%
+% Currently nsm_db supports following store backends:
+%
+%    - Mnesia
+%    - Riak
+
+-module(store).
+-author('Maxim Sokhatsky <maxim@synrc.com>').
+
+-include("users.hrl").
+-include("groups.hrl").
+-include("feeds.hrl").
+-include("acls.hrl").
+-include("meetings.hrl").
+-include("invites.hrl").
+-include("attachments.hrl").
+-include("accounts.hrl").
+-include("log.hrl").
+-include("membership_packages.hrl").
+-include_lib("stdlib/include/qlc.hrl").
+-compile(export_all).
+
+start() -> DBA = ?DBA, DBA:start().
+dir() -> DBA = ?DBA, DBA:dir().
+purchases(UserId) -> DBA = ?DBA, DBA:purchases(UserId).
+transactions(UserId) -> DBA = ?DBA, DBA:transactions(UserId).
+stop() -> DBA = ?DBA, DBA:stop().
+initialize() -> DBA = ?DBA, DBA:initialize().
+delete() -> DBA = ?DBA, DBA:delete().
+init_indexes() -> DBA = ?DBA, DBA:init_indexes().
+
+init_db() ->
+    case nsm_db:get(user,"alice") of
+       {error,_} ->
+            DBA = ?DBA,
+            DBA:init_db(),
+            add_seq_ids(),
+            nsm_accounts:create_account(?SYSTEM_ACCOUNT_ID),
+            add_sample_users(),
+            add_sample_packages(),
+            add_translations(),
+            pointing_rules:setup(),
+            add_configs(),
+            case is_production() of
+                false ->
+                    add_affiliates(),
+                    add_contracts(),
+                    add_purchases();
+                true ->
+                    do_nothing
+            end,
+            nsm_gifts_tools:import_all();
+       {ok,_} -> ignore
+    end.
+
+is_production() ->
+    case nsm_db:get(config, "debug/production", false) of
+        {ok, true} -> true;
+        _ -> false
+    end.
+
+add_affiliates() ->
+%    ?INFO("~w:add_affiliates/0 Started", [?MODULE]),
+    nsm_affiliates:create_affiliate("kunthar"),
+    nsm_affiliates:create_affiliate("alice"),
+    nsm_affiliates:create_affiliate("maxim"),
+    nsm_affiliates:reg_follower("demo1", "kunthar", 1),
+    nsm_affiliates:reg_follower("demo2", "kunthar", 1),
+    nsm_affiliates:reg_follower("kate", "kunthar", 2),
+%    ?INFO("~w:add_affiliates/0 Finished", [?MODULE]),
+    ok.
+
+add_contracts() ->
+   ?INFO("~w:add_contracts/0 Started", [?MODULE]),
+    {CurDate, _} = calendar:now_to_local_time(now()),
+    CurDateDays = calendar:date_to_gregorian_days(CurDate),
+    StartDate = calendar:gregorian_days_to_date(CurDateDays - 15),
+    FinishDate = calendar:gregorian_days_to_date(CurDateDays + 15),
+    catch nsm_affiliates:create_contract("kunthar", "Test contract", % THIS BROKES CONSISTENCY ON BOOT
+                                         StartDate, FinishDate,
+                                         2, 10.2),
+   ?INFO("~w:add_contracts/0 Finished", [?MODULE]),
+    ok.
+
+add_purchases() ->
+%    ?INFO("~w:add_purchases/0 Started", [?MODULE]),
+    {ok, Pkg1} = nsm_membership_packages:get_package(1),
+    {ok, Pkg2} = nsm_membership_packages:get_package(2),
+    {ok, Pkg3} = nsm_membership_packages:get_package(3),
+    {ok, Pkg4} = nsm_membership_packages:get_package(4),
+    PList = [{"kunthar", Pkg1},{"maxim", Pkg2},{"maxim",Pkg4}, {"kate", Pkg3} ],
+    [ok = add_purchase(U, P) || {U, P} <- PList],
+%    ?INFO("~w:add_purchases/0 Finished", [?MODULE]),
+    ok.
+
+add_purchase(UserId, Package) ->
+    {ok, MPId} = nsm_membership_packages:add_purchase(
+                         #membership_purchase{user_id=UserId, membership_package=Package }),
+%    ?INFO("Purchase Added: ~p",[MPId]),
+    nsm_membership_packages:set_purchase_state(MPId, ?MP_STATE_DONE, undefined).
+
+
+
+add_seq_ids() ->
+    Init = fun(Key) ->
+           case nsm_db:get(id_seq, Key) of
+                {error, _} -> ok = nsm_db:put(#id_seq{thing = Key, id = 0});
+                {ok, _} -> ignore
+           end
+    end,
+    Init("player_scoring"),
+    Init("scoring_record"),
+    Init("tournament"),
+    Init("user_transaction"),
+    Init("transaction"),
+    Init("team"),
+    Init("membership_purchase"),
+    Init("table"),
+    Init("acl"),
+    Init("acl_entry"),
+    Init("feed"),
+    Init("entry"),
+    Init("like_entry"),
+    Init("likes"),
+    Init("one_like"),
+    Init("comment"),
+    Init("save_table").
+
+add_translations() ->
+    lists:foreach(fun({English, Lang, Word}) ->
+                          ok = nsm_db:put(#ut_word{english = English, lang = "en",  word = English}),
+                          ok = nsm_db:put(#ut_word{english = Word,    lang = Lang,  word = Word}),
+                          ok = nsm_db:put(#ut_translation{source = {Lang, Word},    word = English}),
+                          ok = nsm_db:put(#ut_translation{source = {"en", English}, word = English}),
+                          ok = nsm_db:put(#ut_translation{source = {Lang, English}, word = Word}),
+              ok
+    end, ?URI_DICTIONARY).
+
+create_tour_users(A,B,Groups) ->
+    ImagioUsers = nsm_auth:imagionary_users(),
+    TourUsers =  [#user{username = nsm_auth:ima_gio(N,ImagioUsers),
+                            password="password",
+                            feed = feed_create(),
+                            name = nsm_auth:ima_gio(N,ImagioUsers),
+                            team = create_team("tours"), direct = feed_create(),
+                            status=ok,
+                            age={1981,9,29},
+                            register_date={1345,14071,852889}
+                           } || N <- lists:seq(A, B)],
+    [ begin
+          [nsm_groups:add_to_group_directly_to_db(Me#user.username, GId, member)||GId<-Groups],
+          nsm_users:init_mq(Me#user.username, Groups),
+          nsm_accounts:create_account(Me#user.username),
+          nsm_accounts:transaction(Me#user.username,
+                                   ?CURRENCY_QUOTA,
+                                   nsm_db:get_config("accounts/default_quota",  300),
+                                   #ti_default_assignment{}),
+          nsm_db:put(Me#user{password = utils:sha(Me#user.password),
+                                starred = feed_create(),
+                                pinned = feed_create()})
+      end || Me <- TourUsers].
+
+create_tour_users2(A,B,Groups) ->
+    ImagioUsers = nsm_auth:imagionary_users2(),
+    TourUsers =  [#user{username = nsm_auth:ima_gio2(N,ImagioUsers),
+                            password="password",
+                            feed = feed_create(),
+                            name = nsm_auth:ima_gio2(N,ImagioUsers),
+                            team = create_team("tours"), direct = feed_create(),
+                            status=ok,
+                            age={1981,9,29},
+                            register_date={1345,14071,852889}
+                           } || N <- lists:seq(A, B)],
+    [ begin
+          [nsm_groups:add_to_group_directly_to_db(Me#user.username, GId, member)||GId<-Groups],
+          nsm_users:init_mq(Me#user.username, Groups),
+          nsm_accounts:create_account(Me#user.username),
+          nsm_accounts:transaction(Me#user.username,
+                                   ?CURRENCY_QUOTA,
+                                   nsm_db:get_config("accounts/default_quota",  300),
+                                   #ti_default_assignment{}),
+          nsm_db:put(Me#user{password = utils:sha(Me#user.password),
+                                starred = feed_create(),
+                                pinned = feed_create()})
+      end || Me <- TourUsers].
+
+add_sample_users() ->
+    UserList =
+                    [#user{username = "demo1", password="kakara20",
+                           name = "Demo", surname = "Nstration", feed = feed_create(),
+                           type = admin, direct = feed_create(),
+                           sex=m,
+                           status=ok,
+                           team = create_team("tours"),
+                           email="demo1@kakaranet.com"},
+                     #user{username = "demo2", password="kakara20",
+                           name = "Demo2 User", surname = "Two", feed = feed_create(),
+                           type = admin, direct = feed_create(),
+                           status=ok,
+                           team = create_team("tours"),
+                           sex=m},
+                     #user{username = "maxim", password="kaka15ra",
+                           name = "Maxim", surname = "Sokhatsky", feed = feed_create(),
+                           type = admin, direct = feed_create(),
+                           sex=m,
+                           status=ok,
+                           team = create_team("tours"),
+                           email="maxim.sokhatsky@gmail.com"},
+                     #user{username = "ahmettez", password="kaka15ra",
+                           name = "Ahmet", surname = "Tez", feed = feed_create(),
+                           type = admin, direct = feed_create(),
+                           sex=m,
+                           status=ok,
+                           team = create_team("tours"),
+                           email="tez.ahmettez@gmail.com"},
+                     #user{username = "kunthar", password="kaka1224", name = "Kunthar", feed = feed_create(),
+                           type = admin, direct = feed_create(),
+                           sex=m,
+                           status=ok,
+                           team = create_team("tours"),
+                           email="kunthar@gmail.com"},
+                     #user{username = "sustel", password="kaka15ra", name = "Sustel",
+                           feed = feed_create(), direct = feed_create(),
+                           type = admin,
+                           team = create_team("tours"),
+                           status=ok,
+                           email = "sinanustel@gmail.com",
+                           sex=m},
+                     #user{username = "kate", password="kaka15ra",
+                           name = "Kate", surname = "Foxconn", feed = feed_create(),
+                           status=ok, direct = feed_create(),
+                           email="kate@synrc.com",
+                           team = create_team("tours"),
+                           sex=f},
+                     #user{username = "alice", password="kaka15ra",
+                           name = "Alicja", surname = "Example", feed = feed_create(),
+                           team = create_team("tours"), direct = feed_create(),
+                           facebook_id = "1234567890",
+                           email="alice@synrc.com",
+                           status=ok,
+                           sex=f},
+                     #user{username = "commonuser", password="123456",
+                           name = "Usert", surname = "Userson", feed = feed_create(),
+                           team = create_team("tours"), direct = feed_create(),
+                           status=ok,
+                           email="commonuser@synrc.com",
+                           sex="male",
+                           location="İstanbul",
+                           age={1986,4,3},
+                           education="highschool",
+                           register_date={1345,14070,852889}
+                     },
+                     #user{username = "imagia", password="123456",
+                           name = "Ima", surname = "Gionari", feed = feed_create(),
+                           team = create_team("tours"), direct = feed_create(),
+                           status=ok,
+                           sex="female",
+                           location="Ankara",
+                           age={1988,4,3},
+                           education="elementary school",
+                           register_date={1345,14070,852889}
+                     },
+                     #user{username = "willbe", password="123456",
+                           name = "Will", surname = "Beimagionary", feed = feed_create(),
+                           team = create_team("tours"), direct = feed_create(),
+                           status=ok,
+                           sex="male",
+                           location="Ankara",
+                           age={1985,8,3},
+                           education="Bc. S.",
+                           register_date={1345,14070,852889}
+                     },
+                     #user{username = "nata88", password="123456",
+                           name = "Nata", surname = "Eightyeight", feed = feed_create(),
+                           team = create_team("tours"), direct = feed_create(),
+                           status=ok,
+                           sex="female",
+                           location="Ankara",
+                           age={1985,8,3},
+                           education="Bc. S.",
+                           register_date={1345,14070,852889}
+                     },
+                     #user{username = "serg", password="kaka1224",
+                           name = "Serg", surname = "Polkovnikov", feed = feed_create(),
+                           team = create_team("tours"), direct = feed_create(),
+                           status=ok,
+                           sex="male",
+                           location="İstanbul",
+                           age={1976,1,8},
+                           education="Ph. D.",
+                           register_date={1345,14070,852889}
+                     },
+                     #user{username = "doxtop", password="password",
+                           feed = feed_create(),
+                           name = "Andrii Zadorozhnii",
+                           email="doxtop@synrc.com",
+                           type=admin,
+                           team = create_team("tours"), direct = feed_create(),
+                           status=ok,
+                           age={1981,9,29},
+                           register_date={1345,14071,852889}
+                     } 
+         ],
+
+
+    ?INFO("creating groups"),
+
+    nsm_users:init_mq("ahmettez", ["kakaranet", "yeniler"]),
+
+    GId1  = nsm_groups:create_group_directly_to_db("ahmettez", "kakaranet", "Kakaranet", "Kakaranet'e Hoşgeldiniz", public),
+    GId2  = nsm_groups:create_group_directly_to_db("ahmettez", "yeniler", "Yeniler", "So, you must be new here.", public),
+
+    create_tour_users(1,386,[GId1,GId2]),
+
+    ?INFO("adding users accounts"),
+    [ begin
+          nsm_accounts:create_account(Me#user.username),
+          nsm_accounts:transaction(Me#user.username, ?CURRENCY_QUOTA, nsm_db:get_config("accounts/default_quota", 300), #ti_default_assignment{}),
+          nsm_db:put(Me#user{password = utils:sha(Me#user.password),
+                                starred = feed_create(),
+                                pinned = feed_create()})
+      end || Me <- UserList],
+    ?INFO("adding users to groups"),
+    [ begin
+          nsm_users:init_mq(Me#user.username, [GId1, GId2]),
+          %subscribe_user_to_list(Me#user.username, UserList),
+          case Me#user.username of
+              "ahmettez" -> ok; % ahmettez already in groups, as admin
+              _ ->
+                  nsm_groups:add_to_group_directly_to_db(Me#user.username, GId1, member),
+                  nsm_groups:add_to_group_directly_to_db(Me#user.username, GId2, member)
+          end
+      end || Me <- UserList ],
+    %% define access for Maxim to feature admin
+    nsm_acl:define_access({user, "maxim"},    {feature, admin}, allow),
+    nsm_acl:define_access({user_type, admin}, {feature, admin}, allow),
+
+    ?INFO("making all users each other friends"),
+
+    [[case Me == Her of
+        true -> ok;
+        false -> nsm_users:subscr_user(Me#user.username, Her#user.username)
+    end || Her <- UserList] || Me <- UserList].
+
+add_sample_packages() -> nsm_membership_packages:add_sample_data().
+version() -> ?INFO("version: ~p", [?VERSION]).
+
+% blocking
+
+block_user(Who, Whom) -> DBA=?DBA, DBA:block_user(Who, Whom).
+list_blocks(Who) -> DBA=?DBA, DBA:list_blocks(Who).
+unblock_user(Who, Whom) -> DBA=?DBA, DBA:unblock_user(Who, Whom).
+list_blocked_me(Me) -> DBA=?DBA, DBA:list_blocked_me(Me).
+is_user_blocked(Who, Whom) -> DBA=?DBA, DBA:is_user_blocked(Who, Whom).
+
+% configs
+
+add_configs() ->
+    %% smtp
+    nsm_db:put(#config{key="smtp/user",     value="noreply@kakaranet.com"}),
+    nsm_db:put(#config{key="smtp/password", value="kakam41l"}),
+    nsm_db:put(#config{key="smtp/host",     value="posta.kakaranet.com"}),
+    nsm_db:put(#config{key="smtp/port",     value=465}),
+    nsm_db:put(#config{key="smtp/with_ssl", value=true}),
+
+    %% accounts
+    nsm_db:put(#config{key="accounts/default_quota", value=2000}),
+    nsm_db:put(#config{key="accounts/quota_limit/soft",  value=-30}),
+    nsm_db:put(#config{key="accounts/quota_limit/hard",  value=-100}),
+
+    %%  purchases
+    nsm_db:put(#config{key= "purchase/notifications/email",  value=["maxim@synrc.com"]}),
+
+    %%  delivery
+    nsm_db:put(#config{key= "delivery/notifications/email",  value=["maxim@synrc.com"]}),
+
+    %% tournament, elimination, Tour time limit
+    nsm_db:put(#config{key= "games/okey/trn/elim/tour_time_limit/1", value = 30*60*1000}),
+    nsm_db:put(#config{key= "games/okey/trn/elim/tour_time_limit/2", value = 30*60*1000}),
+    nsm_db:put(#config{key= "games/okey/trn/elim/tour_time_limit/3", value = 30*60*1000}),
+    nsm_db:put(#config{key= "games/okey/trn/elim/tour_time_limit/4", value = 25*60*1000}),
+    nsm_db:put(#config{key= "games/okey/trn/elim/tour_time_limit/5", value = 25*60*1000}),
+    nsm_db:put(#config{key= "games/okey/trn/elim/tour_time_limit/6", value = 25*60*1000}),
+    nsm_db:put(#config{key= "games/okey/trn/elim/tour_time_limit/7", value = 20*60*1000}),
+    nsm_db:put(#config{key= "games/okey/trn/elim/tour_time_limit/8", value = 20*60*1000}).
+
+% put
+
+-spec put(tuple() | [tuple()]) -> ok.
+put(Record) ->
+%    ?INFO("db:put ~p",[Record]),
+    DBA=?DBA,
+    DBA:put(Record).
+
+
+put_if_none_match(Record) ->
+%    ?INFO("db:put_if_none_match ~p",[Record]),
+    DBA=?DBA,
+    DBA:put_if_none_match(Record).
+
+% update
+
+update(Record, Meta) ->
+%    ?INFO("db:update ~p",[Record]),
+    DBA=?DBA,
+    DBA:update(Record, Meta).
+
+% get
+
+-spec get(atom(), term()) -> {ok, tuple()} | {error, not_found | duplicated}.
+get(RecordName, Key) ->
+    DBA=?DBA,
+    case C = DBA:get(RecordName, Key) of
+    {ok,_R} ->
+%        ?INFO("db:get ~p,", [{RecordName, Key}]),
+        C;
+    A -> A
+    end.
+
+get_for_update(RecordName, Key) ->
+%    ?INFO("db:get_for_update ~p,", [{RecordName, Key}]),
+    DBA=?DBA,
+    DBA:get_for_update(RecordName, Key).
+
+get(RecordName, Key, Default) ->
+    DBA=?DBA,
+    case DBA:get(RecordName, Key) of
+	{ok,{RecordName,Key,Value}} ->
+	    ?INFO("db:get config value ~p,", [{RecordName, Key, Value}]),
+	    {ok,Value};
+	{error, _B} ->
+	    ?INFO("db:get new config value ~p,", [{RecordName, Key, Default}]),
+	    DBA:put({RecordName,Key,Default}),
+	    {ok,Default}
+    end.
+
+get_config(Key, Default) -> {ok, Value} = get(config, Key, Default), Value.
+get_word(Word) -> get(ut_word,Word).
+get_translation({Lang,Word}) -> DBA=?DBA, DBA:get_translation({Lang,Word}).
+
+% delete
+
+delete(Keys) -> DBA=?DBA, DBA:delete(Keys).
+delete(Tab, Key) -> ?INFO("db:delete ~p:~p",[Tab, Key]), DBA=?DBA,DBA:delete(Tab, Key).
+delete_by_index(Tab, IndexId, IndexVal) -> DBA=?DBA,DBA:delete_by_index(Tab, IndexId, IndexVal).
+% select
+
+multi_select(RecordName, Keys) -> DBA=?DBA,DBA:multi_select(RecordName, Keys).
+select(From, PredicateFunction) -> ?INFO("db:select ~p, ~p",[From,PredicateFunction]), DBA=?DBA, DBA:select(From, PredicateFunction).
+count(RecordName) -> DBA=?DBA,DBA:count(RecordName).
+all(RecordName) -> DBA=?DBA,DBA:all(RecordName).
+all_by_index(RecordName, Index, IndexValue) -> DBA=?DBA,DBA:all_by_index(RecordName, Index, IndexValue).
+
+% id generator
+
+next_id(RecordName) -> DBA=?DBA,DBA:next_id(RecordName).
+next_id(RecordName, Incr) -> DBA=?DBA,DBA:next_id(RecordName, Incr).
+next_id(RecordName, Default, Incr) -> DBA=?DBA,DBA:next_id(RecordName, Default, Incr).
+
+% browser counter
+
+delete_browser_counter_older_than(MinTS) -> DBA=?DBA,DBA:delete_browser_counter_older_than(MinTS).
+browser_counter_by_game(Game) -> DBA=?DBA,DBA:browser_counter_by_game(Game).
+
+% invites
+
+unused_invites() -> DBA=?DBA,DBA:unused_invites().
+user_by_verification_code(Code) -> DBA=?DBA,DBA:user_by_verification_code(Code).
+user_by_facebook_id(FBId) -> DBA=?DBA,DBA:user_by_facebook_id(FBId).
+user_by_email(Email) -> DBA=?DBA,DBA:user_by_email(Email).
+user_by_username(Name) -> DBA=?DBA,DBA:user_by_username(Name).
+add_invite_to_issuer(User, O) -> DBA=?DBA,DBA:add_invite_to_issuer(User, O).
+invite_code_by_issuer(User) -> DBA=?DBA,DBA:invite_code_by_issuer(User).
+invite_code_by_user(User) -> DBA=?DBA,DBA:invite_code_by_user(User).
+
+% game info
+
+get_save_tables(Id) -> DBA=?DBA,DBA:get_save_tables(Id).
+save_game_table_by_id(Id) -> DBA=?DBA,DBA:save_game_table_by_id(Id).
+
+% feeds
+
+feed_add_direct_message(FId, User, To, EntryId, Desc, Medias) -> DBA=?DBA,DBA:feed_add_direct_message(FId, User, To, EntryId, Desc, Medias).
+feed_add_entry(FId, User, EntryId, Desc, Medias) -> DBA=?DBA,DBA:feed_add_entry(FId, User, EntryId, Desc, Medias).
+feed_add_entry(FId, User, To, EntryId, Desc, Medias, Type, SharedBy) -> DBA=?DBA,DBA:feed_add_entry(FId, User, To, EntryId, Desc, Medias, Type, SharedBy).
+acl_add_entry(AclId, Accessor, Action) -> DBA=?DBA,DBA:acl_add_entry(AclId, Accessor, Action).
+acl_entries(AclId) -> DBA=?DBA,DBA:acl_entries(AclId).
+entry_by_id(EntryId) -> DBA=?DBA,DBA:entry_by_id(EntryId).
+comment_by_id(CommentId) -> DBA=?DBA,DBA:comment_by_id(CommentId).
+comments_by_entry({_EId, _FId} = EntryId) -> DBA=?DBA,DBA:comments_by_entry(EntryId).
+entries_in_feed(FeedId) -> DBA=?DBA,DBA:entries_in_feed(FeedId, undefined, all).
+entries_in_feed(FeedId, Count) -> DBA=?DBA,DBA:entries_in_feed(FeedId, undefined, Count).
+entries_in_feed(FeedId, StartFrom, Count) -> DBA=?DBA, DBA:entries_in_feed(FeedId, StartFrom, Count).
+add_comment(FId, User, EntryId, ParentComment, CommentId, Content, Medias) -> DBA=?DBA, DBA:feed_add_comment(FId, User, EntryId, ParentComment, CommentId, Content, Medias).
+feed_direct_messages(FId, StartFrom, Count) ->  DBA=?DBA, DBA:entries_in_feed(FId, StartFrom, Count).
+
+% tournaments
+
+tournament_waiting_queue(TID) -> DBA=?DBA, DBA:tournament_waiting_queue(TID).
+join_tournament(UID,TID) -> DBA=?DBA, DBA:join_tournament(UID,TID).
+leave_tournament(UID,TID) -> DBA=?DBA, DBA:leave_tournament(UID,TID).
+tournament_pop_waiting_player(TID) -> DBA=?DBA, DBA:tournament_pop_waiting_player(TID).
+%play_record_add_entry(TeamId, UserId, Tournament, GameId) -> DBA=?DBA, DBA:play_record_add_entry(TeamId, UserId, Tournament, GameId).
+user_tournaments(UID) -> DBA=?DBA, DBA:user_tournaments(UID).
+
+groups_184_update() -> % predefined group creation
+    nsm_groups:create_group("ahmettez", "kakaranet", "Kakaranet", "Kakaranet'e Hoşgeldiniz", public),
+    nsm_groups:create_group("ahmettez", "yeniler", "Yeniler", "So, you must be new here.", public).
+
+subscriptions_update() -> % public beta
+    catch(nsm_db:delete(subscription,"kikiri")),
+
+    Subscriptions = nsm_db:all(subscription),
+    lists:foreach(fun (Subscription) ->
+
+       case Subscription of
+
+            {subscription,User,ToList} ->
+
+       ?INFO("User, ToList: ~p ~p",[User,ToList]),
+       lists:foreach(fun(Sub) ->
+          case Sub of
+             {subscription,User,To} ->
+                  ?INFO("User, To: ~p ~p",[User,To]),
+                  NewSubs = lists:foldl(fun({subscription,A,T},Acc) ->
+                            ?INFO("User: ~p",[A]),
+                            R = nsm_db:get(user,T),
+                            case R of
+                                  {ok,{user,_,_,_,_,_,Name,Surname,_,_,_,_,_,_,_,_,_,_,_,_}} ->
+				            Acc ++ [{subscription,A,T,[Name,32,Surname]}];
+                                   {error,_} -> Acc
+                             end
+                  end,[],ToList),
+                  nsm_db:put({subscription,User,NewSubs,undefined}),
+                  ok;
+             _ -> io:format("Unknown: ~p",[Sub])
+          end
+       end, ToList);
+
+           _ -> io:format("new: ~p",[Subscription])
+
+       end
+
+    end, Subscriptions).
+
+subscriptions_to_subs() ->
+    [[nsm_db:put(#subs{who=Who, whom=Whom}) || #subscription{who=Who, whom=Whom} <- Subs] 
+    || #subscription{who=_, whom=Subs} <- nsm_db:all(subscription)].
+
+%% mebership purchases
+
+add_transaction_to_user(User, Tx) -> DBA=?DBA, DBA:add_transaction_to_user(User, Tx).
+get_purchases_by_user(User, Count, States) -> DBA=?DBA, DBA:get_purchases_by_user(User, Count, States).
+get_purchases_by_user(User, StartFromPurchase, Count, States) -> DBA=?DBA, DBA:get_purchases_by_user(User, StartFromPurchase, Count, States).
+
+%% invitation tree
+
+%% @doc Put invite to tree. Parent is user who has invited User.
+
+-spec put_into_invitation_tree(Parent::string()|{root}, User::string(), InviteCode::string()) -> ok.
+
+put_into_invitation_tree(Parent, User, InviteCode) -> DBA=?DBA, DBA:put_into_invitation_tree(Parent, User, InviteCode).
+
+
+%% @doc build invitaion tree. Depth is shows how many levels of children will be
+%%      returned. Children will de abbed to children field of the #invitaion_tree
+%%      record.
+
+-spec invitation_tree(StartFrom::string()|{root}, Depth::integer()|all) ->
+          [#invitation_tree{}].
+
+invitation_tree(StartFrom, Depth) -> DBA=?DBA, DBA:invitation_tree(StartFrom, Depth).
+
+fast_timeouts() ->
+    nsm_db:put({config,"games/okey/robot_delay_normal",100}),
+    nsm_db:put({config,"games/okey/challenge_timeout_normal",5000}),
+    nsm_db:put({config,"games/okey/turn_timeout_normal",200}).
+
+make_admin(User) ->
+    {ok,U} = nsm_db:get(user, User),
+    nsm_db:put(U#user{type = admin}),
+    nsm_acl:define_access({user, U#user.username}, {feature, admin}, allow),
+    nsm_acl:define_access({user_type, admin}, {feature, admin}, allow),
+    ok.
+
+make_rich(User) -> 
+    Q = nsm_db:get_config("accounts/default_quota",  300),
+    nsm_accounts:transaction(User, ?CURRENCY_QUOTA, Q * 100, #ti_default_assignment{}),
+    nsm_accounts:transaction(User, ?CURRENCY_KAKUSH, Q, #ti_default_assignment{}),
+    nsm_accounts:transaction(User, ?CURRENCY_KAKUSH_CURRENCY, Q * 2, #ti_default_assignment{}).
+
+feed_create() ->
+    FId = nsm_db:next_id("feed", 1),
+    ok = nsm_db:put(#feed{id = FId} ),
+    FId.
+
+create_team(Name) ->
+    TID = nsm_db:next_id("team",1),
+    ok = nsm_db:put(Team = #team{id=TID,name=Name}),
+    TID.
+
+
+list_to_term(String) ->
+    {ok, T, _} = erl_scan:string(String++"."),
+    case erl_parse:parse_term(T) of
+        {ok, Term} ->
+            Term;
+        {error, Error} ->
+            Error
+    end.
+
+list_buckets() ->
+    [list_to_term(B) || B <- nsm_riak:dir()].
+
+save_db(Path) ->
+    Data = lists:append([all(B) || B <- list_buckets()]),
+    salode:save(Path, Data).
+
+load_db(Path) ->
+    nsm_riak:clean(),
+    add_seq_ids(),
+    AllEntries = salode:load(Path),
+    [{_,_,{_,Handler}}] = ets:lookup(config, "riak_client"),
+    [case is_tuple(E) of
+        false -> skip;
+        true ->
+            case element(1, E) of
+                gifts_categories -> %%%%%%%%%%%%%%%%%%%% gifts 
+                    ?INFO("GiftCat: ~p",[E]),
+                    Id = E#gifts_category.id,
+                    ParentId = E#gifts_category.parent,
+                    Obj1 = riak_object:new(<<"gifts_categories">>, term_to_binary(Id), E),
+                    Indices = [{"bucket_bin", <<"gifts_categories">>},
+                               {"catparent_bin", term_to_binary(ParentId)}],
+                    Meta = dict:store(<<"index">>, Indices, dict:new()),
+                    Obj2 = riak_object:update_metadata(Obj1, Meta),
+                    ok = Handler:put(Obj2, []);
+                gift -> 
+                    ?INFO("Gift: ~p",[E]),
+                    Id = E#gift.id,
+                    Cats = E#gift.categories,
+                    Obj1 = riak_object:new(<<"gifts">>, term_to_binary(Id), E),
+                    Indices = [{"bucket_bin", <<"gifts">>} |
+                             [{"categoty_bin", term_to_binary(CatId)} ||
+                              CatId <- lists:usort(Cats)]],
+                    Meta = dict:store(<<"index">>, Indices, dict:new()),
+                    Obj2 = riak_object:update_metadata(Obj1, Meta),
+                    ok = Handler:put(Obj2, []);
+                transaction ->
+                    ?INFO("Tx: ~p",[E]),
+                        case E of {transaction,Id,T,Am,R,A,C,I} ->
+                         Tx = #transaction{id = Id, commit_time = T,  amount = Am, remitter =R, acceptor = A, currency =C, info = I},
+                         %add_transaction_to_user(A,Tx);
+                         put(Tx);
+                         _ -> skip 
+                        end;
+                group ->
+                    ?INFO("Grp: ~p",[E]),
+                        case E of {group,A1,A2,A3,A4,A5,A6,A7,A8} ->
+                         Grp = {group,A1,A2,A3,A4,A5,A6,A7,A8,0,0},
+                         put(Grp);
+                         _ -> put(E)
+                        end;
+                user ->
+                    ?INFO("Usr: ~p",[E]),
+                        case E of {user,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,
+                                        A11,A12,A13,A14,A15,A16,A17,A18,A19,A20,
+                                        A21,A22,A23} ->
+                                   Usr = {user,A1,A2,A3,undefined,A4,A5,A6,A7,A8,A9,A10,
+                                        A11,A12,A13,A14,A15,A16,A17,A18,A19,A20,
+                                        A21,A22,A23},
+                         put(Usr);
+                         _ -> put(E)
+                        end;
+%                tournament -> skip;
+                user_address -> 
+                    ?INFO("UA: ~p",[E]),
+                        case E of
+                            {user_address, UId, A, C, D, PC} -> put({user_address, UId, A, C, D, PC, "", ""});
+                            {user_address, UId, A, C, D, PC, P} -> put({user_address, UId, A, C, D, PC, P, ""});
+                            _ -> put(E)
+                        end;
+                play_record -> 
+                    ?INFO("PR: ~p",[E]),
+                        case E of {play_record, Who, _Id, Tournament, Team, Game_id, Entry_id, _Score_points, _Next, _Prev} ->
+                            PR = {play_record, Who, Tournament, Team, Game_id, Who, 0, 0, 0, 0, []},
+                        put(PR);
+                        _ -> put(E)
+                        end;
+                membership_purchase ->
+                    ?INFO("Mp: ~p",[E]),
+                        case E of {membership_purchase,Id,T,User,S,Pkg,Time,Time2,L,Type} ->
+                         Mp = #membership_purchase{id = Id, external_id = T,
+                                                   user_id = User, state = S,
+                                                   membership_package = Pkg, next = undefined,
+                                                   prev = undefined, start_time = Time,
+                                                   end_time = Time2, state_log = L,
+                                                   info = Type},
+
+                         put(Mp);
+                         _ -> skip 
+                        end;
+                _ -> %%%%%%%%%%%%%%%%%%%% all the rest
+                    ?INFO("Reg: ~p",[E]),
+                    put(E) 
+            end
+    end || E <- AllEntries].
+
+make_paid_fake(UId) ->
+    put({user_purchase, UId, "fake_purchase"}).
+
+

+ 24 - 0
src/store_app.erl

@@ -0,0 +1,24 @@
+-module(store_app).
+-behaviour(application).
+-export([start/2, stop/1, wait_vnodes/0]).
+
+wait_riak() ->
+    case nsm_db:put({test,ok}) of
+         ok -> stop;
+         _ -> wait_riak()
+    end.
+
+wait_vnodes() ->
+    case riak:client_connect(node()) of
+    {ok,C} ->
+    case C:get(<<"test">>,<<"ok">>,[]) of
+         {error,{insufficient_vnodes,_,_,_}} -> wait_vnodes();
+         _ -> stop
+    end;
+    _ -> error end.
+
+start(_StartType, _StartArgs) ->
+    nsm_db_sup:start_link().
+
+stop(_State) ->
+    ok.

+ 312 - 0
src/store_mnesia.erl

@@ -0,0 +1,312 @@
+-module(store_mnesia).
+-author('Fernando Benavides <fernando.benavides@inakanetworks.com>').
+-include("config.hrl").
+-include("user.hrl").
+-include("feed.hrl").
+-include("acl.hrl").
+-include("invite.hrl").
+-include("attachment.hrl").
+-include("table.hrl").
+-include("uri_translator.hrl").
+-include_lib("stdlib/include/qlc.hrl").
+-compile(export_all).
+
+start() ->
+    mnesia:start(),
+    mnesia:change_table_copy_type(schema, node(), disc_copies).
+
+stop() -> mnesia:stop().
+initialize() -> mnesia:create_schema([node()]).
+delete() -> mnesia:delete_schema([node()]).
+
+init_db() ->
+    ok = create_table(acl, record_info(fields, acl), [{storage, permanent}]),
+    ok = create_table(comment, record_info(fields, comment), [{storage, permanent}, {type, ordered_set}]),
+    ok = add_table_index(comment, entry_id),
+    ok = create_table(feed, record_info(fields, feed), [{storage, permanent}]),
+    ok = create_table(entry, record_info(fields, entry), [{storage, permanent}, {type, ordered_set}]),
+    ok = add_table_index(entry, feed_id),
+    ok = add_table_index(entry, entry_id),
+    ok = add_table_index(entry, from),
+    ok = create_table(forget_password, record_info(fields, forget_password), [{storage, permanent}]),
+    ok = create_table(prohibited, record_info(fields, prohibited), [{storage, permanent}, {type, bag}]),
+    ok = create_table(group_member, record_info(fields, group_member), [{storage, permanent}, {type, bag}]),
+    ok = create_table(group_member_rev, record_info(fields, group_member_rev), [{storage, permanent}, {type, bag}]),
+    ok = create_table(group, record_info(fields, group), [{storage, permanent}]),
+    ok = add_table_index(group_member, id),
+    ok = create_table(invite_code, record_info(fields, invite_code), [{storage, permanent}]),
+    ok = add_table_index(invite_code, issuer),
+    ok = add_table_index(invite_code, created_user),
+    ok = create_table(user, record_info(fields, user), [{storage, permanent}]),
+    ok = create_table(user_status, record_info(fields, user_status), [{storage, permanent}]),
+    ok = create_table(subscription, record_info(fields, subscription), [{storage, permanent}, {type, bag}]),
+    ok = create_table(subscription_rev, record_info(fields, subscription_rev), [{storage, permanent}, {type, bag}]),
+    ok = add_table_index(user, verification_code),
+    ok = add_table_index(user, facebook_id),
+    ok = add_table_index(user, email),
+    ok = create_table(uploads, record_info(fields, uploads), [{storage, permanent}, {type, set}]),
+    ok = create_table(save_game_table, record_info(fields, save_game_table), [{storage, permanent}, {type, bag}]),
+    ok = add_table_index(save_game_table, id),
+    ok = create_table(id_seq, record_info(fields, id_seq), [{storage, permanent}]),
+    ok = create_table(feature, record_info(fields, feature), [{storage, permanent}]),
+    ok = create_table(ut_word, record_info(fields, ut_word), [{storage, permanent}, {type, bag}]),
+    ok = create_table(ut_translation, record_info(fields, ut_translation), [{storage, permanent}, {type, ordered_set}]),
+    nsm_membership_packages:create_storage(),
+    ok.
+
+put(Records) when is_list(Records) -> void(fun() -> lists:foreach(fun mnesia:write/1, Records) end);
+put(Record) -> put([Record]).
+
+delete(Keys) when is_list(Keys) -> void(fun() -> lists:foreach(fun mnesia:delete_object/1, Keys) end);
+delete(Keys) -> delete([Keys]).
+delete(Tab, Key) -> mnesia:transaction(fun()-> mnesia:delete({Tab, Key}) end), ok.
+
+multi_select(RecordName, Keys) when is_list(Keys) -> flatten(fun() -> [mnesia:read({RecordName, Key}) || Key <- Keys] end).
+
+select(From, PredicateFunction) when is_function(PredicateFunction) -> exec(qlc:q([Record || Record <- mnesia:table(From), apply(PredicateFunction, [Record])]));
+select(From, [{where, Fn}, {order, {Idx, Order}}]) -> exec(qlc:q([R || R <- qlc:keysort(Idx, mnesia:table(From), [{order, Order}]), apply(Fn, [R])]));
+select(From, [{where, Fn}, {order, {Idx, Order}}, {limit, {1, Length}}]) ->
+    {atomic, Recs} = mnesia:transaction(fun()->
+        QC = qlc:cursor(qlc:q(
+            [R || R <- qlc:keysort(Idx, mnesia:table(From), [{order, Order}]), apply(Fn, [R])])),
+        Ret = qlc:eval(qlc:next_answers(QC, Length)),
+        qlc:delete_cursor(QC),
+        Ret
+    end),
+    Recs;
+
+select(From, [{where, Fn}, {order, {Idx, Order}}, {limit, {Offset, Length}}]) ->
+    {atomic, Recs} = mnesia:transaction(fun()->
+        QC = qlc:cursor(qlc:q(
+            [R || R <- qlc:keysort(Idx, mnesia:table(From), [{order, Order}]), apply(Fn, [R])])),
+        qlc:next_answers(QC, Offset - 1),
+        Ret = qlc:eval(qlc:next_answers(QC, Length)),
+        qlc:delete_cursor(QC),
+        Ret
+    end),
+    Recs;
+select(RecordName, Key) ->
+    many(fun() -> mnesia:read({RecordName, Key}) end).
+
+get(RecordName, Key) -> just_one(fun() -> mnesia:read(RecordName, Key) end).
+
+get_word(Word) ->
+    case mnesia:transaction(fun() -> mnesia:match_object(#ut_word{english = Word, lang = '_', word = Word}) end) of
+        {atomic, []} -> {error, not_found};
+        {atomic, [R]} -> {ok, R};
+        {atomic, [A|_]} -> A;
+        _ -> {error, not_found}
+    end.
+
+get_translation({Lang, Word}) -> get(ut_translation, {Lang, Word}).
+count(RecordName) -> mnesia:table_info(RecordName, size).
+
+all(RecordName) ->
+    flatten(fun() ->
+                    Lists = mnesia:all_keys(RecordName),
+                    [ mnesia:read({RecordName, G}) || G <- Lists ]
+            end).
+
+next_id(RecordName) ->
+    next_id(RecordName, 1).
+
+next_id(RecordName, Incr) ->
+    [RecordStr] = io_lib:format("~p",[RecordName]),
+    mnesia:dirty_update_counter({id_seq, RecordStr}, Incr).
+
+unused_invites() ->
+    {atomic, Result} =
+        mnesia:transaction(
+          fun() ->
+                  length(mnesia:match_object(#invite_code{created_user=undefined, _='_'}))
+          end),
+    Result.
+
+user_by_verification_code(Code) -> just_one(fun() -> mnesia:match_object(#user{verification_code = Code, _='_'}) end).
+user_by_facebook_id(FBId) -> just_one(fun() -> mnesia:index_read(user, FBId, facebook_id) end).
+user_by_email(Email) -> just_one(fun() -> mnesia:index_read(user, Email, email) end).
+user_by_username(Name) -> just_one(fun() -> mnesia:match_object(#user{username = Name, _='_'}) end).
+invite_code_by_issuer(User) -> just_one(fun() -> mnesia:match_object(#invite_code{issuer = User, _='_'}) end).
+invite_code_by_user(User) -> just_one(fun() -> mnesia:match_object(#invite_code{created_user = User, _='_'}) end).
+
+add_to_group(UId, GId, Type) ->
+    store:put([#group_member{who = UId,
+                                 group = GId,
+                                 id = {UId, GId},
+                                 type = Type},
+                   #group_member_rev{group = GId,
+                                     who = UId,
+                                     type = Type}]).
+
+remove_from_group(UId, GId) ->
+    throw({error, needs_fix}),
+    Type = zzzz,
+    store:delete([#group_member{who = UId,
+                                    group = GId,
+                                    id = {UId, GId},
+                                    type = Type},
+                      #group_member_rev{group = GId,
+                                        who = UId,
+                                        type = Type}]).
+
+list_membership(#user{username = UId}) -> list_membership(UId);
+list_membership(UId) when is_list(UId) -> store:select(group_member, UId).
+list_membership_count(#user{username = UId}) -> list_membership_count(UId);
+list_membership_count(UId) when is_list(UId) -> [{G, length(list_group_users(element(3, G)))} || G <-store:select(group_member, UId)].
+list_group_users(GId) -> store:select(group_member_rev, GId).
+membership(UserId, GroupId) -> just_one(fun() -> mnesia:match_object(#group_member{id = {UserId, GroupId}, _='_'}) end).
+get_save_tables(UId) -> store:select(save_game_table,UId).
+ave_game_table_by_id(Id) -> just_one(fun() -> mnesia:match_object(#save_game_table{id = Id, _='_'}) end).
+
+just_one(Fun) ->
+    case mnesia:transaction(Fun) of
+        {atomic, []} -> {error, not_found};
+        {atomic, [R]} -> {ok, R};
+        {atomic, [_|_]} -> {error, duplicated};
+        _ -> {error, not_found}
+    end.
+
+flatten(Fun) ->
+    case mnesia:transaction(Fun) of
+        {atomic, R} -> lists:flatten(R);
+        _ -> []
+    end.
+
+many(Fun) ->
+    case mnesia:transaction(Fun) of
+        {atomic, R} -> R;
+        _ -> []
+    end.
+
+void(Fun) ->
+    case mnesia:transaction(Fun) of
+        {atomic, ok} -> ok;
+        {aborted, Error} -> {error, Error}
+    end.
+
+create_table(Record, RecordInfo,  Opts0) ->
+    Attr = [{attributes, RecordInfo}],
+    Opts = transform_opts(Opts0),
+    AllOpts = lists:concat([Opts, Attr]),
+    case mnesia:create_table(Record, lists:flatten(AllOpts)) of
+        {atomic,ok}                          -> ok;
+        {aborted, {already_exists, Record}}  -> ok;
+        {aborted, Err}                       -> {error, Err}
+    end.
+
+add_table_index(Record, Field) ->
+    case mnesia:add_table_index(Record, Field) of
+        {atomic, ok}                        -> ok;
+        {aborted,{already_exists,Record,_}} -> ok;
+        {aborted, Err}                       -> {error, Err}
+    end.
+
+transform_opts(Opts) -> transform_opts(Opts, []).
+transform_opts([], Acc) -> lists:reverse(Acc);
+transform_opts([{storage, Value} | Rest], Acc0) ->
+    NewOpts = storage_to_mnesia_type(Value),
+    Acc = [NewOpts | Acc0],
+    transform_opts(Rest, Acc);
+transform_opts([Other | Rest], Acc0) ->
+    Acc = [Other | Acc0],
+    transform_opts(Rest, Acc).
+
+storage_to_mnesia_type(permanent) -> {disc_copies, [node()]};
+storage_to_mnesia_type(temporary) -> {ram_copies, [node()]};
+storage_to_mnesia_type(ondisk) -> {disc_only_copies, [node()]}.
+
+exec(Q) -> F = fun() -> qlc:e(Q) end, {atomic, Val} = mnesia:transaction(F), Val.
+
+subscribe_user(MeId, FrId) -> store:put([#subscription{who = MeId, whom = FrId},
+                              #subscription_rev{whom = FrId, who = MeId}]).
+
+remove_subscription(MeId, FrId) -> store:delete([#subscription{who = MeId, whom = FrId},
+                                   #subscription_rev{whom = FrId, who = MeId}]).
+
+list_subscriptions(#user{username = UId}) -> list_subscriptions(UId);
+list_subscriptions(UId) when is_list(UId) -> select(subscription,UId).
+list_subscription_me(UId) -> select(subscription_rev, UId).
+
+is_user_subscribed(Who, Whom) ->
+    case select(subscription,fun(#subscription{who=W1,whom=W2}) when W1=:=Who,W2=:=Whom->true;(_)->false end) of
+        [] -> false
+        ;_ -> true
+    end.
+
+% feeds
+
+feed_add_direct_message(FId, User, Desc, Medias) ->
+    EId = store:next_id(entry, 1),
+    Entry  = #entry{id = {EId, FId},
+                    entry_id = EId,
+                    feed_id = FId,
+                    from = User,
+                    media = Medias,
+                    created_time = now(),
+                    description = Desc,
+                    type = {user, direct}},
+
+    ModEntry = feedformat:format(Entry),
+    case store:put(ModEntry) of
+        ok ->
+            {ok, ModEntry}
+        % ;Error -> {error, Error} %% put always return true
+    end.
+
+feed_add_entry(FId, User, Desc, Medias) ->
+    EId = store:next_id(entry, 1),
+    Entry  = #entry{id = {EId, FId},
+                    entry_id = EId,
+                    feed_id = FId,
+                    from = User,
+                    media = Medias,
+                    created_time = now(),
+                    description = Desc},
+
+    ModEntry = feedformat:format(Entry),
+    case store:put(ModEntry) of
+        ok ->
+            {ok, ModEntry}
+        % ;Error -> {error, Error} %% put always return true
+    end.
+
+entry_by_id(EntryId) -> just_one(fun() -> mnesia:match_object(#entry{entry_id = EntryId, _='_'}) end).
+comment_by_id(CommentId) -> just_one(fun() -> mnesia:match_object(#comment{comment_id = CommentId, _='_'}) end).
+comments_by_entry(EntryId) -> lists:reverse(many(fun() -> mnesia:match_object(#comment{entry_id = EntryId, _='_'}) end)).
+feed_entries(FeedId) -> feed_entries(FeedId, '_').
+feed_entries(FeedId, UserId) -> lists:reverse(many(fun() -> mnesia:match_object(#entry{feed_id = FeedId, from = UserId, _='_'}) end)).
+feed_entries(FId, Page, PageAmount) -> entries_in_feed(FId, Page, PageAmount).
+feed_entries(FId, Page, PageAmount, CurrentUser, CurrentFId) -> entries_in_feed(FId, Page, PageAmount, CurrentUser, CurrentFId).
+
+entries_in_feed(FId, 1, PageAmount) ->
+    F=fun(X) when element(4,X)=:=FId-> true;(_)->false end,
+    store:select(entry,[{where, F},{order, {1, descending}},{limit, {1,PageAmount}}]);
+entries_in_feed(FId, Page, PageAmount) when is_integer(Page), Page>1->
+    Offset=(Page-1)*PageAmount,
+    F=fun(X) when element(4,X)=:=FId-> true;(_)->false end,
+    store:select(entry,[{where, F},{order, {1, descending}},{limit, {Offset,PageAmount}}]).
+entries_in_feed(FId, 1, PageAmount, CurrentUser, CurrentFId) ->
+    F=fun(X) when element(4,X)=:=FId andalso (element(9,X)=:={user,normal} orelse element(1,element(9,X))=:=system)-> true;
+         (X) when element(5,X)=:=CurrentUser,element(9,X)=:={user,direct}-> true;
+         (X) when element(4,X)=:=CurrentFId,element(9,X)=:={user,direct} -> true;
+         (_)->false end,
+    store:select(entry,[{where, F},{order, {1, descending}},{limit, {1,PageAmount}}]);
+entries_in_feed(FId, Page, PageAmount, CurrentUser, CurrentFId) when is_integer(Page), Page>1->
+    Offset=(Page-1)*PageAmount,
+    F=fun(X) when element(4,X)=:=FId andalso (element(9,X)=:={user,normal} orelse element(1,element(9,X))=:=system)-> true;
+         (X) when element(5,X)=:=CurrentUser,element(9,X)=:={user,direct}-> true;
+         (X) when element(4,X)=:=CurrentFId,element(9,X)=:={user,direct} -> true;
+         (_)->false end,
+    store:select(entry,[{where, F},{order, {1, descending}},{limit, {Offset,PageAmount}}]).
+
+feed_direct_messages(_FId, Page, PageAmount, CurrentUser, CurrentFId) when is_integer(Page), Page>0->
+    Offset= case (Page-1)*PageAmount of
+        0 -> 1
+        ;M-> M
+    end,
+    F=fun(X) when element(5,X)=:=CurrentUser,element(9,X)=:={user,direct}-> true;
+         (X) when element(4,X)=:=CurrentFId,element(9,X)=:={user,direct} -> true;
+         (_)->false end,
+    store:select(entry,[{where, F},{order, {1, descending}},{limit, {Offset,PageAmount}}]).
+
+acl_add_entry(_A,_B,_C) -> ok.

+ 1298 - 0
src/store_riak.erl

@@ -0,0 +1,1298 @@
+-module(store_riak).
+-author('Maxim Sokhatsky <maxim@synrc.com>').
+-include("users.hrl").
+-include("feeds.hrl").
+-include("acls.hrl").
+-include("invites.hrl").
+-include("attachments.hrl").
+-include("meetings.hrl").
+-include("membership_packages.hrl").
+-include("accounts.hrl").
+-include("log.hrl").
+-include_lib("stdlib/include/qlc.hrl").
+-compile(export_all).
+
+-define(BUCKET_INDEX, "bucket_bin").
+-define(MD_INDEX, <<"index">>).
+-define(COUNTERS_BUCKET_ID_SEQ, <<"id_seq">>).
+
+delete() -> ok.
+start() -> ok.
+stop() -> stopped.
+
+initialize() ->
+    C = riak:client_connect(?RIAKSERVER_NODE),
+    ets:new(config, [named_table,{keypos,#config.key}]),
+    ets:insert(config, #config{ key = "riak_client", value = C}),
+    ok.
+
+
+init_indexes() ->
+    C = riak_client(),
+    ok = C:set_bucket(t_to_b(mhits), [{backend, leveldb_backend}]),
+    ok = C:set_bucket(t_to_b(affiliates_rels), [{backend, leveldb_backend}]),
+    ok = C:set_bucket(t_to_b(affiliates_contracts), [{backend, leveldb_backend}]),
+    ok = C:set_bucket(t_to_b(affiliates_purchases), [{backend, leveldb_backend}]),
+    ok = C:set_bucket(t_to_b(affiliates_contract_types), [{backend, leveldb_backend}]),
+    ok = C:set_bucket(?COUNTERS_BUCKET_ID_SEQ, [{backend, leveldb_backend}]),
+    ok = C:set_bucket(t_to_b(subs), [{backend, leveldb_backend}]),
+    ok = C:set_bucket(t_to_b(group_subs), [{backend, leveldb_backend}]),
+    ok = C:set_bucket(t_to_b(user_bought_gifts), [{backend, leveldb_backend}]),
+    ok = C:set_bucket(t_to_b(play_record), [{backend, leveldb_backend}]),
+    ok = nsm_gifts_db:init_indexes().
+
+init_db() ->
+%    ?INFO("~w:init_db/0: started", [?MODULE]),
+    C = riak_client(),
+    ok = nsm_affiliates:init_db(),
+    ok = nsm_gifts_db:init_db(),
+%    ?INFO("~w:init_db/0: done", [?MODULE]),
+    ok.
+
+dir() ->
+    C = riak_client(),
+    {ok,Buckets} = C:list_buckets(),
+    [binary_to_list(X)||X<-Buckets].
+
+clean() ->
+    riak_clean(mhits),
+    riak_clean(affiliates_contracts), riak_clean(affiliates_rels), riak_clean(affiliates_counters), riak_clean(affiliates_purchases), riak_clean(affiliates_contract_types),
+    riak_clean(gifts_counters), riak_clean(gifts_config), riak_clean(gifts),
+    riak_clean(play_record), riak_clean(player_scoring), riak_clean(scoring_record), riak_clean(personal_score), riak_clean(pointing_rule),
+    riak_clean(tournament), riak_clean(team),
+    riak_clean(membership_package), riak_clean(user_purchase), riak_clean(membership_purchase),
+    riak_clean(group),
+    riak_clean(browser_counter),
+    riak_clean(invite_code), riak_clean(forget_password),
+    riak_clean(user), riak_clean(user_by_email), riak_clean(user_by_facebook_id), riak_clean(user_address),
+    riak_clean(uploads),
+    riak_clean(acl), riak_clean(acl_entry),
+    riak_clean(account), riak_clean(transaction),
+    riak_clean(feature),
+    riak_clean(table),
+    riak_clean(config),
+    riak_clean(user_transaction),
+    riak_clean(save_game_table),
+    riak_clean(save_table),
+    riak_clean(game_table),
+    riak_clean(entry), riak_clean(likes), riak_clean(one_like), riak_clean(feed), riak_clean(comment),
+    riak_clean(group_member), riak_clean(group_member_rev), riak_clean(subscription), riak_clean(subscription_rev),
+    riak_clean(subs), riak_clean(group_subs), riak_clean(user_bought_gifts),
+    riak_clean(ut_word), riak_clean(ut_translation),
+    riak_clean(active_users_top),
+    riak_clean(user_counter),
+    riak_clean(twitter_oauth),
+    riak_clean(facebook_oauth),
+    riak_clean(hidden_feed),
+    riak_clean("unsuported"),
+    riak_clean("__riak_client_test__"),
+    riak_clean(id_seq),
+    C=riak_client(),
+    C:list_buckets().
+
+riak_clean(Table) when is_list(Table)->
+    C = riak_client(),
+    {ok,Keys}=C:list_keys(erlang:list_to_binary(Table)),
+    [ C:delete(erlang:list_to_binary(Table),Key) || Key <- Keys];
+riak_clean(Table) ->
+    C = riak_client(),
+    [TableStr] = io_lib:format("~p",[Table]),
+    {ok,Keys}=C:list_keys(erlang:list_to_binary(TableStr)),
+    [ store:delete(Table,key_to_bin(Key)) || Key <- Keys].
+
+% Convert Erlang records to Riak objects
+make_object(T, Class) ->
+    Obj1 = make_obj(T, Class),
+    Bucket = r_to_b(T),
+    Indices = [{?BUCKET_INDEX, Bucket} | make_indices(T)], %% Usefull only for level_db buckets
+    Meta = dict:store(?MD_INDEX, Indices, dict:new()),
+    Obj2 = riak_object:update_metadata(Obj1, Meta),
+    Obj2.
+
+%% Record to bucket id
+r_to_b(R) -> t_to_b(element(1,R)).
+
+%% Table id to bucket id
+t_to_b(T) -> 
+    %% list_to_binary(atom_to_list(T)). FIXME: This should be after we eleminate {transaction,_} tables
+                                                % no. We use complex keys for leveldb now
+    [StringBucket] = io_lib:format("~p",[T]),
+    erlang:list_to_binary(StringBucket).
+
+%% Create indicies for the record
+make_indices(#mhits{word = Word, ip = IP, date = Date}) -> [{<<"mhits_date_bin">>, key_to_bin(Date)},
+                                                            {<<"mhits_word_date_bin">>, key_to_bin({Word, Date})},
+                                                            {<<"mhits_word_ip_date_bin">>, key_to_bin({Word,IP,Date})},
+                                                            {<<"mhits_word_bin">>, key_to_bin(Word)},
+                                                            {<<"mhits_word_ip_bin">>, key_to_bin({Word,IP})},
+                                                            {<<"mhits_ip_bin">>, key_to_bin(IP)},
+                                                            {<<"mhits_ip_date_bin">>, key_to_bin({IP, Date})}];
+make_indices(#affiliates_contracts{owner = OwnerId}) -> [{<<"owner_bin">>, key_to_bin(OwnerId)}];
+make_indices(#affiliates_purchases{owner_id = OwnerId, contract_id = ContractId}) -> [{<<"owner_bin">>, key_to_bin(OwnerId)},
+                                                                                      {<<"contract_bin">>, key_to_bin(ContractId)}];
+make_indices(#affiliates_rels{user = OwnerId, affiliate = OwnerId}) -> [{<<"owner_bin">>, key_to_bin(OwnerId)},
+                                                                        {<<"affiliate_bin">>, key_to_bin(1)}];
+make_indices(#affiliates_rels{affiliate = OwnerId}) -> [{<<"owner_bin">>, key_to_bin(OwnerId)}];
+make_indices(#subs{who=Who, whom=Whom}) -> [{<<"subs_who_bin">>, key_to_bin(Who)},
+                                            {<<"subs_whom_bin">>, key_to_bin(Whom)}];
+make_indices(#group_subs{user_id=UId, group_id=GId}) -> [{<<"group_subs_user_id_bin">>, key_to_bin(UId)},
+                                            {<<"group_subs_group_id_bin">>, key_to_bin(GId)}];
+make_indices(#play_record{who=UId, tournament=TId, team=TEId}) -> [{<<"play_record_who_bin">>, key_to_bin(UId)},
+                                            {<<"play_record_tournament_bin">>, key_to_bin(TId)}];
+make_indices(#user_bought_gifts{username=UId}) -> [{<<"user_bought_gifts_username_bin">>, key_to_bin(UId)}];
+
+make_indices(_Record) -> [].
+
+
+make_obj(#mhits{word=W, ip=IP, date=D}=T, mhits) -> riak_object:new(r_to_b(T), key_to_bin({W,IP,D}), T);
+make_obj(T, affiliates_contract_types) -> riak_object:new(r_to_b(T), key_to_bin(T#affiliates_contract_types.id), T);
+make_obj(T, affiliates_contracts) -> riak_object:new(r_to_b(T), key_to_bin(T#affiliates_contracts.id), T);
+make_obj(T, affiliates_look_perms) -> riak_object:new(r_to_b(T), key_to_bin(T#affiliates_look_perms.user_id), T);
+make_obj(T, affiliates_purchases) -> #affiliates_purchases{contract_id = ContractId, user_id = UserId} = T,
+                                     riak_object:new(r_to_b(T), key_to_bin({ContractId, UserId}), T);
+make_obj(T, affiliates_rels) -> riak_object:new(r_to_b(T), key_to_bin(T#affiliates_rels.user), T);
+make_obj(T, subs) -> [Key] = io_lib:format("~p", [{T#subs.who, T#subs.whom}]), riak_object:new(r_to_b(T), list_to_binary(Key), T);
+make_obj(T, group_subs) -> [Key] = io_lib:format("~p", [{T#group_subs.user_id, T#group_subs.group_id}]), riak_object:new(r_to_b(T), list_to_binary(Key), T);
+make_obj(T, play_record) -> [Key] = io_lib:format("~p", [{T#play_record.who, T#play_record.tournament}]), riak_object:new(r_to_b(T), list_to_binary(Key), T);
+make_obj(T, user_bought_gifts) -> [Key] = io_lib:format("~p", [{T#user_bought_gifts.username, T#user_bought_gifts.timestamp}]), riak_object:new(r_to_b(T), list_to_binary(Key), T);
+make_obj(T, account) -> [Key] = io_lib:format("~p", [T#account.id]), riak_object:new(<<"account">>, list_to_binary(Key), T);
+make_obj(T, feed) -> riak_object:new(<<"feed">>, list_to_binary(integer_to_list(T#feed.id)), T);
+make_obj(T, user) -> riak_object:new(<<"user">>, list_to_binary(T#user.username), T);
+make_obj(T, user_address) -> riak_object:new(<<"user_address">>, list_to_binary(T#user_address.username), T);
+make_obj(T, game_table) -> riak_object:new(<<"game_table">>, list_to_binary(T#game_table.id), T);
+make_obj(T, player_scoring) -> riak_object:new(<<"player_scoring">>, list_to_binary(T#player_scoring.id), T);
+make_obj(T, scoring_record) -> riak_object:new(<<"scoring_record">>, list_to_binary(integer_to_list(T#scoring_record.id)), T);
+make_obj(T, personal_score) -> riak_object:new(<<"personal_score">>, list_to_binary(T#personal_score.uid), T);
+make_obj(T, save_game_table) -> riak_object:new(<<"save_game_table">>, list_to_binary(integer_to_list(T#save_game_table.id)), T);
+make_obj(T, feature) -> riak_object:new(<<"feature">>, list_to_binary(T#feature.name), T);
+make_obj(T, config) -> riak_object:new(<<"config">>, list_to_binary(T#config.key), T);
+make_obj(T = {user_by_email, _User, Email}, user_by_email) -> riak_object:new(<<"user_by_email">>, list_to_binary(Email), T);
+make_obj(T = {user_by_verification_code, _User, Code}, user_by_verification_code) -> riak_object:new(<<"user_by_verification_code">>, list_to_binary(Code), T);
+make_obj(T = {user_by_facebook_id, _User, FB}, user_by_facebook_id) -> riak_object:new(<<"user_by_facebook_id">>, list_to_binary(FB), T);
+make_obj(T, forget_password) -> riak_object:new(<<"forget_password">>, list_to_binary(T#forget_password.token), T);
+make_obj(T, entry) -> [Key] = io_lib:format("~p", [T#entry.id]), riak_object:new(<<"entry">>, list_to_binary(Key), T);
+make_obj(T, comment) -> [Key] = io_lib:format("~p", [T#comment.id]), riak_object:new(<<"comment">>, list_to_binary(Key), T);
+make_obj(T = {_,Key,_}, id_seq) -> riak_object:new(<<"id_seq">>, key_to_bin(Key), T);
+make_obj(T, group) -> riak_object:new(<<"group">>, list_to_binary(T#group.username), T);
+make_obj(T, tournament) -> riak_object:new(<<"tournament">>, list_to_binary(integer_to_list(T#tournament.id)), T);
+make_obj(T, team) -> riak_object:new(<<"team">>, list_to_binary(integer_to_list(T#team.id)), T);
+make_obj(T, acl) -> [Key] = io_lib:format("~p", [T#acl.id]), riak_object:new(<<"acl">>, list_to_binary(Key), T);
+make_obj(T, acl_entry) -> [Key] = io_lib:format("~p", [T#acl_entry.id]), riak_object:new(<<"acl_entry">>, list_to_binary(Key), T);
+make_obj(T, group_member) -> riak_object:new(<<"group_member">>, list_to_binary(T#group_member.who), T);
+make_obj(T, group_member_rev) -> riak_object:new(<<"group_member_rev">>, list_to_binary(T#group_member_rev.group), T);
+make_obj(T, subscription) -> riak_object:new(<<"subscription">>, list_to_binary(T#subscription.who), T);
+make_obj(T, subscription_rev) -> riak_object:new(<<"subscription_rev">>, list_to_binary(T#subscription_rev.whom), T);
+make_obj(T, user_ignores) -> riak_object:new(<<"user_ignores">>, list_to_binary(T#user_ignores.who), T);
+make_obj(T, user_ignores_rev) -> riak_object:new(<<"user_ignores_rev">>, list_to_binary(T#user_ignores_rev.whom), T);
+make_obj(T, ut_word) -> riak_object:new(<<"ut_word">>, list_to_binary(T#ut_word.english), T);
+make_obj(T, ut_translation) -> {Lang, English} = T#ut_translation.source, riak_object:new(<<"ut_translation">>, list_to_binary(Lang ++ "_" ++ English), T);
+make_obj(T, membership_package) -> riak_object:new(<<"membership_package">>, list_to_binary(T#membership_package.id), T);
+make_obj(T, membership_purchase) -> riak_object:new(<<"membership_purchase">>, list_to_binary(T#membership_purchase.id), T);
+make_obj(T, user_purchase) -> riak_object:new(<<"user_purchase">>, list_to_binary(T#user_purchase.user), T);
+make_obj(T, pointing_rule) -> [Key] = io_lib:format("~p", [T#pointing_rule.id]), riak_object:new(<<"pointing_rule">>, list_to_binary(Key), T);
+make_obj(T, transaction) -> riak_object:new(<<"transaction">>, list_to_binary(T#transaction.id), T);
+make_obj(T, user_transaction) -> riak_object:new(<<"user_transaction">>, key_to_bin(T#user_transaction.user), T);
+make_obj(T = {feed_blocked_users, UserId, _BlockedUsers}, feed_blocked_users) -> riak_object:new(<<"feed_blocked_users">>, list_to_binary(UserId), T);
+make_obj(T, uploads) -> [Key] = io_lib:format("~p", [T#uploads.key]), riak_object:new(<<"uploads">>, list_to_binary(Key), T);
+make_obj(T, invite_code) -> riak_object:new(<<"invite_code">>, list_to_binary(T#invite_code.code), T);
+make_obj(T = {_,User,_}, invite_code_by_user) -> riak_object:new(<<"invite_code_by_user">>, list_to_binary(User), T);
+make_obj(T, invite_by_issuer) -> riak_object:new(<<"invite_by_issuer">>, list_to_binary(T#invite_by_issuer.user), T);
+make_obj(T, entry_likes) -> riak_object:new(<<"entry_likes">>, list_to_binary(T#entry_likes.entry_id), T);
+make_obj(T, user_likes) -> riak_object:new(<<"user_likes">>, list_to_binary(T#user_likes.user_id), T);
+make_obj(T, one_like) -> riak_object:new(<<"one_like">>, list_to_binary(integer_to_list(T#one_like.id)), T);
+make_obj(T, active_users_top) -> riak_object:new(<<"active_users_top">>, list_to_binary(integer_to_list(T#active_users_top.no)), T);
+make_obj(T, user_count) -> riak_object:new(<<"user_count">>, <<"user_count">>, T);
+make_obj(T, twitter_oauth) -> riak_object:new(<<"twitter_oauth">>, list_to_binary(T#twitter_oauth.user_id), T);
+make_obj(T, facebook_oauth) -> riak_object:new(<<"facebook_oauth">>, list_to_binary(T#facebook_oauth.user_id), T);
+make_obj(T, hidden_feed) -> riak_object:new(<<"hidden_feed">>, list_to_binary(integer_to_list(T#hidden_feed.id)), T);
+make_obj(T, user_etries_count) -> riak_object:new(<<"user_etries_count">>, list_to_binary(T#user_etries_count.user_id), T);
+make_obj(T, invitation_tree) ->
+    Key = case T#invitation_tree.user of
+        ?INVITATION_TREE_ROOT -> [K] = io_lib:format("~p", [T#invitation_tree.user]), K;
+        String -> String
+    end,
+    riak_object:new(<<"invitation_tree">>, list_to_binary(Key), T);
+make_obj(T, Unsupported) -> riak_object:new(<<"unsuported">>, term_to_binary(Unsupported), T).
+
+
+riak_client() -> [{_,_,{_,C}}] = ets:lookup(config, "riak_client"), C.
+
+-spec put(tuple() | [tuple()]) -> ok.
+put(Records) when is_list(Records) ->
+    lists:foreach(fun riak_put/1, Records);
+put(Record) ->
+    put([Record]).
+
+riak_put(Record) ->
+    Class = element(1,Record),
+    Object = make_object(Record, Class),
+    Riak = riak_client(),
+    Result = Riak:put(Object, [{allow_mult,false},{last_write_wins,true}]),
+    post_write_hooks(Class, Record, Riak),
+    Result.
+
+put_if_none_match(Record) ->
+    Class = element(1,Record),
+    Object = make_object(Record, Class),
+    Riak = riak_client(),
+    case Riak:put(Object, [if_none_match]) of
+        ok ->
+            post_write_hooks(Class, Record, Riak),
+            ok;
+        Error ->
+            Error
+    end.
+
+%% update(Record, Meta) -> ok | {error, Reason}
+update(Record, Object) ->
+    Class = element(1,Record),
+    %% Create pseudo new object and take its metadata to store in the updated object
+    NewObject = make_object(Record, Class),
+    NewKey = riak_object:key(NewObject),
+    case riak_object:key(Object) of
+        NewKey ->
+            MetaInfo = riak_object:get_update_metatdata(NewObject),
+            UpdObject2 = riak_object:update_value(Object, Record),
+            UpdObject3 = riak_object:update_metadata(UpdObject2, MetaInfo),
+            Riak = riak_client(),
+            case Riak:put(UpdObject3, [if_not_modified]) of
+                ok ->
+                    post_write_hooks(Class, Record, Riak),
+                    ok;
+                Error ->
+                    Error
+            end;
+        _ ->
+            {error, keys_not_equal}
+    end.
+
+
+post_write_hooks(Class,R,C) ->
+    case Class of
+        user -> case R#user.email of
+                    undefined -> nothing;
+                    _ -> C:put(make_object({user_by_email, R#user.username, R#user.email}, user_by_email))
+                end,
+                case R#user.verification_code of
+                    undefined -> nothing;
+                    _ -> C:put(make_object({user_by_verification_code, R#user.username, R#user.verification_code}, user_by_verification_code))
+                end,
+                case R#user.facebook_id of
+                  undefined -> nothing;
+                  _ -> C:put(make_object({user_by_facebook_id, R#user.username, R#user.facebook_id}, user_by_facebook_id))
+                end;
+
+        invite_code ->
+            #invite_code{created_user=User, issuer = Issuer} = R,
+
+            if Issuer =/= undefined,
+               User =/= undefined ->
+                   nsm_affiliates:invitation_hook(Issuer, User);
+               true -> do_nothing
+            end,
+
+            case R#invite_code.created_user of
+                undefined -> nothing;
+                User -> C:put(make_object({invite_code_by_user, User, R#invite_code.code}, invite_code_by_user))
+            end;
+
+        _ -> continue
+    end.
+
+% get
+
+-spec get(atom(), term()) -> {ok, tuple()} | {error, not_found | duplicated}.
+get(Tab, Key) ->
+    Bucket = t_to_b(Tab),
+    IntKey = key_to_bin(Key),
+    riak_get(Bucket, IntKey).
+
+riak_get(Bucket,Key) -> %% TODO: add state monad here for conflict resolution when not last_win strategy used
+    C = riak_client(),
+    RiakAnswer = C:get(Bucket,Key,[{last_write_wins,true},{allow_mult,false}]),
+    case RiakAnswer of
+        {ok, O} -> {ok,riak_object:get_value(O)};
+        X -> X
+    end.
+
+%% get_for_update(Tab, Key) -> {ok, Record, Meta} | {error, Reason}
+get_for_update(Tab, Key) ->
+    C = riak_client(),
+    case C:get(t_to_b(Tab), key_to_bin(Key), [{last_write_wins,true},{allow_mult,false}]) of
+        {ok, O} -> {ok, riak_object:get_value(O), O};
+        Error -> Error
+    end.
+
+% translations
+
+get_word(Word) ->
+    get(ut_word,Word).
+
+get_translation({Lang, Word}) ->
+    get(ut_translation, Lang ++ "_" ++ Word).
+
+% delete
+
+-spec delete(tuple() | [tuple()]) -> ok.
+delete(Keys) when is_list(Keys) ->
+    lists:foreach(fun mnesia:delete_object/1, Keys); % TODO
+delete(Keys) ->
+    delete([Keys]).
+
+-spec delete(atom(), term()) -> ok.
+delete(Tab, Key) ->
+    C = riak_client(),
+    Bucket = t_to_b(Tab),
+    IntKey = key_to_bin(Key),
+    C:delete(Bucket, IntKey),
+    ok.
+
+-spec delete_by_index(atom(), binary(), term()) -> ok.
+delete_by_index(Tab, IndexId, IndexVal) ->
+    Riak = riak_client(),
+    Bucket = t_to_b(Tab),
+    {ok, Keys} = Riak:get_index(Bucket, {eq, IndexId, key_to_bin(IndexVal)}),
+    [Riak:delete(Bucket, Key) || Key <- Keys],
+    ok.
+
+key_to_bin(Key) ->
+    if is_integer(Key) -> erlang:list_to_binary(integer_to_list(Key));
+       is_list(Key) -> erlang:list_to_binary(Key);
+       is_tuple(Key) -> [ListKey] = io_lib:format("~p", [Key]), erlang:list_to_binary(ListKey);
+       is_atom(Key) -> erlang:list_to_binary(erlang:atom_to_list(Key));
+       is_binary(Key) -> Key;
+       true ->  [ListKey] = io_lib:format("~p", [Key]), erlang:list_to_binary(ListKey)
+    end.
+
+% search
+
+-spec multi_select(atom(), [term()]) -> [tuple()].
+multi_select(_RecordName, _Keys) when is_list(_Keys) -> erlang:error(notimpl).
+%    [mnesia:read({RecordName, Key}) || Key <- Keys].
+
+select(RecordName, Pred) when is_function(Pred) ->
+	%% FIXME: bruteforce select
+	All = all(RecordName),
+	lists:filter(Pred, All);
+
+select(RecordName, Select) when is_list(Select) ->
+	%% FIXME: dummy select!
+	Where = proplists:get_value(where, Select, fun(_)->true end),
+	{Position, _Order} = proplists:get_value(order, Select, {1, descending}),
+
+	Limit = proplists:get_value(limit, Select, all),
+
+	Selected = select(RecordName, Where),
+	Sorted = lists:keysort(Position, Selected),
+
+	case Limit of
+		all ->
+			Sorted;
+		{Offset, Amoumt} ->
+			lists:sublist(Sorted, Offset, Amoumt)
+	end.
+
+
+-spec count(atom()) -> non_neg_integer().
+count(_RecordName) ->
+    erlang:length(all(_RecordName)).
+%   erlang:error(notimpl).
+%   mnesia:table_info(RecordName, size). % TODO
+
+-spec all(atom()) -> [tuple()].
+all(RecordName) ->
+    Riak = riak_client(),
+    [RecordStr] = io_lib:format("~p",[RecordName]),
+    RecordBin = erlang:list_to_binary(RecordStr),
+    {ok,Keys} = Riak:list_keys(RecordBin),
+    Results = [ get_record_from_table({RecordBin, Key, Riak}) || Key <- Keys ],
+    [ Object || Object <- Results, Object =/= failure ].
+
+%% all_by_index(Tab, IndexId, IndexVal) -> RecordsList
+all_by_index(Tab, IndexId, IndexVal) ->
+    Riak = riak_client(),
+    Bucket = t_to_b(Tab),
+    {ok, Keys} = Riak:get_index(Bucket, {eq, IndexId, key_to_bin(IndexVal)}),
+    F = fun(Key, Acc) ->
+                case Riak:get(Bucket, Key, []) of
+                    {ok, O} -> [riak_object:get_value(O) | Acc];
+                    {error, notfound} -> Acc
+                end
+        end,
+    lists:foldl(F, [], Keys).
+
+get_record_from_table({RecordBin, Key, Riak}) ->
+    case Riak:get(RecordBin, Key) of
+        {ok,O} -> riak_object:get_value(O);
+        X -> failure
+    end.
+
+% id generator
+
+-spec next_id(list()) -> pos_integer().
+next_id(CounterId) ->
+    next_id(CounterId, 1).
+
+-spec next_id(term(), integer()) -> pos_integer().
+next_id(CounterId, Incr) ->
+    next_id(CounterId, 0, Incr).
+
+-spec next_id(term(), integer(), integer()) -> pos_integer().
+%% Safe implementation of counters
+next_id(CounterId, Default, Incr) ->
+    Riak = riak_client(),
+    CounterBin = key_to_bin(CounterId),
+    {Object, Value, Options} =
+        case Riak:get(?COUNTERS_BUCKET_ID_SEQ, CounterBin, []) of
+            {ok, CurObj} ->
+                R = #id_seq{id = CurVal} = riak_object:get_value(CurObj),
+                NewVal = CurVal + Incr,
+                Obj = riak_object:update_value(CurObj, R#id_seq{id = NewVal}),
+                {Obj, NewVal, [if_not_modified]};
+            {error, notfound} ->
+                NewVal = Default + Incr,
+                Obj = riak_object:new(?COUNTERS_BUCKET_ID_SEQ, CounterBin, #id_seq{thing = CounterId, id = NewVal}),
+                {Obj, NewVal, [if_none_match]}
+        end,
+    case Riak:put(Object, Options) of
+        ok ->
+            Value;
+        {error, _} -> %% FIXME: Right reason(s) should be specified here
+            next_id(CounterId, Incr)
+    end.
+
+% browser counters
+
+-spec delete_browser_counter_older_than(pos_integer()) -> ok.
+delete_browser_counter_older_than(_MinTS) -> % TODO
+    [].
+%    MatchHead = #browser_counter{minute='$1', _ = '_'},
+%    Guard = {'<', '$1', MinTS},
+%    Result = '$_',
+%    List = mnesia:select(browser_counter, [{MatchHead, [Guard], [Result]}]),
+%          lists:foreach(fun(X) ->
+%                    mnesia:delete_object(X)
+%                end, List),
+%    List.
+
+unused_invites() -> % TODO
+    List = store:all(invite_code),
+    length([ #invite_code{created_user=undefined} || _Code <- List]).
+
+user_by_verification_code(Code) ->
+    R = case store:get(user_by_verification_code,Code) of
+	{ok,{_,User,_}} -> store:get(user,User);
+	Else -> Else
+	end,
+    R.
+
+user_by_facebook_id(FBId) ->
+    R = case store:get(user_by_facebook_id,FBId) of
+	{ok,{_,User,_}} -> store:get(user,User);
+	Else -> Else
+	end,
+    R.
+
+user_by_email(FB) ->
+    R = case store:get(user_by_email,FB) of
+	{ok,{_,User,_}} -> store:get(user,User);
+	Else -> Else
+	end,
+    R.
+
+user_by_username(Name) ->
+    case X = store:get(user,Name) of
+	{ok,_Res} -> X;
+	Else -> Else
+    end.
+
+%invite_code_by_issuer(User) ->
+%    case store:get(invite_code_by_issuer, User) of
+%        {ok, {invite_code_by_user, _, Code}} ->
+%            case store:get(invite_code, Code) of
+%                {ok, #invite_code{} = C} ->
+%                    [C];
+%                _ ->
+%                    []
+%            end;
+%        _ ->
+%            []
+%    end.
+
+invite_code_by_user(User) ->
+    case store:get(invite_code_by_user, User) of
+        {ok, {invite_code_by_user, _, Code}} ->
+            case store:get(invite_code, Code) of
+                {ok, #invite_code{} = C} ->
+                    [C];
+                _ ->
+                    []
+            end;
+        _ ->
+            []
+    end.
+
+
+% Todo: run in background
+update_user_name(UId,UName,Surname) ->
+    Name = case {UName,Surname} of
+        {undefined,undefined} -> UId;
+        _ -> io_lib:format("~s ~s", [UName, Surname])
+    end,
+    case store:get(group_member, UId) of
+    {error, notfound} -> ok;
+    {ok, #group_member{group=List}} ->
+        UpdateUserName = fun(#group_member{group=GId,type=Type}, _) ->
+            case store:get(group_member_rev, GId) of
+            {error, notfound} ->
+                store:put(#group_member_rev{group=GId,
+                                                who=[#group_member_rev{who=UId,who_name=Name,group=GId,type=Type}],
+                                                type=list});
+            {ok,#group_member_rev{who=Whos}} ->
+                NewWhos = lists:map(fun(#group_member_rev{who=U}=M) when U==UId->
+                                           M#group_member_rev{who_name=Name};(M)->M end, Whos),
+                store:put(#group_member_rev{group=GId, who=NewWhos, type=list})
+            end
+        end,
+        lists:foldl(UpdateUserName, undefined, List)
+    end.
+
+% game info
+
+get_save_tables(Id) ->
+    case store:get(save_game_table, Id) of
+	{error,notfound} -> [];
+	{ok,R} -> R
+    end.
+
+save_game_table_by_id(Id) -> store:get(save_game_table, Id).
+
+% subscriptions
+
+subscribe_user(MeId, FrId) ->
+    MeShow = case store:get(user, MeId) of
+        {ok, #user{name=MeName,surname=MeSur}} ->
+            io_lib:format("~s ~s", [MeName,MeSur]);
+        _Z ->
+            io:format("Get ~s: ~p~n", [MeId, _Z]),
+            undefined
+    end,
+    FrShow = case store:get(user, FrId) of
+        {ok, #user{name=FrName,surname=FrSur}} ->
+            io_lib:format("~s ~s", [FrName,FrSur]);
+        _ ->
+            undefined
+    end,
+    Rec = #subscription{who = MeId, whom = FrId, whom_name = FrShow},
+    case store:get(subscription, MeId) of
+        {error, notfound} ->
+            store:put(#subscription{who = MeId, whom = [Rec]});
+        {ok, #subscription{whom = List}} ->
+            case lists:member(Rec, List) of
+                false ->
+                    NewList =
+                        lists:keystore(FrId, #subscription.whom, List, Rec),
+                    store:put(#subscription{who = MeId, whom = NewList});
+                true ->
+                    do_nothing
+            end
+    end,
+    RevRec = #subscription_rev{whom=FrId, who=MeId, who_name=MeShow},
+    case store:get(subscription_rev, FrId) of
+        {error,notfound} ->
+            store:put(#subscription_rev{whom=FrId, who=[RevRec]});
+        {ok,#subscription_rev{who=RevList}} ->
+            case lists:member(RevRec, RevList) of
+                false ->
+                    NewRevList =
+                        lists:keystore(MeId, #subscription_rev.who, RevList, RevRec),
+                    store:put(#subscription_rev{whom=FrId, who=NewRevList});
+                true ->
+                    do_nothing
+            end
+    end,
+    ok.
+
+remove_subscription(MeId, FrId) ->
+    List = nsm_users:list_subscription(MeId),
+    Subs = [ Sub || Sub <- List, not(Sub#subscription.who==MeId andalso Sub#subscription.whom==FrId) ],
+    store:put(#subscription{who = MeId, whom = Subs}),
+    List2 = nsm_users:list_subscription_me(FrId),
+    Revs = [ Rev || Rev <- List2, not(Rev#subscription_rev.who==MeId andalso Rev#subscription_rev.whom==FrId) ],
+    store:put(#subscription_rev{who = Revs, whom = FrId}).
+
+list_subscriptions(#user{username = UId}) -> list_subscriptions(UId);
+list_subscriptions(UId) when is_list(UId) ->
+    case store:get(subscription, UId) of
+	{ok,#subscription{whom = C}} -> C;
+	_ -> []
+    end.
+
+list_subscription_me(UId) ->
+    case store:get(subscription_rev, UId) of
+	{ok,#subscription_rev{who = C}} -> C;
+	_ -> []
+    end.
+
+is_user_subscribed(Who,Whom) ->
+    case store:get(subscription, Who) of
+    {ok,#subscription{whom = W}} ->
+        lists:any(fun(#subscription{who=Who1, whom=Whom1}) -> Who1==Who andalso Whom1==Whom; (_)->false end, W);
+    _ -> false
+    end.
+
+
+% blocking user
+
+block_user(Who, Whom) ->
+    case store:get(user_ignores, Who) of
+        {error, notfound} ->
+            store:put(#user_ignores{who = Who, whom = [Whom]});
+        {ok, #user_ignores{whom = List}} ->
+            case lists:member(Whom, List) of
+                false ->
+                    NewList = [Whom | List],
+                    store:put(#user_ignores{who = Who, whom = NewList});
+                true ->
+                    do_nothing
+            end
+    end,
+    case store:get(user_ignores_rev, Whom) of
+        {error,notfound} ->
+            store:put(#user_ignores_rev{whom=Whom, who=[Who]});
+        {ok,#user_ignores_rev{who=RevList}} ->
+            case lists:member(Who, RevList) of
+                false ->
+                    NewRevList = [Who | RevList],
+                    store:put(#user_ignores_rev{whom=Whom, who=NewRevList});
+                true ->
+                    do_nothing
+            end
+    end,
+    ok.
+
+unblock_user(Who, Whom) ->
+    List = list_blocks(Who),
+    case lists:member(Whom, List) of
+        true ->
+            NewList = [ UID || UID <- List, UID =/= Whom ],
+            store:put(#user_ignores{who = Who, whom = NewList});
+        false ->
+            do_nothing
+    end,
+    List2 = list_blocked_me(Whom),
+    case lists:member(Who, List2) of
+        true ->
+            NewRevList = [ UID || UID <- List2, UID =/= Who ],
+            store:put(#user_ignores_rev{whom = Whom, who = NewRevList});
+        false ->
+            do_nothing
+    end.
+
+list_blocks(#user{username = UId}) -> list_blocks(UId);
+list_blocks(UId) when is_list(UId) ->
+    case store:get(user_ignores, UId) of
+        {ok,#user_ignores{whom = C}} -> C;
+        _ -> []
+    end.
+
+list_blocked_me(UId) ->
+    case store:get(user_ignores_rev, UId) of
+        {ok,#user_ignores_rev{who = C}} -> C;
+        _ -> []
+    end.
+
+is_user_blocked(Who, Whom) ->
+    case store:get(user_ignores, Who) of
+        {ok,#user_ignores{whom = List}} ->
+            lists:member(Whom, List);
+        _ -> false
+    end.
+
+% feeds
+
+feed_add_direct_message(FId,User,To,EntryId,Desc,Medias) -> feed_add_entry(FId,User,To,EntryId,Desc,Medias,{user,direct},"").
+feed_add_entry(FId,From,EntryId,Desc,Medias) -> feed_add_entry(FId,From,undefined,EntryId,Desc,Medias,{user,normal},"").
+feed_add_entry(FId, User, To, EntryId,Desc,Medias,Type,SharedBy) ->
+    %% prevent adding of duplicate records to feed
+    case store:entry_by_id({EntryId, FId}) of
+        {ok, _} -> ok;
+        _ -> do_feed_add_entry(FId, User, To, EntryId, Desc, Medias, Type, SharedBy)
+    end.
+
+do_feed_add_entry(FId, User, To, EntryId, Desc, Medias, Type, SharedBy) ->
+    {ok,Feed} = store:get(feed,erlang:integer_to_list(FId)),
+    Id = {EntryId, FId},
+    Next = undefined,
+    Prev = case Feed#feed.top of
+               undefined ->
+                   undefined;
+               X ->
+                   case store:get(entry, X) of
+                       {ok, TopEntry} ->
+                           EditedEntry = TopEntry#entry{next = Id},
+                           % update prev entry
+                           store:put(EditedEntry),
+                           TopEntry#entry.id;
+                       {error,notfound} ->
+                           undefined
+                   end
+           end,
+
+    store:put(#feed{id = FId, top = {EntryId, FId}}), % update feed top with current
+
+    Entry  = #entry{id = {EntryId, FId},
+                    entry_id = EntryId,
+                    feed_id = FId,
+                    from = User,
+                    to = To,
+                    type = Type,
+                    media = Medias,
+                    created_time = now(),
+                    description = Desc,
+                    raw_description = Desc,
+                    shared = SharedBy,
+                    next = Next,
+                    prev = Prev},
+
+    ModEntry = case catch feedformat:format(Entry) of
+                   {_, Reason} ->
+                       ?ERROR("feedformat error: ~p", [Reason]),
+                       Entry;
+                   #entry{} = ME ->
+                       ME
+               end,
+
+    store:put(ModEntry),
+    {ok, ModEntry}.
+
+feed_add_comment(FId, User, EntryId, ParentComment, CommentId, Content, Medias) ->
+    FullId = {CommentId, {EntryId, FId}},
+
+    Prev = case ParentComment of
+        undefined ->
+            {ok, Entry} = store:entry_by_id({EntryId, FId}),
+            {PrevC, E} = case Entry#entry.comments of
+                        undefined ->
+                            {undefined, Entry#entry{comments_rear = FullId}};
+                        Id ->
+                            {ok, PrevTop} = store:get(comment, Id),
+                            store:put(PrevTop#comment{next = FullId}),
+                            {Id, Entry}
+                   end,
+
+            store:put(E#entry{comments=FullId}),
+            PrevC;
+
+        _ ->
+            {ok, Parent} = store:get(comment, {{EntryId, FId}, ParentComment}),
+            {PrevC, CC} = case Parent#comment.comments of
+                        undefined ->
+                            {undefined, Parent#comment{comments_rear = FullId}};
+                        Id ->
+                            {ok, PrevTop} = store:get(comment, Id),
+                            store:put(PrevTop#comment{next = FullId}),
+                            {Id, Parent}
+                    end,
+            store:put(CC#comment{comments = FullId}),
+            PrevC
+    end,
+    Comment = #comment{id = FullId,
+                       author_id = User,
+                       comment_id = CommentId,
+                       entry_id = EntryId,
+                       raw_content = Content,
+                       content = Content,
+                       media = Medias,
+                       create_time = now(),
+                       prev = Prev,
+                       next = undefined
+                      },
+    store:put(Comment),
+    {ok, Comment}.
+
+add_transaction_to_user(UserId,Purchase) ->
+    {ok,Team} = case store:get(user_transaction, UserId) of
+                     {ok,T} -> {ok,T};
+                     _ -> ?INFO("user_transaction not found"),
+                          Head = #user_transaction{ user = UserId, top = undefined},
+                          {store:put(Head),Head}
+                end,
+
+    EntryId = Purchase#transaction.id, %store:next_id("membership_purchase",1),
+    Prev = undefined,
+    case Team#user_transaction.top of
+        undefined -> Next = undefined;
+        X -> case store:get(transaction, X) of
+                 {ok, TopEntry} ->
+                     Next = TopEntry#transaction.id,
+                     EditedEntry = #transaction {
+                           commit_time = TopEntry#transaction.commit_time,
+                           amount = TopEntry#transaction.amount,
+                           remitter = TopEntry#transaction.remitter,
+                           acceptor = TopEntry#transaction.acceptor,
+                           currency = TopEntry#transaction.currency,
+                           info = TopEntry#transaction.info,
+                           id = TopEntry#transaction.id,
+                           next = TopEntry#transaction.next,
+                           prev = EntryId },
+                    store:put(EditedEntry); % update prev entry
+                 {error,notfound} -> Next = undefined
+             end
+    end,
+
+    Entry  = #transaction{id = EntryId,
+                           commit_time = Purchase#transaction.commit_time,
+                           amount = Purchase#transaction.amount,
+                           remitter = Purchase#transaction.remitter,
+                           acceptor = Purchase#transaction.acceptor,
+                           currency = Purchase#transaction.currency,
+                           info = Purchase#transaction.info,
+                           next = Next,
+                           prev = Prev},
+
+    case store:put(Entry) of ok -> store:put(#user_transaction{ user = UserId, top = EntryId}), {ok, EntryId};
+                              Error -> ?INFO("Cant write transaction"), {failure,Error} end.
+
+add_purchase_to_user(UserId,Purchase) ->
+    {ok,Team} = case store:get(user_purchase, UserId) of
+                     {ok,T} -> ?INFO("user_purchase found"), {ok,T};
+                     _ -> ?INFO("user_purchase not found"),
+                          Head = #user_purchase{ user = UserId, top = undefined},
+                          {store:put(Head),Head}
+                end,
+
+    EntryId = Purchase#membership_purchase.id, %store:next_id("membership_purchase",1),
+    Prev = undefined,
+    case Team#user_purchase.top of
+        undefined -> Next = undefined;
+        X -> case store:get(membership_purchase, X) of
+                 {ok, TopEntry} ->
+                     Next = TopEntry#membership_purchase.id,
+                     EditedEntry = #membership_purchase{
+                           external_id = TopEntry#membership_purchase.external_id,
+                           user_id = TopEntry#membership_purchase.user_id,
+                           state = TopEntry#membership_purchase.state,
+                           membership_package = TopEntry#membership_purchase.membership_package,
+                           start_time = TopEntry#membership_purchase.start_time,
+                           end_time = TopEntry#membership_purchase.end_time,
+                           state_log = TopEntry#membership_purchase.state_log,
+                           info = TopEntry#membership_purchase.info,
+                           id = TopEntry#membership_purchase.id,
+                           next = TopEntry#membership_purchase.next,
+                           prev = EntryId},
+                    store:put(EditedEntry); % update prev entry
+                 {error,notfound} -> Next = undefined
+             end
+    end,
+
+    store:put(#user_purchase{ user = UserId, top = EntryId}), % update team top with current
+
+    Entry  = #membership_purchase{id = EntryId,
+                                  user_id = UserId,
+                                  external_id = Purchase#membership_purchase.external_id,
+                                  state = Purchase#membership_purchase.state,
+                                  membership_package = Purchase#membership_purchase.membership_package,
+                                  start_time = Purchase#membership_purchase.start_time,
+                                  end_time = Purchase#membership_purchase.end_time,
+                                  state_log = Purchase#membership_purchase.state_log,
+                                  info = Purchase#membership_purchase.info,
+                                  next = Next,
+                                  prev = Prev},
+
+    case store:put(Entry) of ok -> {ok, EntryId};
+                           Error -> ?INFO("Cant write purchase"), {failure,Error} end.
+
+invite_code_by_issuer(UserId) -> invite_code_by_issuer(UserId, undefined, 1000).
+
+invite_code_by_issuer(UserId, undefined, PageAmount) ->
+    case store:get(invite_by_issuer, UserId) of
+        {ok, O} when O#invite_by_issuer.top =/= undefined -> 
+                                invite_code_by_issuer(UserId, O#invite_by_issuer.top, PageAmount);
+        {error, notfound} -> []
+    end;
+invite_code_by_issuer(UserId, StartFrom, Limit) ->
+    case store:get(invite_code,StartFrom) of
+        {ok, #invite_code{next = N}=P} -> [ P | riak_traversal(invite_code, #invite_code.next, N, Limit)];
+        X -> []
+    end.
+
+add_invite_to_issuer(UserId,O) ->
+    {ok,Team} = case store:get(invite_by_issuer, UserId) of
+                     {ok,T} -> ?INFO("user inviters root found"), {ok,T};
+                     _ -> ?INFO("user invites root not found"),
+                          Head = #invite_by_issuer{ user = UserId, top = undefined},
+                          {store:put(Head),Head}
+                end,
+
+    EntryId = O#invite_code.code, 
+    Prev = undefined,
+    case Team#invite_by_issuer.top of
+        undefined -> Next = undefined;
+        X -> case store:get(invite_code, X) of
+                 {ok, TopEntry} ->
+                     Next = TopEntry#invite_code.code,
+                     EditedEntry = #invite_code{
+                           code = TopEntry#invite_code.code,
+                           create_date = TopEntry#invite_code.create_date,
+                           issuer = TopEntry#invite_code.issuer,
+                           recipient = TopEntry#invite_code.recipient,
+                           created_user = TopEntry#invite_code.created_user,
+                           next = TopEntry#invite_code.next,
+                           prev = EntryId},
+                    store:put(EditedEntry); % update prev entry
+                 {error,notfound} -> Next = undefined
+             end
+    end,
+
+    store:put(#invite_by_issuer{ user = UserId, top = EntryId}), % update team top with current
+
+    Entry  = #invite_code{ code = EntryId,
+                           create_date = O#invite_code.create_date,
+                           issuer = O#invite_code.issuer,
+                           recipient = O#invite_code.recipient,
+                           created_user = O#invite_code.created_user,
+                     next = Next,
+                     prev = Prev},
+
+    case store:put(Entry) of ok -> {ok, EntryId};
+                           Error -> ?INFO("Cant write invite_by_issuer"), {failure,Error} end.
+
+acl_add_entry(Resource, Accessor, Action) ->
+    Acl = case store:get(acl, Resource) of
+              {ok, A} ->
+                  A;
+              %% if acl record wasn't created already
+              {error, notfound} ->
+                  A = #acl{id = Resource, resource=Resource},
+                  store:put(A),
+                  A
+          end,
+
+    EntryId = {Accessor, Resource},
+
+    case store:get(acl_entry, EntryId) of
+        %% there is no entries for specified Acl and Accessor, we have to add it
+        {error, notfound} ->
+            Next = undefined,
+            Prev = case Acl#acl.top of
+                       undefined ->
+                           undefined;
+
+                       Top ->
+                           case store:get(acl_entry, Top) of
+                               {ok, TopEntry} ->
+                                   EditedEntry = TopEntry#acl_entry{next = EntryId},
+                                   store:put(EditedEntry), % update prev entry
+                                   TopEntry#acl_entry.id;
+
+                               {error, notfound} ->
+                                   undefined
+                           end
+                   end,
+
+            %% update acl with top of acl entries list
+            store:put(Acl#acl{top = EntryId}),
+
+            Entry  = #acl_entry{id = EntryId,
+                                entry_id = EntryId,
+                                accessor = Accessor,
+                                action = Action,
+                                next = Next,
+                                prev = Prev},
+
+            ok = store:put(Entry),
+            Entry;
+
+        %% if acl entry for Accessor and Acl is defined - just change action
+        {ok, AclEntry} ->
+            store:put(AclEntry#acl_entry{action = Action}),
+            AclEntry
+    end.
+
+
+join_tournament(UserId, TournamentId) ->
+    case store:get(user, UserId) of
+        {ok, User} ->
+            GP = case nsm_accounts:balance(UserId, ?CURRENCY_GAME_POINTS) of
+                     {ok, AS1} -> AS1;
+                     {error, _} -> 0 end,
+            K = case nsm_accounts:balance(UserId,  ?CURRENCY_KAKUSH) of
+                     {ok, AS2} -> AS2;
+                     {error, _} -> 0 end,
+            KC = case nsm_accounts:balance(UserId,  ?CURRENCY_KAKUSH_CURRENCY) of
+                     {ok, AS3} -> AS3;
+                     {error, _} -> 0 end,
+            Q = case nsm_accounts:balance(UserId,  ?CURRENCY_QUOTA) of
+                     {ok, AS4} -> AS4;
+                     {error, _} -> 0 end,
+            RN = nsm_users:user_realname(UserId),
+            store:put(#play_record{
+                 who = UserId,
+                 tournament = TournamentId,
+                 team = User#user.team,
+                 game_id = undefined, 
+		 other = now(),
+                 realname = RN,
+                 game_points = GP,
+                 kakush = K,
+                 kakush_currency = KC,
+                 quota = Q});
+        _ ->
+            ?INFO(" User ~p not found for joining tournament ~p", [UserId, TournamentId])
+    end.
+
+leave_tournament(UserId, TournamentId) ->
+    case store:get(play_record, {UserId, TournamentId}) of
+        {ok, _} -> 
+            store:delete(play_record, {UserId, TournamentId}),
+            leave_tournament(UserId, TournamentId); % due to WTF error with old records
+        _ -> ok
+    end.
+
+user_tournaments(UId) -> 
+    store:all_by_index(play_record, <<"play_record_who_bin">>, list_to_binary(UId)).
+
+tournament_waiting_queue(TId) ->
+    store:all_by_index(play_record, <<"play_record_tournament_bin">>, list_to_binary(integer_to_list(TId))).
+
+
+-spec entry_by_id(term()) -> {ok, #entry{}} | {error, not_found}.
+entry_by_id(EntryId) -> store:get(entry, EntryId).
+
+-spec comment_by_id({{EntryId::term(), FeedId::term()}, CommentId::term()}) -> {ok, #comment{}}.
+comment_by_id(CommentId) -> store:get(CommentId).
+
+-spec comments_by_entry(EId::{string(), term()}) -> [#comment{}].
+comments_by_entry({EId, FId}) ->
+    case store:entry_by_id({EId, FId}) of
+        {ok, #entry{comments_rear = undefined}} ->
+            [];
+        {ok, #entry{comments_rear = First}} ->
+            lists:flatten(read_comments_rev(First));
+        _ ->
+            []
+    end.
+
+purchases(UserId) -> purchases_in_basket(UserId, undefined, 1000).
+
+get_purchases_by_user(UserId, Count, States) -> get_purchases_by_user(UserId, undefined, Count, States).
+get_purchases_by_user(UserId, Start, Count, States) ->
+    List = purchases_in_basket(UserId, Start, Count),
+    case States == all of
+        true -> List;
+        false -> [P||P<-List, lists:member(P#membership_purchase.state, States)]
+    end.
+
+purchases_in_basket(UserId, undefined, PageAmount) ->
+    case store:get(user_purchase, UserId) of
+        {ok, O} when O#user_purchase.top =/= undefined -> 
+                                     purchases_in_basket(UserId, O#user_purchase.top, PageAmount);
+        {error, notfound} -> []
+    end;
+purchases_in_basket(UserId, StartFrom, Limit) ->
+    case store:get(membership_purchase,StartFrom) of
+        {ok, #membership_purchase{next = N}=P} -> [ P | riak_traversal(membership_purchase, #membership_purchase.next, N, Limit)];
+        X -> []
+    end.
+
+transactions(UserId) -> tx_list(UserId, undefined, 10000).
+
+tx_list(UserId, undefined, PageAmount) ->
+    case store:get(user_transaction, UserId) of
+        {ok, O} when O#user_transaction.top =/= undefined -> tx_list(UserId, O#user_transaction.top, PageAmount);
+        {error, notfound} -> []
+    end;
+tx_list(UserId, StartFrom, Limit) ->
+    case store:get(transaction,StartFrom) of
+        {ok, #transaction{next = N}=P} -> [ P | riak_traversal(transaction, #transaction.next, N, Limit)];
+        X -> []
+    end.
+
+read_comments(undefined) -> [];
+read_comments([#comment{comments = C} | Rest]) -> [read_comments(C) | read_comments(Rest)];
+read_comments(C) -> riak_traversal(comment, #comment.prev, C, all).
+
+read_comments_rev(undefined) -> [];
+read_comments_rev([#comment{comments = C} | Rest]) -> [read_comments_rev(C) | read_comments_rev(Rest)];
+read_comments_rev(C) -> riak_traversal(comment, #comment.next, C, all).
+
+riak_traversal( _, _, undefined, _) -> [];
+riak_traversal(_, _, _, 0) -> [];
+riak_traversal(RecordType, PrevPos, Next, Count)->
+    case nsm_riak:get(RecordType, Next) of
+        {error,notfound} -> [];
+        {ok, R} ->
+            Prev = element(PrevPos, R),
+            Count1 = case Count of C when is_integer(C) -> C - 1; _-> Count end,
+            [R | riak_traversal(RecordType, PrevPos, Prev, Count1)]
+    end.
+
+riak_read_acl_entries(_, undefined, Result) -> Result;
+riak_read_acl_entries(C, Next, Result) ->
+    NextStr = io_lib:format("~p",[Next]),
+    RA = C:get(<<"acl_entry">>,erlang:list_to_binary(NextStr)),
+    case RA of
+         {ok,RO} -> O = riak_object:get_value(RO), riak_read_acl_entries(C, O#acl_entry.prev, Result ++ [O]);
+         {error,notfound} -> Result
+    end.
+
+purge_feed(FeedId) ->
+    {ok,Feed} = store:get(feed,FeedId),
+    Removal = riak_entry_traversal(Feed#feed.top, -1),
+    [store:delete(entry,Id)||#entry{id=Id}<-Removal],
+    store:put(Feed#feed{top=undefined}).
+
+purge_unverified_feeds() ->
+    [purge_feed(FeedId) || #user{feed=FeedId,status=S,email=E} <- store:all(user),E==undefined].
+
+purge_system_messages() ->
+    [delete_system_messages(U,FeedId) || U=#user{feed=FeedId} <- store:all(user)].
+
+delete_system_messages(U,FeedId) ->
+    ?INFO("Cleaning ~p user feed from system messages",[U]),
+    {ok,Feed} = store:get(feed,FeedId),
+    Entries = riak_entry_traversal(Feed#feed.top, -1),
+    {Removal,Relink} = lists:partition(fun(#entry{type={_,Type}}) -> Type == system orelse Type == system_note end,Entries),
+    [store:delete(entry,Id)||#entry{id=Id}<-Removal],
+    Len = length(Relink),
+    case Len of
+         0 -> skip;
+         _ ->  Relinked = [begin E = lists:nth(N,Relink),
+                               {Next,Prev} = case N of 
+                                   1 -> case Len of
+                                             1 -> {undefined,undefined};
+                                             _ -> NextEntry = lists:nth(N+1,Relink),
+                                                 {undefined,NextEntry#entry.id}
+                                        end;
+                                 Len -> PrevEntry = lists:nth(N-1,Relink),
+                                       {PrevEntry#entry.id,undefined};
+                                   _ -> PrevEntry = lists:nth(N-1,Relink),
+                                        NextEntry = lists:nth(N+1,Relink),
+                                       {PrevEntry#entry.id,NextEntry#entry.id}
+                                end,
+                                E#entry{next = Next, prev = Prev}
+               end || N <- lists:seq(1,Len)],
+               Link = hd(Relinked),
+               store:put(Feed#feed{top=Link#entry.id}),
+               [store:put(X) || X <- Relinked] 
+    end,
+    Len.
+
+riak_entry_traversal(undefined, _) -> [];
+riak_entry_traversal(_, 0) -> [];
+riak_entry_traversal(Next, Count)->
+    case nsm_riak:get(entry, Next) of
+        {error,notfound} -> [];
+        {ok, R} ->
+            Prev = element(#entry.prev, R),
+            Count1 = case Count of 
+                C when is_integer(C) -> case R#entry.type of
+                    {_, system} -> C;   % temporal entries are entries too, but they shouldn't be counted
+                    {_, system_note} -> C;
+                    _ -> C - 1
+                end;
+                _-> Count 
+            end,
+            [R | riak_entry_traversal(Prev, Count1)]
+    end.
+
+entries_in_feed(FeedId, undefined, PageAmount) ->
+    case store:get(feed, FeedId) of
+        {ok, O} -> riak_entry_traversal(O#feed.top, PageAmount);
+        {error, notfound} -> []
+    end;
+entries_in_feed(FeedId, StartFrom, PageAmount) ->
+    %% construct entry unic id
+    case store:get(entry,{StartFrom, FeedId}) of
+        {ok, #entry{prev = Prev}} -> riak_entry_traversal(Prev, PageAmount);
+        _ -> []
+    end.
+
+acl_entries(AclId) ->
+    C = riak_client(),
+    [AclStr] = io_lib:format("~p",[AclId]),
+    RA = C:get(<<"acl">>, erlang:list_to_binary(AclStr)),
+    case RA of
+        {ok,RO} ->
+            O = riak_object:get_value(RO),
+            riak_read_acl_entries(C, O#acl.top, []);
+        {error, notfound} -> []
+    end.
+
+
+feed_direct_messages(_FId, Page, PageAmount, CurrentUser, CurrentFId) ->
+    Page, PageAmount, CurrentUser, CurrentFId,
+    [].
+
+-spec put_into_invitation_tree(Parent::string()|{root}, User::string(), InviteCode::string()) -> #invitation_tree{}.
+put_into_invitation_tree(Parent, User, InviteCode) ->
+    URecord =  #invitation_tree{user = User,
+                                invite_code = InviteCode,
+                                parent = Parent},
+
+    case store:get(invitation_tree, Parent) of
+        {ok, #invitation_tree{first_child = TopChild} = P} ->
+            store:put(P#invitation_tree{first_child = User}),
+            URecord1 = URecord#invitation_tree{next_sibling = TopChild},
+            store:put(URecord1),
+            URecord1;
+        _ ->
+            R = case Parent of
+                    ?INVITATION_TREE_ROOT ->
+                        #invitation_tree{user = ?INVITATION_TREE_ROOT};
+                    _ ->
+                        put_into_invitation_tree(?INVITATION_TREE_ROOT,
+                                                 Parent, undefined)
+                end,
+            store:put(R#invitation_tree{first_child = User}),
+            store:put(URecord),
+            URecord
+    end.
+
+
+-spec invitation_tree(StartFrom::string()|{root}, Depth::integer()|all) -> [#invitation_tree{}].
+invitation_tree(_, -1) -> [];
+invitation_tree(none, _) -> [];
+invitation_tree(UserId, Depth) ->
+    case store:get(invitation_tree, UserId) of
+        {ok, #invitation_tree{} = ITreeU} ->
+            FirstChild = ITreeU#invitation_tree.first_child,
+            SiblingId = ITreeU#invitation_tree.next_sibling,
+            Depth1 = case Depth of
+                         all -> Depth;
+                         _ -> Depth - 1
+                     end,
+            [ITreeU#invitation_tree{children=invitation_tree(FirstChild, Depth1)}|
+                                       invitation_tree(SiblingId, Depth)];
+        {error, _} -> []
+    end.
+
+%% @doc delete link to child from children list
+invitation_tree_delete_child_link(RootUser, User) ->
+    case invitation_tree(RootUser, 1) of
+        [#invitation_tree{first_child = User} = Root] ->
+            Root1 = Root#invitation_tree{children = []},
+            case store:get(invitation_tree, User) of
+                {ok, #invitation_tree{next_sibling = none}} ->
+                    store:put(Root1#invitation_tree{first_child = none});
+                {ok, #invitation_tree{next_sibling = Next}} ->
+                    store:put(Root1#invitation_tree{first_child = Next});
+                _ -> ok
+            end;
+
+        [#invitation_tree{children = Children}] ->
+            case
+                [E || #invitation_tree{next_sibling = NS, user = U} = E <- Children,
+                      NS == User orelse U == User] of
+
+                [Before, Exactly] ->
+                    NextSibling =  Exactly#invitation_tree.next_sibling,
+                    store:put(Before#invitation_tree{next_sibling = NextSibling,
+                                                         children = []});
+                _ -> ok
+            end;
+        _ -> ok
+    end.
+
+

+ 58 - 0
src/store_sup.erl

@@ -0,0 +1,58 @@
+-module(store_sup).
+-behaviour(supervisor).
+-export([start_link/0, stop_riak/0]).
+-export([init/1]).
+-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
+
+start_link() ->
+    supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+stop_riak() ->
+    application:stop(riak_kv), 
+    application:stop(riak_pipe),
+    application:stop(eleveldb),
+    application:stop(erlang_js),
+    application:stop(webmachine),
+    application:stop(mochiweb),
+    application:stop(bitcask).
+
+init([]) ->
+
+  RestartStrategy = one_for_one,
+    MaxRestarts = 1000,
+    MaxSecondsBetweenRestarts = 3600,
+
+    SupFlags = {RestartStrategy, MaxRestarts, MaxSecondsBetweenRestarts},
+
+    Restart = permanent,
+    Shutdown = 2000,
+    Type = worker,
+
+ case nsx_opt:get_env(nsp_srv,riak_srv_node,0) of
+    0 ->
+
+    error_logger:info_msg("Waiting for Riak to Start...."),
+    store:start(),
+    error_logger:info_msg("Waiting for Riak to Initialize...."),
+    store_app:wait_vnodes();
+    _ -> skip end,
+
+    store:initialize(),
+
+ case nsx_opt:get_env(nsp_srv,riak_srv_node,0) of
+    0 ->
+
+    store:init_indexes(),
+    case nsx_opt:get_env(store,sync_nodes,false) of
+         true -> [ error_logger:info_msg("Joined: ~p ~p~n", [N, riak_core:join(N)]) || N <- nsx_opt:get_env(store, nodes, []) -- [node()] ];
+         false -> skip
+    end,
+    case  nsx_opt:get_env(store,pass_init_db, true) of 
+         false -> store:init_db();
+         true -> pass
+    end;
+    _ -> skip
+          end,
+
+    {ok, { {one_for_one, 5, 10}, []} }.
+

+ 394 - 0
src/users.erl

@@ -0,0 +1,394 @@
+-module(users).
+-include("user.hrl").
+-include("accounts.hrl").
+-include("log.hrl").
+-include_lib("white_rabbit/include/nsm_mq.hrl").
+-compile(export_all).
+
+do_register(#user{username=U} = RegisterData0) ->
+  case check_register_data(RegisterData0) of
+    ok ->
+      HashedPassword = case RegisterData0#user.password of
+        undefined -> undefined;
+        PlainPassword -> utils:sha(PlainPassword)
+      end,
+      RegisterData = RegisterData0#user{
+        feed     = store:feed_create(),
+        direct   = store:feed_create(),
+        pinned   = store:feed_create(),
+        starred  = store:feed_create(),
+        password = HashedPassword},
+
+      store:put(RegisterData),
+      %nsx_msg:notify(["system", "put"], RegisterData),
+      nsm_accounts:create_account(U),
+      % assign quota
+      {ok, DefaultQuota} = store:get(config, "accounts/default_quota",  300),
+      nsm_accounts:transaction(U, ?CURRENCY_QUOTA, DefaultQuota, #ti_default_assignment{}),
+      %% init message queues infrastructure
+      init_mq(U, []),
+      login_posthook(U),
+      {ok, U};
+    {error, Error} -> {error, Error}
+  end.
+
+register(#user{username=U, email=Email, facebook_id = FbId} = RegisterData0) ->
+  FindUser = case check_username(U, FbId) of
+    {error, E} -> {error, E};
+    {ok, NewName} ->
+      case get_user({email, Email}) of
+        {error, _NotFound} -> {ok, NewName};
+        {ok, _} -> {error, email_taken}
+      end
+  end,
+
+  FindUser2 = case FindUser of
+    {ok, UserName} ->
+      case nsm_groups:get_group(UserName) of
+        {error, notfound} -> {ok, UserName}; % it means username is free
+        _ -> {error, username_taken}
+      end;
+    SomethingElse -> SomethingElse
+  end,
+
+  case FindUser2 of
+    {ok, Name} -> do_register(RegisterData0#user{username=Name});
+    {error, username_taken} -> {error, user_exist};
+    {error, email_taken} ->    {error, email_taken}
+  end.
+
+check_username(Name, FbId) ->
+  case get_user(Name) of
+    {error, notfound} -> {ok, Name};
+    {ok, User} when FbId =/= undefined ->
+      check_username(User#user.username  ++ integer_to_list(crypto:rand_uniform(0,10)), FbId);
+    {ok, _User}-> {error, username_taken}
+  end.
+
+% @doc
+% Removes user with all tigth connected entries relying on user_id
+delete_user(UserName) ->
+   % get User record
+   case get_user(UserName) of
+	{ok, User} ->
+	   %% remove from all groups
+       GIds = nsm_groups:list_groups_per_user(UserName),
+       [nsx_msg:notify(["subscription", "user", UserName, "remove_from_group"], {GId}) || GId <- GIds],
+	   %% remove from subcribtions
+	   S = list_subscr(User),
+	   F2U = [ {MeId, FrId} || #subscription{who = MeId, whom = FrId} <- S ],
+	   [ unsubscr_user(MeId, FrId) || {MeId, FrId} <- F2U ],
+	   [ unsubscr_user(FrId, MeId) || {MeId, FrId} <- F2U ],
+	   store:delete(user_status, UserName),
+	   store:delete(user, UserName),
+	   {ok, User};
+	E -> E
+   end.
+
+get_user({username, UserName}) -> store:user_by_username(UserName);
+get_user({facebook, FBId}) -> store:user_by_facebook_id(FBId);
+get_user({email, Email}) -> store:user_by_email(Email);
+get_user(UId) -> store:get(user, UId).
+
+get_all_users() -> store:all(user).
+
+subscribe(Who, Whom) ->
+    case is_user_blocked(Who, Whom) of
+        false ->
+            Record = #subs{who = Who, whom = Whom},
+            ok = store:put(Record),
+            subscribe_user_mq(user, Who, Whom);
+        true -> do_nothing
+    end.
+
+unsubscribe(Who, Whom) ->
+    case is_user_subscr(Who, Whom) of
+        true ->
+            ok = store:delete(subs, {Who, Whom}),
+            remove_subscription_mq(user, Who, Whom);
+        false ->
+            do_nothing
+    end.
+
+list_subscr(undefined)-> [];
+list_subscr(#user{username = UId}) -> list_subscr(UId);
+list_subscr(UId) when is_list(UId) -> lists:sort( store:all_by_index(subs, <<"subs_who_bin">>, list_to_binary(UId)) ).
+list_subscr(UId, PageNumber, PageAmount) when is_list(UId) -> 
+    Offset = case (PageNumber-1)*PageAmount of
+        I when is_integer(I), I>0 -> I+1;
+        _ -> 1
+	 end,
+	lists:sublist(list_subscr(UId), Offset, PageAmount).
+list_subscr_usernames(UId) -> [UserId || #subs{whom = UserId} <- list_subscr(UId)].
+
+list_subscr_me(#user{username = UId}) -> list_subscr_me(UId);
+list_subscr_me(UId) when is_list(UId) -> lists:sort( store:all_by_index(subs, <<"subs_whom_bin">>, list_to_binary(UId)) ).
+
+is_user_subscr(Who, Whom) ->
+    case store:get(subs, {Who, Whom}) of
+        {ok, _} -> ?INFO("User ~p is a friend of user ~p", [Whom, Who]), true;
+        _Response -> ?INFO("User ~p is not a friend of user ~p. Response: ~p", [Whom, Who, _Response]), false
+    end.
+
+update_after_login(User) -> %RPC to cleanup
+    Update =
+        case user_status(User) of
+            {error, status_info_not_found} ->
+                #user_status{username = User,
+                             last_login = erlang:now()};
+            {ok, UserStatus} ->
+                UserStatus#user_status{last_login = erlang:now()}
+        end,
+    store:put(Update).
+
+user_status(User) ->
+    case store:get(user_status, User) of
+        {ok, Status} ->
+            {ok, Status};
+        {error, notfound} ->
+            {error, status_info_not_found}
+    end.
+
+
+user_status(User, Key) ->
+    case user_status(User) of
+        {ok, Status0} ->
+            Fields = record_info(fields, user_status),
+            [user_status | List] = tuple_to_list(Status0),
+            Status = lists:zip(Fields, List),
+            {_, V} = lists:keyfind(Key, 1, Status),
+            {ok, V};
+        _ ->
+            {error, status_info_not_found}
+    end.
+
+user_status(User, Key, Value) ->
+    case user_status(User) of
+        {ok, Status0} ->
+            Fields = record_info(fields, user_status),
+            [user_status | List] = tuple_to_list(Status0),
+            Status = lists:zip(Fields, List),
+            NewStatus0 = lists:keyreplace(Key, 1, Status, {Key, Value}),
+            NewStatus = [user_status | element(2, lists:unzip(NewStatus0))],
+            store:put(list_to_tuple(NewStatus));
+        _ ->
+            {error, status_info_not_found}
+    end.
+
+get_user_by_feed_id(Fid) ->
+    store:select(user, fun(#user{feed=F}) when F=:=Fid-> true;(_)->false end).
+
+search_user("") ->
+	store:all(user);
+search_user(Str) ->
+    store:select(user,
+        fun(#user{email=E}) when E=:=Str-> true;
+        (#user{username=N}) when N=:=Str-> true;
+        (#user{name=N})     when N=:=Str-> true;
+        (#user{surname=S})  when S=:=Str-> true;
+        (#user{surname=S, name=N})      ->
+            case S ++ " " ++ N of
+                Sum when Sum=:=Str -> true;
+                _                  -> false
+            end;
+        (_)                             ->false
+        end).
+
+get_user_game_status(User) ->
+    case store:get(user_game_status, User) of
+        {ok, #user_game_status{status=Status}} -> Status
+        ;_                                     -> "offline"
+    end.
+
+set_user_game_status(User, Status) -> store:put(#user_game_status{user=User, status=Status}).
+
+% TODO: game_session:525 move real DB operation from here behind rabbit
+%       two level: first message received by session pid
+%                   then it goes to change db bg worker
+
+block(Who, Whom) ->
+    ?INFO("~w:block_user/2 Who=~p Whom=~p", [?MODULE, Who, Whom]),
+    unsubscr_user(Who, Whom),
+    store:block_user(Who, Whom),
+    nsx_msg:notify_user_block(Who, Whom).
+
+unblock(Who, Whom) ->
+    ?INFO("~w:unblock_user/2 Who=~p Whom=~p", [?MODULE, Who, Whom]),
+    store:unblock_user(Who, Whom),
+    nsx_msg:notify_user_unblock(Who, Whom).
+
+blocked_users(UserId) -> store:list_blocks(UserId).
+
+get_blocked_users_feed_id(UserId) ->
+    UsersId = store:list_blocks(UserId),
+    Users = store:select(user, fun(#user{username=U})-> lists:member(U, UsersId) end),
+    {UsersId, [Fid || #user{feed=Fid} <- Users]}.
+
+
+is_user_blocked(Who, Whom) -> store:is_user_blocked(Who,Whom).
+
+update_user(#user{username=UId,name=Name,surname=Surname} = NewUser) ->
+    OldUser = case store:get(user,UId) of
+        {error,notfound} -> NewUser;
+        {ok,#user{}=User} -> User
+    end,
+    store:put(NewUser),
+    case Name==OldUser#user.name andalso Surname==OldUser#user.surname of
+        true -> ok;
+        false -> store:update_user_name(UId,Name,Surname)
+    end.
+
+subscribe_user_mq(Type, MeId, ToId) -> process_subscription_mq(Type, add, MeId, ToId).
+remove_subscription_mq(Type, MeId, ToId) -> process_subscription_mq(Type, delete, MeId, ToId).
+process_subscription_mq(Type, Action, MeId, ToId) ->
+    {ok, Channel} = nsm_mq:open([]),
+    Routes = case Type of
+                 user ->
+                     rk_user_feed(ToId);
+                 group ->
+                     rk_group_feed(ToId)
+             end,
+    case Action of
+        add ->
+            bind_user_exchange(Channel, MeId, Routes);
+        delete ->
+            catch(unbind_user_exchange(Channel, MeId, Routes))
+    end,
+    nsm_mq_channel:close(Channel),
+    ok.
+
+init_mq(User, Groups) ->
+    ?INFO("~p init mq. nsm_users: ~p", [User, Groups]),
+    UserExchange = ?USER_EXCHANGE(User),
+    %% we need fanout exchange to give all information to all users queues
+    ExchangeOptions = [{type, <<"fanout">>},
+                       durable,
+                       {auto_delete, false}],
+    {ok, Channel} = nsm_mq:open([]),
+    ?INFO("Cration Exchange: ~p,",[{Channel,UserExchange,ExchangeOptions}]),
+    ok = nsm_mq_channel:create_exchange(Channel, UserExchange,
+                                        ExchangeOptions),
+                                          ?INFO("Created OK"),
+    %% build routing keys for user's relations
+    Relations = build_user_relations(User, Groups),
+
+    %% RK = Routing Key. Bind exchange to all user related keys.
+    [bind_user_exchange(Channel, User, RK)
+       || RK <- [rk([feed, delete, User])|Relations]],
+
+    nsm_mq_channel:close(Channel),
+    ok.
+
+init_mq_for_user(User) ->
+    init_mq(User, nsm_groups:list_groups_per_user(User) ).
+
+build_user_relations(User, Groups) ->
+    %% Feed Keys. Subscribe for self events, system and groups events
+    %% feed.FeedOwnerType.FeedOwnerId.ElementType.ElementId.Action
+    %% feed.system.ElementType.Action
+    [rk_user_feed(User),
+     %% API
+     rk( [db, user, User, put] ),
+     rk( [subscription, user, User, add_to_group]),
+     rk( [subscription, user, User, remove_from_group]),
+     rk( [subscription, user, User, leave_group]),
+     rk( [login, user, User, update_after_login]),
+     rk( [likes, user, User, add_like]),
+     rk( [personal_score, user, User, add]),
+
+     rk( [feed, user, User, count_entry_in_statistics] ),
+     rk( [feed, user, User, count_comment_in_statistics] ),
+
+     rk( [feed, user, User, post_note] ),
+
+     rk( [subscription, user, User, subscribe_user]),
+     rk( [subscription, user, User, remove_subscribe]),
+     rk( [subscription, user, User, set_user_game_status]),
+     rk( [subscription, user, User, update_user]),
+     rk( [subscription, user, User, block_user]),
+     rk( [subscription, user, User, unblock_user]),
+
+     rk( [affiliates, user, User, create_affiliate]),
+     rk( [affiliates, user, User, delete_affiliate]),
+     rk( [affiliates, user, User, enable_to_look_details]),
+     rk( [affiliates, user, User, disable_to_look_details]),
+
+     rk( [purchase, user, User, set_purchase_external_id]),
+     rk( [purchase, user, User, set_purchase_state]),
+     rk( [purchase, user, User, set_purchase_info]),
+     rk( [purchase, user, User, add_purchase]),
+
+     rk( [transaction, user, User, add_transaction]),
+
+     rk( [invite, user, User, add_invite_to_issuer]),
+
+     rk( [tournaments, user, User, create]),
+     rk( [tournaments, user, User, create_and_join]),
+
+     rk( [gifts, user, User, buy_gift]),
+     rk( [gifts, user, User, give_gift]),
+     rk( [gifts, user, User, mark_gift_as_deliving]),
+
+     %% system message format: feed.system.ElementType.Action
+     rk( [feed, system, '*', '*']) |
+     [rk_group_feed(G) || G <- Groups]].
+
+bind_user_exchange(Channel, User, RoutingKey) ->
+    {bind, RoutingKey, nsm_mq_channel:bind_exchange(Channel, ?USER_EXCHANGE(User), ?NOTIFICATIONS_EX, RoutingKey)}.
+
+unbind_user_exchange(Channel, User, RoutingKey) ->
+    {unbind, RoutingKey, nsm_mq_channel:unbind_exchange(Channel, ?USER_EXCHANGE(User), ?NOTIFICATIONS_EX, RoutingKey)}.
+
+bind_group_exchange(Channel, Group, RoutingKey) ->
+    {bind, RoutingKey, nsm_mq_channel:bind_exchange(Channel, ?GROUP_EXCHANGE(Group), ?NOTIFICATIONS_EX, RoutingKey)}.
+
+unbind_group_exchange(Channel, Group, RoutingKey) ->
+    {unbind, RoutingKey, nsm_mq_channel:unbind_exchange(Channel, ?GROUP_EXCHANGE(Group), ?NOTIFICATIONS_EX, RoutingKey)}.
+
+init_mq_for_group(Group) ->
+    GroupExchange = ?GROUP_EXCHANGE(Group),
+    ExchangeOptions = [{type, <<"fanout">>},
+                       durable,
+                       {auto_delete, false}],   
+    {ok, Channel} = nsm_mq:open([]),
+    ok = nsm_mq_channel:create_exchange(Channel, GroupExchange, ExchangeOptions),
+    Relations = build_group_relations(Group),
+    [bind_group_exchange(Channel, Group, RK) || RK <- Relations],
+    nsm_mq_channel:close(Channel),
+    ok.
+
+build_group_relations(Group) ->
+    [
+        rk( [db, group, Group, put] ),
+        rk( [db, group, Group, update_group] ),
+        rk( [db, group, Group, remove_group] ),
+        rk( [likes, group, Group, add_like]),   % for comet mostly
+        rk( [feed, delete, Group] ),
+        rk( [feed, group, Group, '*', '*', '*'] )
+    ].
+
+
+rk(List) ->
+    nsm_mq_lib:list_to_key(List).
+
+rk_user_feed(User) ->
+    rk([feed, user, User, '*', '*', '*']).
+
+rk_group_feed(Group) ->
+    rk([feed, group, Group, '*', '*', '*']).
+
+retrieve_connections(Id,Type) ->
+    Friends = case Type of 
+                  user -> nsm_users:list_subscr_usernames(Id);
+                     _ -> nsm_groups:list_group_members(Id) end,
+    case Friends of
+	[] -> [];
+	Full -> Sub = lists:sublist(Full, 10),
+                case Sub of
+                     [] -> [];
+                      _ -> Data = [begin case store:get(user,Who) of
+                                       {ok,User} -> RealName = nsm_users:user_realname_user(User),
+                                                    Paid = nsm_accounts:user_paid(Who),
+                                                    {Who,Paid,RealName};
+				               _ -> undefined end end || Who <- Sub],
+			   [X||X<-Data, X/=undefined] end end.