<commit_before>import _ from "underscore";

import MetabaseAnalytics from 'metabase/lib/analytics';
import MetabaseCookies from 'metabase/lib/cookies';
import * as MetabaseCore from 'metabase/lib/core';
import MetabaseSettings from 'metabase/lib/settings';


var MetabaseServices = angular.module('metabase.services', ['http-auth-interceptor', 'ipCookie', 'metabase.core.services']);

MetabaseServices.factory('AppState', ['$rootScope', '$q', '$location', '$interval', '$timeout', 'ipCookie', 'Session', 'User', 'Settings',
    function($rootScope, $q, $location, $interval, $timeout, ipCookie, Session, User, Settings) {
        // this is meant to be a global service used for keeping track of our overall app state
        // we fire 2 events as things change in the app
        // 1. appstate:user

        var initPromise;
        var currentUserPromise;

        var service = {

            model: {
                setupToken: null,
                currentUser: null,
                appContext: 'none',
                requestedUrl: null
            },

            init: function() {

                if (!initPromise) {
                    // hackery to allow MetabaseCookies to tie into Angular
                    MetabaseCookies.bootstrap($rootScope, $location, ipCookie);

                    var deferred = $q.defer();
                    initPromise = deferred.promise;

                    // grab our global settings
                    service.refreshSiteSettings();

                    // just make sure we grab the current user
                    service.refreshCurrentUser().then(function(user) {
                        deferred.resolve();
                    }, function(error) {
                        deferred.resolve();
                    });
                }

                return initPromise;
            },

            clearState: function() {
                currentUserPromise = null;
                service.model.currentUser = null;

                // clear any existing session cookies if they exist
                ipCookie.remove('metabase.SESSION_ID');
            },

            refreshCurrentUser: function() {

                // this is meant to be called once on app startup
                var userRefresh = User.current(function(result) {
                    service.model.currentUser = result;

                    $rootScope.$broadcast('appstate:user', result);

                }, function(error) {
                    console.log('unable to get current user', error);
                });

                // NOTE: every time we refresh the user we update our current promise to ensure that
                //       we can guarantee we've resolved the current user
                currentUserPromise = userRefresh.$promise;

                return currentUserPromise;
            },

            refreshSiteSettings: function() {

                var settingsRefresh = Session.properties(function(settings) {

                    MetabaseSettings.setAll(_.omit(settings, function(value, key, object) {
                        return (key.indexOf('$') === 0);
                    }));

                    $rootScope.$broadcast('appstate:site-settings', settings);

                }, function(error) {
                    console.log('unable to get site settings', error);
                });

                return settingsRefresh.$promise;
            },

            // This function performs whatever state cleanup and next steps are required when a user tries to access
            // something they are not allowed to.
            invalidAccess: function(user, url, message) {
                $location.path('/unauthorized/');
            },

            setAppContext: function(appContext) {
                service.model.appContext = appContext;
                $rootScope.$broadcast('appstate:context-changed', service.model.appContext);
            },

            routeChanged: function(event) {
                // establish our application context based on the route (URI)
                // valid app contexts are: 'setup', 'auth', 'main', 'admin', or 'unknown'
                var routeContext;
                if ($location.path().indexOf('/auth/') === 0) {
                    routeContext = 'auth';
                } else if ($location.path().indexOf('/setup/') === 0) {
                    routeContext = 'setup';
                } else if ($location.path().indexOf('/admin/') === 0) {
                    routeContext = 'admin';
                } else if ($location.path() === '/') {
                    routeContext = 'home';
                } else {
                    routeContext = 'main';
                }

                // if the context of the app has changed due to this route change then send out an event
                if (service.model.appContext !== routeContext) {
                    service.setAppContext(routeContext);
                }

                // this code is here to ensure that we have resolved our currentUser BEFORE we execute any other
                // code meant to establish app context based on the current route
                if (currentUserPromise) {
                    currentUserPromise.then(function(user) {
                        service.routeChangedImpl(event);
                    }, function(error) {
                        service.routeChangedImpl(event);
                    });
                } else {
                    service.routeChangedImpl(event);
                }
            },

            routeChangedImpl: function(event) {
                // whenever we have a route change (including initial page load) we need to establish some context

                // handle routing protections for /setup/
                if ($location.path().indexOf('/setup') === 0 && !MetabaseSettings.hasSetupToken()) {
                    // someone trying to access setup process without having a setup token, so block that.
                    $location.path('/');
                    return;
                } else if ($location.path().indexOf('/setup') !== 0 && MetabaseSettings.hasSetupToken()) {
                    // someone who has a setup token but isn't routing to setup yet, so send them there!
                    $location.path('/setup/');
                    return;
                }

                // if we don't have a current user then the only sensible destination is the login page
                if (!service.model.currentUser) {
                    // make sure we clear out any current state just to be safe
                    service.clearState();

                    if ($location.path().indexOf('/auth/') !== 0 && $location.path().indexOf('/setup/') !== 0) {
                        // if the user is asking for a url outside of /auth/* then record the url then send them
                        // to login page, otherwise we will let the user continue on to their requested page
                        service.model.requestedUrl = $location.path();
                        $location.path('/auth/login');
                    }

                    return;
                }

                if ($location.path().indexOf('/admin/') === 0) {
                    // the user is trying to change to a superuser page
                    if (!service.model.currentUser.is_superuser) {
                        service.invalidAccess(service.model.currentUser, $location.url(), "user is not a superuser!!!");
                        return;
                    }

                }
            },

            redirectAfterLogin: function() {
                if (service.model.requestedUrl) {
                    $location.path(service.model.requestedUrl);
                    delete service.model.requestedUrl;
                } else {
                    $location.path('/');
                }
            }
        };

        // listen for location changes and use that as a trigger for page view tracking
        $rootScope.$on('$locationChangeSuccess', function() {
            // NOTE: we are only taking the path right now to avoid accidentally grabbing sensitive data like table/field ids
            MetabaseAnalytics.trackPageView($location.path());
        });

        // listen for all route changes so that we can update organization as appropriate
        $rootScope.$on('$routeChangeSuccess', service.routeChanged);

        // login just took place, so lets force a refresh of the current user
        $rootScope.$on("appstate:login", function(event, session_id) {
            service.refreshCurrentUser();
        });

        // logout just took place, do some cleanup
        $rootScope.$on("appstate:logout", function(event, session_id) {

            // clear out any current state
            service.clearState();

            // NOTE that we don't really care about callbacks in this case
            Session.delete({
                'session_id': session_id
            });
        });

        // enable / disable GA based on opt-out of anonymous tracking
        $rootScope.$on("appstate:site-settings", function(event, settings) {
            const ga_code = MetabaseSettings.get('ga_code');
            if (MetabaseSettings.isTrackingEnabled()) {
                // we are doing tracking
                window['ga-disable-'+ga_code] = null;
            } else {
                // tracking is disabled
                window['ga-disable-'+ga_code] = true;
            }
        });

        // NOTE: the below events are generated from the http-auth-interceptor which listens on our $http calls
        //       and intercepts calls that result in a 401 or 403 so that we can handle them here.  You must be
        //       careful to consider the implications of this because any endpoint that returns a 401/403 can
        //       have its call stack interrupted now and handled here instead of its normal callback sequence.

        // $http interceptor received a 401 response
        $rootScope.$on("event:auth-loginRequired", function() {
            // this is effectively just like a logout, we want to reset everything to a base state, then force login
            service.clearState();

            // this is ridiculously stupid.  we have to wait (300ms) for the cookie to actually be set in the browser :(
            $timeout(function() {
                $location.path('/auth/login');
            }, 300);
        });

        // $http interceptor received a 403 response
        $rootScope.$on("event:auth-forbidden", function() {
            $location.path("/unauthorized");
        });

        return service;
    }
]);

MetabaseServices.service('MetabaseCore', ['User', function(User) {
    // this just makes it easier to access the current user
    this.currentUser = User.current;

    // copy over MetabaseCore properties and functions
    angular.forEach(MetabaseCore, (value, key) => this[key] = value);
}]);


// API Services
var CoreServices = angular.module('metabase.core.services', ['ngResource', 'ngCookies']);

CoreServices.factory('Activity', ['$resource', '$cookies', function($resource, $cookies) {
    return $resource('/api/activity', {}, {
        list: {
            method: 'GET',
            isArray: true
        },
        recent_views: {
            url: '/api/activity/recent_views',
            method: 'GET',
            isArray: true
        }
    });
}]);

CoreServices.factory('Card', ['$resource', '$cookies', function($resource, $cookies) {
    return $resource('/api/card/:cardId', {}, {
        list: {
            url: '/api/card/?f=:filterMode',
            method: 'GET',
            isArray: true
        },
        create: {
            url: '/api/card',
            method: 'POST'
        },
        get: {
            method: 'GET',
            params: {
                cardId: '@cardId'
            }
        },
        update: {
            method: 'PUT',
            params: {
                cardId: '@id'
            }
        },
        delete: {
            method: 'DELETE',
            params: {
                cardId: '@cardId'
            }
        },
        isfavorite: {
            url: '/api/card/:cardId/favorite',
            method: 'GET',
            params: {
                cardId: '@cardId'
            }
        },
        favorite: {
            url: '/api/card/:cardId/favorite',
            method: 'POST',
            params: {
                cardId: '@cardId'
            }
        },
        unfavorite: {
            url: '/api/card/:cardId/favorite',
            method: 'DELETE',
            params: {
                cardId: '@cardId'
            }
        }
    });
}]);

CoreServices.factory('Dashboard', ['$resource', '$cookies', function($resource, $cookies) {
    return $resource('/api/dashboard/:dashId', {}, {
        list: {
            url:'/api/dashboard?f=:filterMode',
            method:'GET',
            isArray:true
        },
        create: {
            url:'/api/dashboard',
            method:'POST'
        },
        get: {
            method:'GET',
            params:{dashId:'@dashId'},
        },
        update: {
            method:'PUT',
            params:{dashId:'@id'}
        },
        delete: {
            method:'DELETE',
            params:{dashId:'@dashId'}
        },
        addcard: {
            url:'/api/dashboard/:dashId/cards',
            method:'POST',
            params:{dashId:'@dashId'}
        },
        removecard: {
            url:'/api/dashboard/:dashId/cards',
            method:'DELETE',
            params:{dashId:'@dashId'}
        },
        reposition_cards: {
            url:'/api/dashboard/:dashId/cards',
            method:'PUT',
            params:{dashId:'@dashId'}
        }
    });
}]);

CoreServices.factory('Email', ['$resource', function($resource) {
    return $resource('/api/email', {}, {

        updateSettings: {
            url: '/api/email/',
            method: 'PUT'
        },

        sendTest: {
            url: '/api/email/test',
            method: 'POST'
        }
    });
}]);

CoreServices.factory('Slack', ['$resource', function($resource) {
    return $resource('/api/slack', {}, {

        updateSettings: {
            url: '/api/slack/settings',
            method: 'PUT'
        }
    });
}]);

CoreServices.factory('ForeignKey', ['$resource', '$cookies', function($resource, $cookies) {
    return $resource('/api/foreignkey/:fkID', {}, {
        delete: {
            method: 'DELETE',
            params: {
                fkID: '@fkID'
            }
        }
    });
}]);

CoreServices.factory('Metabase', ['$resource', '$cookies', 'MetabaseCore', function($resource, $cookies, MetabaseCore) {
    return $resource('/api/meta', {}, {
        db_list: {
            url: '/api/database/',
            method: 'GET',
            isArray: true
        },
        db_list_with_tables: {
            method: 'GET',
            url: '/api/database/',
            params: {
                include_tables: 'true'
            },
            isArray: true
        },
        db_create: {
            url: '/api/database/',
            method: 'POST'<commit_msg>missed remove the old ForeignKey api endpoint/service.<commit_after>import _ from "underscore";

import MetabaseAnalytics from 'metabase/lib/analytics';
import MetabaseCookies from 'metabase/lib/cookies';
import * as MetabaseCore from 'metabase/lib/core';
import MetabaseSettings from 'metabase/lib/settings';


var MetabaseServices = angular.module('metabase.services', ['http-auth-interceptor', 'ipCookie', 'metabase.core.services']);

MetabaseServices.factory('AppState', ['$rootScope', '$q', '$location', '$interval', '$timeout', 'ipCookie', 'Session', 'User', 'Settings',
    function($rootScope, $q, $location, $interval, $timeout, ipCookie, Session, User, Settings) {
        // this is meant to be a global service used for keeping track of our overall app state
        // we fire 2 events as things change in the app
        // 1. appstate:user

        var initPromise;
        var currentUserPromise;

        var service = {

            model: {
                setupToken: null,
                currentUser: null,
                appContext: 'none',
                requestedUrl: null
            },

            init: function() {

                if (!initPromise) {
                    // hackery to allow MetabaseCookies to tie into Angular
                    MetabaseCookies.bootstrap($rootScope, $location, ipCookie);

                    var deferred = $q.defer();
                    initPromise = deferred.promise;

                    // grab our global settings
                    service.refreshSiteSettings();

                    // just make sure we grab the current user
                    service.refreshCurrentUser().then(function(user) {
                        deferred.resolve();
                    }, function(error) {
                        deferred.resolve();
                    });
                }

                return initPromise;
            },

            clearState: function() {
                currentUserPromise = null;
                service.model.currentUser = null;

                // clear any existing session cookies if they exist
                ipCookie.remove('metabase.SESSION_ID');
            },

            refreshCurrentUser: function() {

                // this is meant to be called once on app startup
                var userRefresh = User.current(function(result) {
                    service.model.currentUser = result;

                    $rootScope.$broadcast('appstate:user', result);

                }, function(error) {
                    console.log('unable to get current user', error);
                });

                // NOTE: every time we refresh the user we update our current promise to ensure that
                //       we can guarantee we've resolved the current user
                currentUserPromise = userRefresh.$promise;

                return currentUserPromise;
            },

            refreshSiteSettings: function() {

                var settingsRefresh = Session.properties(function(settings) {

                    MetabaseSettings.setAll(_.omit(settings, function(value, key, object) {
                        return (key.indexOf('$') === 0);
                    }));

                    $rootScope.$broadcast('appstate:site-settings', settings);

                }, function(error) {
                    console.log('unable to get site settings', error);
                });

                return settingsRefresh.$promise;
            },

            // This function performs whatever state cleanup and next steps are required when a user tries to access
            // something they are not allowed to.
            invalidAccess: function(user, url, message) {
                $location.path('/unauthorized/');
            },

            setAppContext: function(appContext) {
                service.model.appContext = appContext;
                $rootScope.$broadcast('appstate:context-changed', service.model.appContext);
            },

            routeChanged: function(event) {
                // establish our application context based on the route (URI)
                // valid app contexts are: 'setup', 'auth', 'main', 'admin', or 'unknown'
                var routeContext;
                if ($location.path().indexOf('/auth/') === 0) {
                    routeContext = 'auth';
                } else if ($location.path().indexOf('/setup/') === 0) {
                    routeContext = 'setup';
                } else if ($location.path().indexOf('/admin/') === 0) {
                    routeContext = 'admin';
                } else if ($location.path() === '/') {
                    routeContext = 'home';
                } else {
                    routeContext = 'main';
                }

                // if the context of the app has changed due to this route change then send out an event
                if (service.model.appContext !== routeContext) {
                    service.setAppContext(routeContext);
                }

                // this code is here to ensure that we have resolved our currentUser BEFORE we execute any other
                // code meant to establish app context based on the current route
                if (currentUserPromise) {
                    currentUserPromise.then(function(user) {
                        service.routeChangedImpl(event);
                    }, function(error) {
                        service.routeChangedImpl(event);
                    });
                } else {
                    service.routeChangedImpl(event);
                }
            },

            routeChangedImpl: function(event) {
                // whenever we have a route change (including initial page load) we need to establish some context

                // handle routing protections for /setup/
                if ($location.path().indexOf('/setup') === 0 && !MetabaseSettings.hasSetupToken()) {
                    // someone trying to access setup process without having a setup token, so block that.
                    $location.path('/');
                    return;
                } else if ($location.path().indexOf('/setup') !== 0 && MetabaseSettings.hasSetupToken()) {
                    // someone who has a setup token but isn't routing to setup yet, so send them there!
                    $location.path('/setup/');
                    return;
                }

                // if we don't have a current user then the only sensible destination is the login page
                if (!service.model.currentUser) {
                    // make sure we clear out any current state just to be safe
                    service.clearState();

                    if ($location.path().indexOf('/auth/') !== 0 && $location.path().indexOf('/setup/') !== 0) {
                        // if the user is asking for a url outside of /auth/* then record the url then send them
                        // to login page, otherwise we will let the user continue on to their requested page
                        service.model.requestedUrl = $location.path();
                        $location.path('/auth/login');
                    }

                    return;
                }

                if ($location.path().indexOf('/admin/') === 0) {
                    // the user is trying to change to a superuser page
                    if (!service.model.currentUser.is_superuser) {
                        service.invalidAccess(service.model.currentUser, $location.url(), "user is not a superuser!!!");
                        return;
                    }

                }
            },

            redirectAfterLogin: function() {
                if (service.model.requestedUrl) {
                    $location.path(service.model.requestedUrl);
                    delete service.model.requestedUrl;
                } else {
                    $location.path('/');
                }
            }
        };

        // listen for location changes and use that as a trigger for page view tracking
        $rootScope.$on('$locationChangeSuccess', function() {
            // NOTE: we are only taking the path right now to avoid accidentally grabbing sensitive data like table/field ids
            MetabaseAnalytics.trackPageView($location.path());
        });

        // listen for all route changes so that we can update organization as appropriate
        $rootScope.$on('$routeChangeSuccess', service.routeChanged);

        // login just took place, so lets force a refresh of the current user
        $rootScope.$on("appstate:login", function(event, session_id) {
            service.refreshCurrentUser();
        });

        // logout just took place, do some cleanup
        $rootScope.$on("appstate:logout", function(event, session_id) {

            // clear out any current state
            service.clearState();

            // NOTE that we don't really care about callbacks in this case
            Session.delete({
                'session_id': session_id
            });
        });

        // enable / disable GA based on opt-out of anonymous tracking
        $rootScope.$on("appstate:site-settings", function(event, settings) {
            const ga_code = MetabaseSettings.get('ga_code');
            if (MetabaseSettings.isTrackingEnabled()) {
                // we are doing tracking
                window['ga-disable-'+ga_code] = null;
            } else {
                // tracking is disabled
                window['ga-disable-'+ga_code] = true;
            }
        });

        // NOTE: the below events are generated from the http-auth-interceptor which listens on our $http calls
        //       and intercepts calls that result in a 401 or 403 so that we can handle them here.  You must be
        //       careful to consider the implications of this because any endpoint that returns a 401/403 can
        //       have its call stack interrupted now and handled here instead of its normal callback sequence.

        // $http interceptor received a 401 response
        $rootScope.$on("event:auth-loginRequired", function() {
            // this is effectively just like a logout, we want to reset everything to a base state, then force login
            service.clearState();

            // this is ridiculously stupid.  we have to wait (300ms) for the cookie to actually be set in the browser :(
            $timeout(function() {
                $location.path('/auth/login');
            }, 300);
        });

        // $http interceptor received a 403 response
        $rootScope.$on("event:auth-forbidden", function() {
            $location.path("/unauthorized");
        });

        return service;
    }
]);

MetabaseServices.service('MetabaseCore', ['User', function(User) {
    // this just makes it easier to access the current user
    this.currentUser = User.current;

    // copy over MetabaseCore properties and functions
    angular.forEach(MetabaseCore, (value, key) => this[key] = value);
}]);


// API Services
var CoreServices = angular.module('metabase.core.services', ['ngResource', 'ngCookies']);

CoreServices.factory('Activity', ['$resource', '$cookies', function($resource, $cookies) {
    return $resource('/api/activity', {}, {
        list: {
            method: 'GET',
            isArray: true
        },
        recent_views: {
            url: '/api/activity/recent_views',
            method: 'GET',
            isArray: true
        }
    });
}]);

CoreServices.factory('Card', ['$resource', '$cookies', function($resource, $cookies) {
    return $resource('/api/card/:cardId', {}, {
        list: {
            url: '/api/card/?f=:filterMode',
            method: 'GET',
            isArray: true
        },
        create: {
            url: '/api/card',
            method: 'POST'
        },
        get: {
            method: 'GET',
            params: {
                cardId: '@cardId'
            }
        },
        update: {
            method: 'PUT',
            params: {
                cardId: '@id'
            }
        },
        delete: {
            method: 'DELETE',
            params: {
                cardId: '@cardId'
            }
        },
        isfavorite: {
            url: '/api/card/:cardId/favorite',
            method: 'GET',
            params: {
                cardId: '@cardId'
            }
        },
        favorite: {
            url: '/api/card/:cardId/favorite',
            method: 'POST',
            params: {
                cardId: '@cardId'
            }
        },
        unfavorite: {
            url: '/api/card/:cardId/favorite',
            method: 'DELETE',
            params: {
                cardId: '@cardId'
            }
        }
    });
}]);

CoreServices.factory('Dashboard', ['$resource', '$cookies', function($resource, $cookies) {
    return $resource('/api/dashboard/:dashId', {}, {
        list: {
            url:'/api/dashboard?f=:filterMode',
            method:'GET',
            isArray:true
        },
        create: {
            url:'/api/dashboard',
            method:'POST'
        },
        get: {
            method:'GET',
            params:{dashId:'@dashId'},
        },
        update: {
            method:'PUT',
            params:{dashId:'@id'}
        },
        delete: {
            method:'DELETE',
            params:{dashId:'@dashId'}
        },
        addcard: {
            url:'/api/dashboard/:dashId/cards',
            method:'POST',
            params:{dashId:'@dashId'}
        },
        removecard: {
            url:'/api/dashboard/:dashId/cards',
            method:'DELETE',
            params:{dashId:'@dashId'}
        },
        reposition_cards: {
            url:'/api/dashboard/:dashId/cards',
            method:'PUT',
            params:{dashId:'@dashId'}
        }
    });
}]);

CoreServices.factory('Email', ['$resource', function($resource) {
    return $resource('/api/email', {}, {

        updateSettings: {
            url: '/api/email/',
            method: 'PUT'
        },

        sendTest: {
            url: '/api/email/test',
            method: 'POST'
        }
    });
}]);

CoreServices.factory('Slack', ['$resource', function($resource) {
    return $resource('/api/slack', {}, {

        updateSettings: {
            url: '/api/slack/settings',
            method: 'PUT'
        }
    });
}]);

CoreServices.factory('Metabase', ['$resource', '$cookies', 'MetabaseCore', function($resource, $cookies, MetabaseCore) {
    return $resource('/api/meta', {}, {
        db_list: {
            url: '/api/database/',
            method: 'GET',
            isArray: true
        },
        db_list_with_tables: {
            method: 'GET',
            url: '/api/database/',
            params: {
                include_tables: 'true'
            },
            isArray: true
        },
        db_create: {
            url: '/api/database/',
            method: 'POST'
{'fraction_non_alphanumeric': 0.11677423647614611, 'fraction_numerical': 0.002328830926874709, 'mean_word_length': 1.5128741013208493, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6925647', 'n_tokens_mistral': 7515, 'n_tokens_neox': 7087, 'n_words': 1990}