1:- module(bc_api_analytics, []).

HTTP handlers for managing posts */

    5:- use_module(library(http/http_dispatch)).    6:- use_module(library(http/http_wrapper)).    7:- use_module(library(url)).    8
    9:- use_module(library(arouter)).   10:- use_module(library(dict_schema)).   11
   12:- use_module(bc_view).   13:- use_module(bc_api_io).   14:- use_module(bc_api_auth).   15:- use_module(bc_api_actor).   16:- use_module(bc_data_entry).   17:- use_module(bc_analytics).   18:- use_module(bc_analytics_db).   19:- use_module(bc_admin_file).   20
   21% Handlers for data from the readers script.
   22
   23:- route_post(api/readers/user, record_user).   24
   25record_user:-
   26    bc_read_by_schema(bc_analytics_user, User),
   27    bc_analytics_record_user(User, UserId),
   28    bc_reply_success(UserId).
   29
   30:- route_post(api/readers/session, record_session).   31
   32record_session:-
   33    bc_read_by_schema(bc_analytics_session, Session),
   34    bc_analytics_record_session(Session, SessionId),
   35    bc_reply_success(SessionId).
   36
   37:- route_post(api/readers/pageview, record_pageview).   38
   39record_pageview:-
   40    bc_read_by_schema(bc_analytics_pageview, Pageview),
   41    bc_analytics_record_pageview(Pageview, PageviewId),
   42    bc_reply_success(PageviewId).
   43
   44:- route_post(api/readers/pageview_extend, record_pageview_extend).   45
   46record_pageview_extend:-
   47    bc_read_by_schema(bc_analytics_pageview_extend, Pageview),
   48    bc_analytics_record_pageview_extend(Pageview),
   49    bc_reply_success(true).
   50
   51% Full tracking script.
   52
   53:- route_get(bc/'readers.min.js', visitor_script).   54
   55visitor_script:-
   56    bc_admin_send_file('js/readers.min.js').
   57
   58:- route_get(bc/'readers.min.js.map', visitor_script_map).   59
   60visitor_script_map:-
   61    bc_admin_send_file('js/readers.min.js.map').
   62
   63% Pixel-based tracking script.
   64
   65:- route_get(bc/'readers.image.min.js', visitor_image_script).   66
   67visitor_image_script:-
   68    bc_admin_send_file('js/readers.image.min.js').
   69
   70:- route_get(bc/'readers.image.min.js.map', visitor_image_script_map).   71
   72visitor_image_script_map:-
   73    bc_admin_send_file('js/readers.image.min.js.map').
   74
   75:- route_get(bc/'reader.png', visitor_pixel).   76
   77visitor_pixel:-
   78    http_current_request(Request),
   79    pixel_location(Request, Location),
   80    pixel_agent(Request, Agent),
   81    memberchk(search(Query), Request),
   82    param_or_null(u, Query, UserId),
   83    param_or_null(s, Query, SessionId),
   84    param_or_null(p, Query, Platform),
   85    param_or_null(t, Query, Title),
   86    param_or_null(e, Query, EntryId),
   87    param_or_null(r, Query, Referrer),
   88    Data = pixel{
   89        user_id: UserId,
   90        agent: Agent,
   91        platform: Platform,
   92        session_id: SessionId,
   93        location: Location,
   94        referrer: Referrer,
   95        entry_id: EntryId,
   96        title: Title
   97    },
   98    bc_analytics_record_pixel(Data),
   99    bc_admin_relative(img/'reader.png', ImagePath),
  100    http_reply_file(ImagePath, [
  101        unsafe(true),
  102        cache(false),
  103        headers([ cache_control('no-cache') ])
  104    ], Request).
  105
  106param_or_null(Name, Query, Value):-
  107    memberchk(Name=Value, Query), !.
  108
  109param_or_null(_, _, null).
  110
  111pixel_location(Request, Path):-
  112    memberchk(referer(Referrer), Request), !,
  113    parse_url(Referrer, Attributes),
  114    memberchk(path(Path), Attributes).
  115
  116pixel_location(_, null).
  117
  118pixel_agent(Request, Agent):-
  119    memberchk(user_agent(Agent), Request), !.
  120
  121pixel_agent(_, null).
  122
  123:- register_schema(bc_analytics_user, _{
  124    type: dict,
  125    tag: user,
  126    keys: _{}
  127}).  128
  129:- register_schema(bc_analytics_session, _{
  130    type: dict,
  131    tag: session,
  132    keys: _{
  133        user_id: atom,
  134        agent: atom,
  135        platform: atom
  136    }
  137}).  138
  139:- register_schema(bc_analytics_pageview, _{
  140    type: dict,
  141    tag: pageview,
  142    keys: _{
  143        session_id: atom,
  144        location: atom,
  145        referrer: atom,
  146        elapsed: integer,
  147        entry_id: atom,
  148        title: atom
  149    }
  150}).  151
  152:- register_schema(bc_analytics_pageview_extend, _{
  153    type: dict,
  154    tag: pageview_extend,
  155    keys: _{
  156        pageview_id: atom,
  157        elapsed: integer
  158    }
  159}).  160
  161% Analytic timeseries results for the administration API.
  162
  163:- route_get(api/analytics/timeseries/From/To/Duration,
  164   bc_auth, analytics_timeseries(From, To, Duration)).  165
  166% TODO: check atom_number/2 calls.
  167
  168analytics_timeseries(From, To, Duration):-
  169    atom_number(Duration, DurationNum),
  170    parse_month(From, FromParsed),
  171    parse_month(To, ToParsed),
  172    bc_analytics_user_ts(FromParsed-ToParsed, DurationNum, Users),
  173    bc_analytics_session_ts(FromParsed-ToParsed, DurationNum, Sessions),
  174    bc_analytics_pageview_ts(FromParsed-ToParsed, DurationNum, Pageviews),
  175    bc_reply_success(_{
  176        users: Users,
  177        sessions: Sessions,
  178        pageviews: Pageviews}).
  179
  180% Analytics summary.
  181
  182:- route_get(api/analytics/summary/From/To/Duration,
  183   bc_auth, analytics_summary(From, To, Duration)).  184
  185analytics_summary(From, To, Duration):-
  186    atom_number(Duration, DurationNum),
  187    parse_month(From, FromParsed),
  188    parse_month(To, ToParsed),
  189    bc_analytics_summary(FromParsed-ToParsed,
  190        DurationNum, Summary),
  191    bc_reply_success(Summary).
  192
  193% List of recent users.
  194
  195:- route_get(api/analytics/users/From/To/Duration/Offset/Count,
  196   bc_auth, analytics_users(From, To, Duration, Offset, Count)).  197
  198analytics_users(From, To, Duration, Offset, Count):-
  199    atom_number(Duration, DurationNum),
  200    parse_month(From, FromParsed),
  201    parse_month(To, ToParsed),
  202    atom_number(Offset, OffsetNum),
  203    atom_number(Count, CountNum),
  204    bc_analytics_users(FromParsed-ToParsed,
  205        DurationNum, OffsetNum, CountNum, Users),
  206    bc_reply_success(Users).
  207
  208% List of top pages.
  209
  210:- route_get(api/analytics/pages/From/To/Duration,
  211   bc_auth, analytics_pages(From, To, Duration)).  212
  213analytics_pages(From, To, Duration):-
  214    atom_number(Duration, DurationNum),
  215    parse_month(From, FromParsed),
  216    parse_month(To, ToParsed),
  217    bc_analytics_top_pages(FromParsed-ToParsed,
  218        DurationNum, Pages),
  219    bc_reply_success(Pages).
  220
  221parse_month(Atom, (YearNum, MonthNum)):-
  222    atomic_list_concat([Year, Month], -, Atom),
  223    atom_number(Year, YearNum),
  224    atom_number(Month, MonthNum)