<commit_before>var GtfsEditor = GtfsEditor || {};

(function(G, $) {

  G.Agency = Backbone.Model.extend({
    urlRoot: G.config.baseUrl + 'api/agency/',

    defaults: {
      id: null,
      gtfsAgencyId: null,
      name: null,
      url: null,
      timezone: null,
      lang: null,
      phone: null,
      defaultLat: null,
      defaultLon: null,
      defaultRouteType: null
    }
  });

  G.Agencies = Backbone.Collection.extend({
    type: 'Agencies',
    model: G.Agency,
    url: G.config.baseUrl + 'api/agency/'
  });

G.RouteType = Backbone.Model.extend({
    urlRoot: G.config.baseUrl + 'api/routetype/',

    defaults: {
      id: null,
      gtfsRouteType: null,
      hvtRouteType: null,
      localizedVehicleType: null,
      description: null
    }
  });

G.RouteTypes = Backbone.Collection.extend({
    type: 'RouteTypes',
    model: G.RouteType,
    url: G.config.baseUrl + 'api/routetype/'
  });

  G.Route = Backbone.Model.extend({
    defaults: {
      id: null,
      status: 'IN_PROGRESS',
      publiclyVisible: false,
      gtfsRouteId: null,
      routeShortName: null,
      routeLongName: null,
      routeDesc: null,
      routeType: null,
      routeUrl: null,
      routeColor: 'FFFFFF',
      routeTextColor: '000000',
      agency: null
    },

    initialize: function() {
      this.tripPatterns = new G.TripPatterns();
    }
  });

  G.Routes = Backbone.Collection.extend({
    type: 'Routes',
    model: G.Route,
    url: G.config.baseUrl + 'api/route/'
  });

  G.Stop = Backbone.Model.extend({
    defaults: {
      id: null,
      gtfsStopId: null,
      stopCode: null,
      stopName: null,
      stopDesc: null,
      zoneId: null,
      stopUrl: null,
      agency: null,
      locationType: null,
      parentStation: null,
      bikeParking: null,
      carParking: null,
      wheelchairBoarding: null,
      majorStop: false,
      location: null
    },

    blacklist: ['justAdded','lat','lng'],
    toJSON: function(options) {
        return _.omit(this.attributes, this.blacklist);
    },

    set: function(attributes, options) {
            //my custom code
        var finalAttributes = _.omit(attributes, this.blacklist);

        if(attributes.lat != undefined &&  attributes.lng != undefined)
          finalAttributes['location'] = {lat: attributes.lat, lng: attributes.lng};

        return Backbone.Model.prototype.set.call(this, finalAttributes, options);
    }
  });

  G.Stops = Backbone.Collection.extend({
    type: 'Stops',
    model: G.Stop,
    url: G.config.baseUrl + 'api/stop/'
  });

  G.StopGroup = Backbone.Model.extend({
    defaults: {
      id: null,
      mergedStop: null,
      stops: []
    },
    initialize: function() {
       this.set('stops', []);

       _.bindAll(this, 'setMergedStop', 'addStop', 'addGroup', 'merge', 'onFinishedMerge');
    },

    addStop: function(stop) {

      if(this.existingStops == undefined) {
        this.existingStops = {}
      }

      // make first stop the merged stop by default
      if(this.get('mergedStop') == null)
        this.set('mergedStop', stop);

      // don't add duplicate stops
      if(!this.existingStops[stop.id]) {

        this.existingStops[stop.id] = true;

        var stops = this.get('stops');
        stops.push(stop);

        this.set('stops', stops);
      }

    },

    setMergedStop: function(stop) {
        this.addStop(stop);
        this.set('mergedStop', stop);
    },

    addGroup: function(group){

      var existingGroup = this;

      // merge stops into group
      _.each(group.stops, function(stop){
        if(!_.contains(existingStops, stop.id)) {
          existingGroup.addStop(stop);
        }
      });
    },

    onFinishedMerge: function(evt) {
      this.trigger('merge');
    },

    merge: function() {

        if(this.get('mergedStop') != null && this.get('stops').length > 0) {

          var ids = _.without(_.pluck(this.get('stops'), 'id'), this.get('mergedStop').id);
          var idList = ids.join(',');

          $.get(G.config.baseUrl + 'api/mergeStops', {stop1Id: this.get('mergedStop').id, mergedStopIds: idList}, this.onFinishedMerge);
        }

    }

  });

  G.StopGroups = Backbone.Collection.extend({
    type: 'StopGroups',
    model: G.StopGroup,

    initialize: function(opts) {

      this.agencyId = opts.agencyId;
      this.success = opts.success;
      this.groupMap = {};

      this.on('merge', this.onMerge);

      _.bindAll(this, 'loadGroups', 'group', 'findDuplicateStops', 'onMerge');
    },

    onMerge: function(evt) {
      this.findDuplicateStops();
    },

    findDuplicateStops: function() {

      this.reset();
      this.groupMap = {};

      $.get(G.config.baseUrl + 'api/findDuplicateStops', {agencyId: this.agencyId}, this.loadGroups);
    },

    loadGroups: function(pairs) {

      this.success();

      var _stopGroups = this;

      _.each(pairs, function(pair) {

        _stopGroups.group(new G.Stop(pair[0]), new G.Stop(pair[1]));

      });
    },

    merge: function(groupId) {

      this.groupMap[groupId].merge();

    },

    group: function(stop1, stop2){

      if(this.groupMap[stop1.id] == undefined) {
        if(this.groupMap[stop2.id] != undefined) {
          // add stop1 to existing group for stop2
          (this.groupMap[stop2.id]).addStop(stop1);
        }
        else {
          // add stop1 and stop2 to new group
          this.groupMap[stop1.id] = new G.StopGroup();
          (this.groupMap[stop1.id]).addStop(stop1);
          (this.groupMap[stop1.id]).addStop(stop2);

          this.add(this.groupMap[stop1.id]);
        }
      }
      else {
        if(this.groupMap[stop2.id] == undefined) {
          // add stop2 to existing group for stop1
          this.groupMap[stop1.id].addStop(stop2);
        }
        else {
          // both stop1 and stop2 belong to existing group, merge stop2 group into stop1 group
          this.groupMap[stop1.id].addGroup(this.groupMap[stop2.id]);
        }
      }
    }

  });

  G.TripPattern = Backbone.Model.extend({
    defaults: {
      id: null,
      name: null,
      headsign: null,
      encodedShape: null,
      patternStops: [],
      shape: null,
      route: null,
      useFrequency: null
    },

    urlRoot: G.config.baseUrl + 'api/trippattern/',

    initialize: function() {
      this.on('change:patternStops', this.normalizeSequence, this);

      this.trips = new G.Trips({patternId: this.id});

      this.sortPatternStops();
    },

    /**
     * Get all of the pattern stops for a particular stop.
     * Generally there will only be one, but there can be more on a loop route.
     * If you'd like only one stop, see getPatternStop(stopId, stopSequence)
     */
    getPatternStops: function(stopId) {
      return this.isPatternStop(stopId);
    },

    isPatternStop: function(stopId) {
      var patternStops = _.filter(this.get('patternStops'), function(ps, i) {
        return ps.stop.id == stopId;
      });
      return patternStops.length > 0 ? patternStops : false;
    },

    /**
     * Get the pattern stop for this stop ID and stop sequence.
     * If you don't have a stop sequence, see getPatternStops(stopId)
     */
     getPatternStop: function (stopId, stopSequence) {
       var ret = _.find(this.get('patternStops'), function (ps) {
         return ps.stop.id == stopId && ps.stopSequence == stopSequence;
       });

        // match the behavior of getPatternStop/isPatternStop
       return ret === null ? false : ret;
     },

    getPatternStopLabel: function(stopId) {
      var stopsSequences = [];
      _.each(this.get('patternStops'), function(ps, i) {
        if(ps.stop.id == stopId) {
          stopsSequences.push(ps.stopSequence);
        }
      });
      return stopsSequences.join(" & ");
    },

    reverse: function() {

      var patternStops = this.get('patternStops');
      var newPatternStops = new Array();

      _.each(patternStops, function(stop, i){
        if(i+1 < patternStops.length) {
          stop.defaultTravelTime = patternStops[i+1].defaultTravelTime;
          stop.defaultDwellTime = patternStops[i+1].defaultDwellTime;
        }

        if(patternStops.length == i+1) {
          stop.defaultTravelTime = null;
          stop.defaultDwellTime = null;
        }

        newPatternStops.push(stop)
      });

      newPatternStops.reverse();

      this.set('patternStops', newPatternStops);

      this.normalizeSequence();

      var latlngs = (new L.EncodedPolyline(this.get('encodedShape'))).getLatLngs();

      latlngs.reverse();

      var reversedLine = createEncodedPolyline(L.polyline(latlngs));

      this.get('encodedShape', reversedLine);

    },

    sortPatternStops: function() {
      var patternStops = _.sortBy(this.get('patternStops'), function(ps){
        return ps.stopSequence;
      });

      this.set('patternStops', patternStops, {silent: true});
    },

    normalizeSequence: function () {
      _.each(this.get('patternStops'), function(ps, i) {
        ps.stopSequence = i+1;
      });
      this.sortPatternStops();
      //this.save();
    },

    validate: function(attrs) {
      // Override the sequence value to match the array order
      this.sortPatternStops();
    },

    // name, headsign, alignment, stop_times[], shape, route_id (fk)
    // stop_id, travel_time, dwell_time

    addStop: function(stopTime) {
      var patternStops = this.get('patternStops');
      patternStops.push(stopTime);
      this.set('patternStops', patternStops);
      this.normalizeSequence();
    },

    insertStopAt: function(stopTime, i) {
      var patternStops = this.get('patternStops');
      patternStops.splice(i, 0, stopTime);
      this.set('patternStops', patternStops);
      this.normalizeSequence();
    },

    removeStopAt: function(i) {
      var patternStops = this.get('patternStops'),
          removed = patternStops.splice(i, 1)[0];
      this.set('patternStops', patternStops);
      this.normalizeSequence();
      return removed;
    },

    removeAllStops: function() {
      this.set('patternStops', []);
    },

    moveStopTo: function(fromIndex, toIndex) {
      var stopTimes = this.get('patternStops'),
          stopTime;

      stopTime = this.removeStopAt(fromIndex);
      this.insertStopAt(stopTime, toIndex);
      this.normalizeSequence();
    },

    updatePatternStop: function(data) {
      var patternStops = this.get('patternStops');
      this.removeAllStops();
      this.save();

      patternStops[data.stopSequence] = data;
      this.set('patternStops', patternStops);
      this.save();
    },

    useFrequency: function() {
      this.trips.clearTrips();
      this.set('useFrequency', true);
      this.save();
    },

    useTimetable: function() {
      this.trips.clearTrips();
      this.set('useFrequency', false);
      this.save()
    }

  });

  G.TripPatterns = Backbone.Collection.extend({
    type: 'TripPatterns',
    model: G.TripPattern,
    url: G.config.baseUrl + 'api/trippattern/'
  });

  G.Calendar = Backbone.Model.extend({
    defaults: {
      id: null,
      agency: null,
      description: null,
      gtfsServiceId: null,
      monday: null,
      tuesday: null,
      wednesday: null,
      thursday: null,
      friday: null,
      saturday: null,
      sunday: null,
      startDate: null,
      endDate: null
    },
    urlRoot: G.config.baseUrl + 'api/calendar/',
    // days, start_date, end_date, exceptions[]

    getRoutesText: function () {
      var routes = this.get('routes');
      if (routes.length > 5) {
        routes = routes.slice(0, 5);
        routes.push('...');
      }

      return routes.join(', ');
    }
  });

  G.Calendars = Backbone.Collection.extend({
    type: 'Calendars',
    model: G.Calendar,
    url: G.config.baseUrl + 'api/calendar/',

    /** get text labels for the routes in each calendar */
    getRoutesText: function () {
      this.each(function (model) {
        model.set('routesText', model.getRoutesText());
      });
    }
  });

G.Trip = Backbone.Model.extend({
    defaults: {
      tripDescription: null,
      pattern: null,
      serviceCalendar: null,
      startTime: null,
      endTime: null,
      headway: null,
      useFrequency: null
    },

    clone: function () {
      var ret = new G.Trip(this.toJSON());

      // wipe out the IDs
      ret.set('id', null);
      ret.set('gtfsTripId', null);
      ret.set('blockId', null);

      // clone the stop times
      var stopTimes = _.map(this.get('stopTimes'), function (st) {
        // clone
        st = _.extend({}, st);
        st.id = null;
        return st;
      });

      ret.set('stopTimes', stopTimes);

      return ret;
    }
   });

  G.Trips = Backbone.Collection.extend({
    type: 'Trips',
    model: G.Trip,
    url: G.config.baseUrl + 'api/trip/',

    comparator: function (trip) {
      var sts = trip.get('stopTimes');
      if (!_.isUndefined(sts) && sts != null && sts.length > 0) {
        return sts[0].departureTime;
      }
      // 500 hours past midnight should put these trips at the end
      // the longest train journey is Moscow to Pyongyang (210 h), per Wikipedia
      return 500 * 60 * 60;
    },

    initialize: function(opts) {
      if(opts != undefined)
        this.patternId  = opts.patternId;
    },

    fetchTrips: function(onSuccess) {
      if(onSuccess)
        this.fetch({data: {patternId: this.patternId}, success: onSuccess});
      else
        this.fetch({data: {patternId: this.patternId}});
    },

    clearTrips: function() {
      var _this = this;

      var deleteTripModels = function() {
        var model;

        while (model = _this.first()) {
          model.destroy();
        }
      }

      this.fetchTrips(deleteTripModels)
    }
  });

  /** Represents an exception to the schedule */
  G.ScheduleException = Backbone.Model.extend({
    defaults: {
      agency: null,
      exemplar: null,
      dates: null,
      customSchedule: null
    },

    /** add a javascript date object to the effective dates of this */
    addDate: function (date) {
      var dates = this.get('dates');

      if (dates == null)
        dates = [];

      var foundDate = false;

      _.each(dates, function (rawDate) {
        var exDate = new Date(rawDate);
        if (exDate.getUTCDate() == date.getUTCDate() && exDate.getUTCMonth() == date.getUTCMonth() &&
          exDate.getUTCFullYear() == date.getUTCFullYear())
          foundDate = true;
      });

      if (!foundDate) {
        dates.push(date.getTime());
        this.set('dates', dates);
      }
    },

    removeDate: function (year, month, dayOfMonth) {
      var dates = _.filter(this.get('dates'), function (epoch) {
        var date = new Date(epoch);
        return date.getUTCFullYear() != year || date.getUTCMonth() != month || date.getUTCDate() != dayOfMonth;
      });

      this.set('dates', dates);
    },

    urlRoot: G.config.baseUrl + 'api/scheduleexception/'
  });

  G.ScheduleExceptions = Backbone.Collection.extend({
    model: G.ScheduleException,
    url: G.config.baseUrl + 'api/scheduleexception/'
  });

})(GtfsEditor, jQuery);
<commit_msg>Reverse Stop sequence now reverse shape. Timing calc now return good times<commit_after>var GtfsEditor = GtfsEditor || {};

(function(G, $) {

  G.Agency = Backbone.Model.extend({
    urlRoot: G.config.baseUrl + 'api/agency/',

    defaults: {
      id: null,
      gtfsAgencyId: null,
      name: null,
      url: null,
      timezone: null,
      lang: null,
      phone: null,
      defaultLat: null,
      defaultLon: null,
      defaultRouteType: null
    }
  });

  G.Agencies = Backbone.Collection.extend({
    type: 'Agencies',
    model: G.Agency,
    url: G.config.baseUrl + 'api/agency/'
  });

G.RouteType = Backbone.Model.extend({
    urlRoot: G.config.baseUrl + 'api/routetype/',

    defaults: {
      id: null,
      gtfsRouteType: null,
      hvtRouteType: null,
      localizedVehicleType: null,
      description: null
    }
  });

G.RouteTypes = Backbone.Collection.extend({
    type: 'RouteTypes',
    model: G.RouteType,
    url: G.config.baseUrl + 'api/routetype/'
  });

  G.Route = Backbone.Model.extend({
    defaults: {
      id: null,
      status: 'IN_PROGRESS',
      publiclyVisible: false,
      gtfsRouteId: null,
      routeShortName: null,
      routeLongName: null,
      routeDesc: null,
      routeType: null,
      routeUrl: null,
      routeColor: 'FFFFFF',
      routeTextColor: '000000',
      agency: null
    },

    initialize: function() {
      this.tripPatterns = new G.TripPatterns();
    }
  });

  G.Routes = Backbone.Collection.extend({
    type: 'Routes',
    model: G.Route,
    url: G.config.baseUrl + 'api/route/'
  });

  G.Stop = Backbone.Model.extend({
    defaults: {
      id: null,
      gtfsStopId: null,
      stopCode: null,
      stopName: null,
      stopDesc: null,
      zoneId: null,
      stopUrl: null,
      agency: null,
      locationType: null,
      parentStation: null,
      bikeParking: null,
      carParking: null,
      wheelchairBoarding: null,
      majorStop: false,
      location: null
    },

    blacklist: ['justAdded','lat','lng'],
    toJSON: function(options) {
        return _.omit(this.attributes, this.blacklist);
    },

    set: function(attributes, options) {
            //my custom code
        var finalAttributes = _.omit(attributes, this.blacklist);

        if(attributes.lat != undefined &&  attributes.lng != undefined)
          finalAttributes['location'] = {lat: attributes.lat, lng: attributes.lng};

        return Backbone.Model.prototype.set.call(this, finalAttributes, options);
    }
  });

  G.Stops = Backbone.Collection.extend({
    type: 'Stops',
    model: G.Stop,
    url: G.config.baseUrl + 'api/stop/'
  });

  G.StopGroup = Backbone.Model.extend({
    defaults: {
      id: null,
      mergedStop: null,
      stops: []
    },
    initialize: function() {
       this.set('stops', []);

       _.bindAll(this, 'setMergedStop', 'addStop', 'addGroup', 'merge', 'onFinishedMerge');
    },

    addStop: function(stop) {

      if(this.existingStops == undefined) {
        this.existingStops = {}
      }

      // make first stop the merged stop by default
      if(this.get('mergedStop') == null)
        this.set('mergedStop', stop);

      // don't add duplicate stops
      if(!this.existingStops[stop.id]) {

        this.existingStops[stop.id] = true;

        var stops = this.get('stops');
        stops.push(stop);

        this.set('stops', stops);
      }

    },

    setMergedStop: function(stop) {
        this.addStop(stop);
        this.set('mergedStop', stop);
    },

    addGroup: function(group){

      var existingGroup = this;

      // merge stops into group
      _.each(group.stops, function(stop){
        if(!_.contains(existingStops, stop.id)) {
          existingGroup.addStop(stop);
        }
      });
    },

    onFinishedMerge: function(evt) {
      this.trigger('merge');
    },

    merge: function() {

        if(this.get('mergedStop') != null && this.get('stops').length > 0) {

          var ids = _.without(_.pluck(this.get('stops'), 'id'), this.get('mergedStop').id);
          var idList = ids.join(',');

          $.get(G.config.baseUrl + 'api/mergeStops', {stop1Id: this.get('mergedStop').id, mergedStopIds: idList}, this.onFinishedMerge);
        }

    }

  });

  G.StopGroups = Backbone.Collection.extend({
    type: 'StopGroups',
    model: G.StopGroup,

    initialize: function(opts) {

      this.agencyId = opts.agencyId;
      this.success = opts.success;
      this.groupMap = {};

      this.on('merge', this.onMerge);

      _.bindAll(this, 'loadGroups', 'group', 'findDuplicateStops', 'onMerge');
    },

    onMerge: function(evt) {
      this.findDuplicateStops();
    },

    findDuplicateStops: function() {

      this.reset();
      this.groupMap = {};

      $.get(G.config.baseUrl + 'api/findDuplicateStops', {agencyId: this.agencyId}, this.loadGroups);
    },

    loadGroups: function(pairs) {

      this.success();

      var _stopGroups = this;

      _.each(pairs, function(pair) {

        _stopGroups.group(new G.Stop(pair[0]), new G.Stop(pair[1]));

      });
    },

    merge: function(groupId) {

      this.groupMap[groupId].merge();

    },

    group: function(stop1, stop2){

      if(this.groupMap[stop1.id] == undefined) {
        if(this.groupMap[stop2.id] != undefined) {
          // add stop1 to existing group for stop2
          (this.groupMap[stop2.id]).addStop(stop1);
        }
        else {
          // add stop1 and stop2 to new group
          this.groupMap[stop1.id] = new G.StopGroup();
          (this.groupMap[stop1.id]).addStop(stop1);
          (this.groupMap[stop1.id]).addStop(stop2);

          this.add(this.groupMap[stop1.id]);
        }
      }
      else {
        if(this.groupMap[stop2.id] == undefined) {
          // add stop2 to existing group for stop1
          this.groupMap[stop1.id].addStop(stop2);
        }
        else {
          // both stop1 and stop2 belong to existing group, merge stop2 group into stop1 group
          this.groupMap[stop1.id].addGroup(this.groupMap[stop2.id]);
        }
      }
    }

  });

  G.TripPattern = Backbone.Model.extend({
    defaults: {
      id: null,
      name: null,
      headsign: null,
      encodedShape: null,
      patternStops: [],
      shape: null,
      route: null,
      useFrequency: null
    },

    urlRoot: G.config.baseUrl + 'api/trippattern/',

    initialize: function() {
      this.on('change:patternStops', this.normalizeSequence, this);

      this.trips = new G.Trips({patternId: this.id});

      this.sortPatternStops();
    },

    /**
     * Get all of the pattern stops for a particular stop.
     * Generally there will only be one, but there can be more on a loop route.
     * If you'd like only one stop, see getPatternStop(stopId, stopSequence)
     */
    getPatternStops: function(stopId) {
      return this.isPatternStop(stopId);
    },

    isPatternStop: function(stopId) {
      var patternStops = _.filter(this.get('patternStops'), function(ps, i) {
        return ps.stop.id == stopId;
      });
      return patternStops.length > 0 ? patternStops : false;
    },

    /**
     * Get the pattern stop for this stop ID and stop sequence.
     * If you don't have a stop sequence, see getPatternStops(stopId)
     */
     getPatternStop: function (stopId, stopSequence) {
       var ret = _.find(this.get('patternStops'), function (ps) {
         return ps.stop.id == stopId && ps.stopSequence == stopSequence;
       });

        // match the behavior of getPatternStop/isPatternStop
       return ret === null ? false : ret;
     },

    getPatternStopLabel: function(stopId) {
      var stopsSequences = [];
      _.each(this.get('patternStops'), function(ps, i) {
        if(ps.stop.id == stopId) {
          stopsSequences.push(ps.stopSequence);
        }
      });
      return stopsSequences.join(" & ");
    },

    reverse: function() {

      var patternStops = this.get('patternStops');
      var newPatternStops = new Array();

      _.each(patternStops, function(stop, i){
        if(i+1 < patternStops.length) {
          stop.defaultTravelTime = patternStops[i+1].defaultTravelTime;
          stop.defaultDwellTime = patternStops[i+1].defaultDwellTime;
        }

        if(patternStops.length == i+1) {
          stop.defaultTravelTime = null;
          stop.defaultDwellTime = null;
        }

        newPatternStops.push(stop)
      });

      newPatternStops.reverse();

      this.set('patternStops', newPatternStops);

      this.normalizeSequence();

      var latlngs = (new L.EncodedPolyline(this.get('encodedShape'))).getLatLngs();

      latlngs.reverse();

      var reversedLine = createEncodedPolyline(L.polyline(latlngs));

      this.set('encodedShape', reversedLine);

    },

    sortPatternStops: function() {
      var patternStops = _.sortBy(this.get('patternStops'), function(ps){
        return ps.stopSequence;
      });

      this.set('patternStops', patternStops, {silent: true});
    },

    normalizeSequence: function () {
      _.each(this.get('patternStops'), function(ps, i) {
        ps.stopSequence = i+1;
      });
      this.sortPatternStops();
      //this.save();
    },

    validate: function(attrs) {
      // Override the sequence value to match the array order
      this.sortPatternStops();
    },

    // name, headsign, alignment, stop_times[], shape, route_id (fk)
    // stop_id, travel_time, dwell_time

    addStop: function(stopTime) {
      var patternStops = this.get('patternStops');
      patternStops.push(stopTime);
      this.set('patternStops', patternStops);
      this.normalizeSequence();
    },

    insertStopAt: function(stopTime, i) {
      var patternStops = this.get('patternStops');
      patternStops.splice(i, 0, stopTime);
      this.set('patternStops', patternStops);
      this.normalizeSequence();
    },

    removeStopAt: function(i) {
      var patternStops = this.get('patternStops'),
          removed = patternStops.splice(i, 1)[0];
      this.set('patternStops', patternStops);
      this.normalizeSequence();
      return removed;
    },

    removeAllStops: function() {
      this.set('patternStops', []);
    },

    moveStopTo: function(fromIndex, toIndex) {
      var stopTimes = this.get('patternStops'),
          stopTime;

      stopTime = this.removeStopAt(fromIndex);
      this.insertStopAt(stopTime, toIndex);
      this.normalizeSequence();
    },

    updatePatternStop: function(data) {
      var patternStops = this.get('patternStops');
      this.removeAllStops();
      this.save();

      patternStops[data.stopSequence] = data;
      this.set('patternStops', patternStops);
      this.save();
    },

    useFrequency: function() {
      this.trips.clearTrips();
      this.set('useFrequency', true);
      this.save();
    },

    useTimetable: function() {
      this.trips.clearTrips();
      this.set('useFrequency', false);
      this.save()
    }

  });

  G.TripPatterns = Backbone.Collection.extend({
    type: 'TripPatterns',
    model: G.TripPattern,
    url: G.config.baseUrl + 'api/trippattern/'
  });

  G.Calendar = Backbone.Model.extend({
    defaults: {
      id: null,
      agency: null,
      description: null,
      gtfsServiceId: null,
      monday: null,
      tuesday: null,
      wednesday: null,
      thursday: null,
      friday: null,
      saturday: null,
      sunday: null,
      startDate: null,
      endDate: null
    },
    urlRoot: G.config.baseUrl + 'api/calendar/',
    // days, start_date, end_date, exceptions[]

    getRoutesText: function () {
      var routes = this.get('routes');
      if (routes.length > 5) {
        routes = routes.slice(0, 5);
        routes.push('...');
      }

      return routes.join(', ');
    }
  });

  G.Calendars = Backbone.Collection.extend({
    type: 'Calendars',
    model: G.Calendar,
    url: G.config.baseUrl + 'api/calendar/',

    /** get text labels for the routes in each calendar */
    getRoutesText: function () {
      this.each(function (model) {
        model.set('routesText', model.getRoutesText());
      });
    }
  });

G.Trip = Backbone.Model.extend({
    defaults: {
      tripDescription: null,
      pattern: null,
      serviceCalendar: null,
      startTime: null,
      endTime: null,
      headway: null,
      useFrequency: null
    },

    clone: function () {
      var ret = new G.Trip(this.toJSON());

      // wipe out the IDs
      ret.set('id', null);
      ret.set('gtfsTripId', null);
      ret.set('blockId', null);

      // clone the stop times
      var stopTimes = _.map(this.get('stopTimes'), function (st) {
        // clone
        st = _.extend({}, st);
        st.id = null;
        return st;
      });

      ret.set('stopTimes', stopTimes);

      return ret;
    }
   });

  G.Trips = Backbone.Collection.extend({
    type: 'Trips',
    model: G.Trip,
    url: G.config.baseUrl + 'api/trip/',

    comparator: function (trip) {
      var sts = trip.get('stopTimes');
      if (!_.isUndefined(sts) && sts != null && sts.length > 0) {
        return sts[0].departureTime;
      }
      // 500 hours past midnight should put these trips at the end
      // the longest train journey is Moscow to Pyongyang (210 h), per Wikipedia
      return 500 * 60 * 60;
    },

    initialize: function(opts) {
      if(opts != undefined)
        this.patternId  = opts.patternId;
    },

    fetchTrips: function(onSuccess) {
      if(onSuccess)
        this.fetch({data: {patternId: this.patternId}, success: onSuccess});
      else
        this.fetch({data: {patternId: this.patternId}});
    },

    clearTrips: function() {
      var _this = this;

      var deleteTripModels = function() {
        var model;

        while (model = _this.first()) {
          model.destroy();
        }
      }

      this.fetchTrips(deleteTripModels)
    }
  });

  /** Represents an exception to the schedule */
  G.ScheduleException = Backbone.Model.extend({
    defaults: {
      agency: null,
      exemplar: null,
      dates: null,
      customSchedule: null
    },

    /** add a javascript date object to the effective dates of this */
    addDate: function (date) {
      var dates = this.get('dates');

      if (dates == null)
        dates = [];

      var foundDate = false;

      _.each(dates, function (rawDate) {
        var exDate = new Date(rawDate);
        if (exDate.getUTCDate() == date.getUTCDate() && exDate.getUTCMonth() == date.getUTCMonth() &&
          exDate.getUTCFullYear() == date.getUTCFullYear())
          foundDate = true;
      });

      if (!foundDate) {
        dates.push(date.getTime());
        this.set('dates', dates);
      }
    },

    removeDate: function (year, month, dayOfMonth) {
      var dates = _.filter(this.get('dates'), function (epoch) {
        var date = new Date(epoch);
        return date.getUTCFullYear() != year || date.getUTCMonth() != month || date.getUTCDate() != dayOfMonth;
      });

      this.set('dates', dates);
    },

    urlRoot: G.config.baseUrl + 'api/scheduleexception/'
  });

  G.ScheduleExceptions = Backbone.Collection.extend({
    model: G.ScheduleException,
    url: G.config.baseUrl + 'api/scheduleexception/'
  });

})(GtfsEditor, jQuery);
<|endoftext|>


<commit_before>const express = require("express");
const Ratelimit = require("express-brute");
const PasswordChangeController = require("../controllers/PasswordChangeController");
const JWTController = require("../controllers/JWTController");
const DeactivateAccountController = require("../controllers/DeactivateAccountController");
const BoardImageController = require("../controllers/BoardImageController");
const PlaceController = require("../controllers/PlaceController");
const PixelInfoController = require("../controllers/PixelInfoController");
const FeatureAvailabilityController = require("../controllers/FeatureAvailabilityController");
const ChatController = require("../controllers/ChatController");
const AdminActionsController = require("../controllers/AdminActionsController");
const ModeratorUserController = require("../controllers/ModeratorUserController");
const AuthController = require("../controllers/AuthController");
const AccountPageController = require("../controllers/AccountPageController");
const TOTPSetupController = require("../controllers/TOTPSetupController");
const ChangelogController = require("../controllers/ChangelogController");
const WarpController = require("../controllers/WarpController");
const UserDownloadController = require("../controllers/UserDownloadController");

function APIRouter(app) {
    let router = express.Router();

    router.use(function(req, res, next) {
        res.header("Cache-Control", "private, no-cache, no-store, must-revalidate");
        res.header("Expires", "-1");
        res.header("Pragma", "no-cache");
        next();
    })

    router.use(function(req, res, next) {
        if (req.user && !req.user.usernameSet && req.user.OAuthName) return res.status(401).json({
            success: false,
            error: {
                message: "Please create a username for your account before continuing.",
                code: "oauth_no_username"
            }
        });
        if (req.user && req.user.passwordResetKey) return res.status(401).json({
            success: false,
            error: {
                message: `Please go to the ${req.place.config.siteName} website to reset your password.`,
                code: "forced_password_reset"
            }
        });
        if(!req.user) return next();
        req.user.getMustAcceptTOS().then((mustAcceptTOS) => {
            if(!mustAcceptTOS) return next();
            res.status(401).json({
                success: false,
                error: {
                    message: `Please go to the ${req.place.config.siteName} website to accept the Terms of Service.`,
                    code: "tos_not_accepted"
                }
            })
        }).catch(err => next());
    });

    const requireUser = (req, res, next) => {
        if (!req.user) return res.status(401).json({
            success: false,
            error: {
                message: "You are not signed in.",
                code: "not_signed_in"
            }
        });
        next()
    }

    // Normal APIs

    const signupRatelimit = new Ratelimit(require("../util/RatelimitStore")(), {
        freeRetries: 3, // 3 signups per hour
        attachResetToRequest: false,
        refreshTimeoutOnRequest: false,
        minWait: 60 * 60 * 1000, // 1 hour
        maxWait: 60 * 60 * 1000, // 1 hour,
        failCallback: (req, res, next, nextValidRequestDate) => {
            res.status(429).json({success: false, error:{message: "You're doing that too fast."}});
        },
        handleStoreError: (error) => app.reportError("Sign up rate limit store error:", error),
        proxyDepth: app.config.trustProxyDepth
    });

    router.post("/signin", AuthController.postSignIn);
    router.post("/signup", signupRatelimit.prevent, AuthController.postSignUp);

    router.post("/identify", JWTController.identifyAPIUser);

    router.route("/user/totp-setup", requireUser).get([requireUser, TOTPSetupController.getTOTPSetup]).post([requireUser, TOTPSetupController.postTOTPSetup]).delete([requireUser, TOTPSetupController.deleteTOTPSetup]);
    router.post("/user/change-password", requireUser, PasswordChangeController.postSelfServePassword);

    router.post("/user/deactivate", requireUser, DeactivateAccountController.postAPIDeactivate);
    router.delete("/user", requireUser, DeactivateAccountController.deleteAccount);

    router.get("/session", requireUser, function(req, res, next) {
        res.json({
            success: true,
            user: req.user.toInfo(app)
        });
    });

    router.get("/board-image", BoardImageController.getAPIBoardImage);

    router.get("/feature-availability", FeatureAvailabilityController.getAvailability);
    router.post("/beta-signup", requireUser, FeatureAvailabilityController.betaSignup);

    router.post("/place", requireUser, PlaceController.postAPIPixel);

    router.get("/timer", requireUser, PlaceController.getAPITimer);

    const accountDataRatelimit = new Ratelimit(require("../util/RatelimitStore")(), {
        freeRetries: 1, // 1 download per hour
        attachResetToRequest: false,
        refreshTimeoutOnRequest: false,
        minWait: 60 * 60 * 1000, // 1 hour
        maxWait: 3 * 60 * 60 * 1000, // 3 hour,
        failCallback: (req, res, next, nextValidRequestDate) => {
            res.status(429).json({success: false, error:{message: "You're doing that too fast."}});
        },
        handleStoreError: (error) => app.reportError("Account data rate limit store error:", error),
        proxyDepth: app.config.trustProxyDepth
    });

    router.get("/account-data", [requireUser, accountDataRatelimit.prevent], UserDownloadController.getAccountData);

    router.get("/online", function(req, res, next) {
        return res.json({
            success: true,
            online: {
                count: req.place.websocketServer.connectedClients
            }
        });
    });

    router.get("/active-now", function(req, res, next) {
        app.userActivityController.getInfo().then((info) => {
            return res.json({
                success: true,
                active: info
            });
        }).catch((err) => res.status(500).json({
            success: false
        }))
    });

    router.get("/pixel", PixelInfoController.getAPIPixelInfo);
    router.get("/pos-info", PixelInfoController.getAPIPixelInfo);

    router.get("/leaderboard", function(req, res, next) {
        app.leaderboardManager.getInfo((err, info) => {
            if (err || !info) {
                if (err) app.reportError("Error fetching leaderboard: " + err);
                if (res.headersSent) return null;
                return res.status(500).json({
                    success: false
                });
            }
            res.json({
                success: true,
                leaderboard: info.leaderboard.splice(0, 25),
                lastUpdated: info.lastUpdated
            });
        })
    });

    const chatRatelimit = new Ratelimit(require("../util/RatelimitStore")("Chat"), {
        freeRetries: 7, // 7 messages per 10-15 seconds
        attachResetToRequest: false,
        refreshTimeoutOnRequest: false,
        minWait: 10 * 1000, // 10 seconds
        maxWait: 15 * 1000, // 15 seconds,
        lifetime: 25, // remember spam for max of 25 seconds
        failCallback: (req, res, next, nextValidRequestDate) => {
            var seconds = Math.round((nextValidRequestDate - new Date()) / 1000);
            return res.status(429).json({
                success: false,
                error: {
                    message: `You're sending messages too fast! To avoid spamming the chat, please get everything into one message if you can. You will be able to chat again in ${seconds.toLocaleString()} second${seconds == 1 ? "" : "s"}.`,
                    code: "rate_limit"
                }
            })
        },
        handleStoreError: (error) => app.reportError("Chat rate limit store error:", error),
        proxyDepth: app.config.trustProxyDepth
    });

    router.route("/chat").get(ChatController.getAPIChat).post([requireUser, chatRatelimit.prevent], ChatController.postAPIChatMessage);

    router.get("/user/:username", AccountPageController.getAPIAccount);

    router.get("/changelog/latest", ChangelogController.getLatestChangelog);
    router.route("/changelog/missed").get([requireUser, ChangelogController.getMissedChangelogs]).post([requireUser, ChangelogController.postMissedChangelogs]).delete([requireUser, ChangelogController.deleteMissedChangelogs]);
    router.get("/changelog/:version", ChangelogController.getChangelog);

    router.route("/warps").get([requireUser, WarpController.getWarps]).post([requireUser, WarpController.postWarp]);
    router.route("/warps/:id").get([requireUser, WarpController.getWarp]).delete([requireUser, WarpController.deleteWarp]);

    // Admin APIs

    router.get("/admin/stats", app.modMiddleware, AdminActionsController.getAPIStats);
    router.get("/admin/refresh_clients", app.adminMiddleware, AdminActionsController.apiRefreshClients);
    router.get("/admin/reload_config", app.adminMiddleware, AdminActionsController.apiReloadConfig);
    router.post("/admin/broadcast", app.adminMiddleware, AdminActionsController.apiBroadcastAlert);
    router.delete("/user/:userID", app.adminMiddleware, AdminActionsController.deleteUser);

    router.post("/admin/users", app.modMiddleware, ModeratorUserController.getAPIUsersTable);
    router.get("/admin/toggle_mod", app.adminMiddleware, ModeratorUserController.postAPIToggleModerator);
    router.get("/admin/disable_totp", app.adminMiddleware, ModeratorUserController.postAPIDisableTOTP);
    router.get("/admin/force_pw_reset", app.adminMiddleware, ModeratorUserController.postAPIForcePasswordReset);

    // Mod APIs

    router.get("/mod/toggle_ban", app.modMiddleware, ModeratorUserController.postAPIToggleBan);
    router.get("/mod/toggle_active", app.modMiddleware, ModeratorUserController.postAPIToggleActive);
    router.route("/mod/user_notes").get(app.modMiddleware, ModeratorUserController.getAPIUserNotes).post(app.modMiddleware, ModeratorUserController.postAPIUserNotes)
    router.get("/mod/similar_users/:userID", app.modMiddleware, ModeratorUserController.getAPISimilarUsers);
    router.get("/mod/actions", app.modMiddleware, ModeratorUserController.getAPIActions);

    return router;
}

APIRouter.prototype = Object.create(APIRouter.prototype);

module.exports = APIRouter;
<commit_msg>Fix #232<commit_after>const express = require("express");
const Ratelimit = require("express-brute");
const PasswordChangeController = require("../controllers/PasswordChangeController");
const JWTController = require("../controllers/JWTController");
const DeactivateAccountController = require("../controllers/DeactivateAccountController");
const BoardImageController = require("../controllers/BoardImageController");
const PlaceController = require("../controllers/PlaceController");
const PixelInfoController = require("../controllers/PixelInfoController");
const FeatureAvailabilityController = require("../controllers/FeatureAvailabilityController");
const ChatController = require("../controllers/ChatController");
const AdminActionsController = require("../controllers/AdminActionsController");
const ModeratorUserController = require("../controllers/ModeratorUserController");
const AuthController = require("../controllers/AuthController");
const AccountPageController = require("../controllers/AccountPageController");
const TOTPSetupController = require("../controllers/TOTPSetupController");
const ChangelogController = require("../controllers/ChangelogController");
const WarpController = require("../controllers/WarpController");
const UserDownloadController = require("../controllers/UserDownloadController");

function APIRouter(app) {
    let router = express.Router();

    router.use(function(req, res, next) {
        res.header("Cache-Control", "private, no-cache, no-store, must-revalidate");
        res.header("Expires", "-1");
        res.header("Pragma", "no-cache");
        next();
    })

    router.use(function(req, res, next) {
        if (req.user && !req.user.usernameSet && req.user.OAuthName) return res.status(401).json({
            success: false,
            error: {
                message: "Please create a username for your account before continuing.",
                code: "oauth_no_username"
            }
        });
        if (req.user && req.user.passwordResetKey) return res.status(401).json({
            success: false,
            error: {
                message: `Please go to the ${req.place.config.siteName} website to reset your password.`,
                code: "forced_password_reset"
            }
        });
        if(!req.user) return next();
        req.user.getMustAcceptTOS().then((mustAcceptTOS) => {
            if(!mustAcceptTOS) return next();
            res.status(401).json({
                success: false,
                error: {
                    message: `Please go to the ${req.place.config.siteName} website to accept the Terms of Service.`,
                    code: "tos_not_accepted"
                }
            })
        }).catch(err => next());
    });

    const requireUser = (req, res, next) => {
        if (!req.user) return res.status(401).json({
            success: false,
            error: {
                message: "You are not signed in.",
                code: "not_signed_in"
            }
        });
        next()
    }

    // Normal APIs

    const signUpRatelimit = new Ratelimit(require("../util/RatelimitStore")(), {
        freeRetries: 3, // 3 sign ups per hour
        attachResetToRequest: false,
        refreshTimeoutOnRequest: false,
        minWait: 60 * 60 * 1000, // 1 hour
        maxWait: 60 * 60 * 1000, // 1 hour,
        failCallback: (req, res, next, nextValidRequestDate) => {
            res.status(429).json({success: false, error: {message: "You have exceeded the amount of accounts you can create."}});
        },
        handleStoreError: (error) => app.reportError("Sign up rate limit store error:", error),
        proxyDepth: app.config.trustProxyDepth
    });

    const signInRatelimit = new Ratelimit(require("../util/RatelimitStore")(), {
        freeRetries: 5, // 5 sign in attempts per 15-60 minutes
        attachResetToRequest: false,
        refreshTimeoutOnRequest: false,
        minWait: 15 * 60 * 1000, // 15 minutes,
        maxWait: 60 * 60 * 1000, // 1 hour,
        failCallback: (req, res, next, nextValidRequestDate) => {
            res.status(429).json({success: false, error: {message: "You have exceeded the sign in attempt limit. Try again later."}});
        },
        handleStoreError: (error) => app.reportError("Sign in rate limit store error:", error),
        proxyDepth: app.config.trustProxyDepth
    });

    router.post("/signin", signInRatelimit.prevent, AuthController.postSignIn);
    router.post("/signup", signUpRatelimit.prevent, AuthController.postSignUp);

    router.post("/identify", JWTController.identifyAPIUser);

    router.route("/user/totp-setup", requireUser).get([requireUser, TOTPSetupController.getTOTPSetup]).post([requireUser, TOTPSetupController.postTOTPSetup]).delete([requireUser, TOTPSetupController.deleteTOTPSetup]);
    router.post("/user/change-password", requireUser, PasswordChangeController.postSelfServePassword);

    router.post("/user/deactivate", requireUser, DeactivateAccountController.postAPIDeactivate);
    router.delete("/user", requireUser, DeactivateAccountController.deleteAccount);

    router.get("/session", requireUser, function(req, res, next) {
        res.json({
            success: true,
            user: req.user.toInfo(app)
        });
    });

    router.get("/board-image", BoardImageController.getAPIBoardImage);

    router.get("/feature-availability", FeatureAvailabilityController.getAvailability);
    router.post("/beta-signup", requireUser, FeatureAvailabilityController.betaSignup);

    router.post("/place", requireUser, PlaceController.postAPIPixel);

    router.get("/timer", requireUser, PlaceController.getAPITimer);

    const accountDataRatelimit = new Ratelimit(require("../util/RatelimitStore")(), {
        freeRetries: 1, // 1 download per hour
        attachResetToRequest: false,
        refreshTimeoutOnRequest: false,
        minWait: 60 * 60 * 1000, // 1 hour
        maxWait: 3 * 60 * 60 * 1000, // 3 hour,
        failCallback: (req, res, next, nextValidRequestDate) => {
            res.status(429).json({success: false, error:{message: "You're doing that too fast."}});
        },
        handleStoreError: (error) => app.reportError("Account data rate limit store error:", error),
        proxyDepth: app.config.trustProxyDepth
    });

    router.get("/account-data", [requireUser, accountDataRatelimit.prevent], UserDownloadController.getAccountData);

    router.get("/online", function(req, res, next) {
        return res.json({
            success: true,
            online: {
                count: req.place.websocketServer.connectedClients
            }
        });
    });

    router.get("/active-now", function(req, res, next) {
        app.userActivityController.getInfo().then((info) => {
            return res.json({
                success: true,
                active: info
            });
        }).catch((err) => res.status(500).json({
            success: false
        }))
    });

    router.get("/pixel", PixelInfoController.getAPIPixelInfo);
    router.get("/pos-info", PixelInfoController.getAPIPixelInfo);

    router.get("/leaderboard", function(req, res, next) {
        app.leaderboardManager.getInfo((err, info) => {
            if (err || !info) {
                if (err) app.reportError("Error fetching leaderboard: " + err);
                if (res.headersSent) return null;
                return res.status(500).json({
                    success: false
                });
            }
            res.json({
                success: true,
                leaderboard: info.leaderboard.splice(0, 25),
                lastUpdated: info.lastUpdated
            });
        })
    });

    const chatRatelimit = new Ratelimit(require("../util/RatelimitStore")("Chat"), {
        freeRetries: 7, // 7 messages per 10-15 seconds
        attachResetToRequest: false,
        refreshTimeoutOnRequest: false,
        minWait: 10 * 1000, // 10 seconds
        maxWait: 15 * 1000, // 15 seconds,
        lifetime: 25, // remember spam for max of 25 seconds
        failCallback: (req, res, next, nextValidRequestDate) => {
            var seconds = Math.round((nextValidRequestDate - new Date()) / 1000);
            return res.status(429).json({
                success: false,
                error: {
                    message: `You're sending messages too fast! To avoid spamming the chat, please get everything into one message if you can. You will be able to chat again in ${seconds.toLocaleString()} second${seconds == 1 ? "" : "s"}.`,
                    code: "rate_limit"
                }
            })
        },
        handleStoreError: (error) => app.reportError("Chat rate limit store error:", error),
        proxyDepth: app.config.trustProxyDepth
    });

    router.route("/chat").get(ChatController.getAPIChat).post([requireUser, chatRatelimit.prevent], ChatController.postAPIChatMessage);

    router.get("/user/:username", AccountPageController.getAPIAccount);

    router.get("/changelog/latest", ChangelogController.getLatestChangelog);
    router.route("/changelog/missed").get([requireUser, ChangelogController.getMissedChangelogs]).post([requireUser, ChangelogController.postMissedChangelogs]).delete([requireUser, ChangelogController.deleteMissedChangelogs]);
    router.get("/changelog/:version", ChangelogController.getChangelog);

    router.route("/warps").get([requireUser, WarpController.getWarps]).post([requireUser, WarpController.postWarp]);
    router.route("/warps/:id").get([requireUser, WarpController.getWarp]).delete([requireUser, WarpController.deleteWarp]);

    // Admin APIs

    router.get("/admin/stats", app.modMiddleware, AdminActionsController.getAPIStats);
    router.get("/admin/refresh_clients", app.adminMiddleware, AdminActionsController.apiRefreshClients);
    router.get("/admin/reload_config", app.adminMiddleware, AdminActionsController.apiReloadConfig);
    router.post("/admin/broadcast", app.adminMiddleware, AdminActionsController.apiBroadcastAlert);
    router.delete("/user/:userID", app.adminMiddleware, AdminActionsController.deleteUser);

    router.post("/admin/users", app.modMiddleware, ModeratorUserController.getAPIUsersTable);
    router.get("/admin/toggle_mod", app.adminMiddleware, ModeratorUserController.postAPIToggleModerator);
    router.get("/admin/disable_totp", app.adminMiddleware, ModeratorUserController.postAPIDisableTOTP);
    router.get("/admin/force_pw_reset", app.adminMiddleware, ModeratorUserController.postAPIForcePasswordReset);

    // Mod APIs

    router.get("/mod/toggle_ban", app.modMiddleware, ModeratorUserController.postAPIToggleBan);
    router.get("/mod/toggle_active", app.modMiddleware, ModeratorUserController.postAPIToggleActive);
    router.route("/mod/user_notes").get(app.modMiddleware, ModeratorUserController.getAPIUserNotes).post(app.modMiddleware, ModeratorUserController.postAPIUserNotes)
    router.get("/mod/similar_users/:userID", app.modMiddleware, ModeratorUserController.getAPISimilarUsers);
    router.get("/mod/actions", app.modMiddleware, ModeratorUserController.getAPIActions);

    return router;
}

APIRouter.prototype = Object.create(APIRouter.prototype);

module.exports = APIRouter;
<|endoftext|>


<commit_before>"use strict";

define([
    'jquery',
    'underscore',
    'backbone',
    'views/HomeView',
    'views/projects/ProjectsListView',
    'views/ResumeView'
], function($, _, Backbone, HomeView, ProjectsListView, ResumeView) {
    var AppRouter = Backbone.Router.extend({
        routes: {
            // Define some URL routes
            'projects': 'showProjectsList',
            'resume': 'showResume',
            // Default
            '*actions': 'defaultAction'
        },

        titles: {
            'showProjectsList': 'Projects',
            'showResume': 'Resume',
            'default': 'Tetris'
        },

        initialize: function(options) {
            var that = this;

            this.on('route', function(router, route, params) {
                if (that.titles) {
                    document.title = (
                        that.titles[router] ? that.titles[router] + ' - ' : ''
                    ) + (
                        that.titles['default'] ? that.titles['default'] : ''
                    );
                }
            });
        }
    });

    var initialize = function() {
        var router = new AppRouter();

        router.on('route:showProjectsList', function() {
            var projectsListView = new ProjectsListView();
            projectsListView.render();
        });

        router.on('route:showResume', function() {
            var resumeView = new ResumeView();
            resumeView.render();
        });

        router.on('route:defaultAction', function(actions) {
            // no matching route, fall back to HomeView
            var homeView = new HomeView();
            homeView.render();
        });

        Backbone.history.start({
            pushState: true
        });

        attachGlobalClickListener();
        attachGlobalScrollListener();
        attachGlobalNavMenuToggleListener();
    };

    var attachGlobalClickListener = function() {
        // on every click, check if it's an href that can be handled by the router
        $(document).on('click', 'a', function(evt) {
            var a = $(this);
            var rel = a.attr('rel');
            var href = a.attr('href');
            var protocol = this.protocol + '//';

            if (rel == 'external') {
                a.attr('target', '_blank');
                return true;
            }

            if (href.slice(protocol.length) !== protocol &&
                protocol !== 'javascript://' &&
                href.substring(0, 1) !== '#'
            ) {
                evt.preventDefault();
                Backbone.history.navigate(href, {trigger: true});
            }
        });
    };

    var attachGlobalScrollListener = function() {
        $(window).scroll(function() {
            var smallLogoHeight = $('.small-logo').height();
            var bigLogoHeight = $('.big-logo-container').height();
            var navbarHeight = $('.navbar-header').height();

            var smallLogoEndPos = 0;
            var smallSpeed = (smallLogoHeight / bigLogoHeight);
            var ySmall = ($(window).scrollTop() * smallSpeed);
            var smallPadding = navbarHeight - ySmall;

            if (smallPadding > navbarHeight) {
                smallPadding = navbarHeight;
            }
            if (smallPadding < smallLogoEndPos) {
                smallPadding = smallLogoEndPos;
            }
            if (smallPadding < 0) {
                smallPadding = 0;
            }

            $('.small-logo-container').css({
                'width': (smallLogoHeight > smallPadding) ? '50px' : 0,
                'padding-top': smallPadding
            });

            var navOpacity = ySmall / smallLogoHeight;
            if (navOpacity > 1) {
                navOpacity = 1;
            }
            if (navOpacity < 0) {
                navOpacity = 0;
            }

            var shadowOpacity = navOpacity * 0.3;
            if (ySmall > 1) {
                $('.navbar').css({'box-shadow': '0 2px 3px rgba(0,0,0,' + shadowOpacity + ')'});
            } else {
                $('.navbar').css({'box-shadow': 'none'});
            }
        });
    };

    var attachGlobalNavMenuToggleListener = function() {
        var originalPadding = parseFloat($('.big-logo-row').css('padding-top'));

        if (_.isNaN(originalPadding)) {
            originalPadding = 0;
        }

        $('.navbar .collapse').on('\
            shown.bs.collapse\
            hidden.bs.collapse\
        ', function() {
            $('.big-logo-row').css({
                'padding-top': originalPadding + $(this).height() + 'px'
            });
        });
    };

    return {
        initialize: initialize
    };
});
<commit_msg>Added view cleanup method loadView and auto toggle collapse nav menu code<commit_after>"use strict";

define([
    'jquery',
    'underscore',
    'backbone',
    'views/HomeView',
    'views/projects/ProjectsListView',
    'views/ResumeView'
], function($, _, Backbone, HomeView, ProjectsListView, ResumeView) {
    var AppRouter = Backbone.Router.extend({
        routes: {
            // Define some URL routes
            'projects': 'showProjectsList',
            'resume': 'showResume',
            // Default
            '*actions': 'defaultAction'
        },

        titles: {
            'showProjectsList': 'Projects',
            'showResume': 'Resume',
            'default': 'Tetris'
        },

        initialize: function(options) {
            var that = this;

            this.on('route', function(router, route, params) {
                if (that.titles) {
                    document.title = (
                        that.titles[router] ? that.titles[router] + ' - ' : ''
                    ) + (
                        that.titles['default'] ? that.titles['default'] : ''
                    );
                }
            });
        },

        loadView: function(view) {
            if (this.view) {
                if (this.view.close) {
                    this.view.close();
                } else
                    this.view.remove();
            }
            this.view = view;

            return this.view;
        }
    });

    var initialize = function(options) {
        var router = new AppRouter();

        router
            .on('route:showProjectsList', function() {
                $('.tetris-main-container').append(
                    this.loadView(new ProjectsListView(options)).render().el
                );
            })
            .on('route:showResume', function() {
                $('.tetris-main-container').append(
                    this.loadView(new ResumeView(options)).render().el
                );
            })
            .on('route:defaultAction', function(actions) {
                // no matching route, fall back to HomeView
                $('.tetris-main-container').append(
                    this.loadView(new HomeView(options)).render().el
                );
            });

        Backbone.history.start({
            pushState: true
        });

        attachGlobalClickListener();
        attachGlobalScrollListener();
        attachGlobalNavMenuToggleListener();
    };

    var attachGlobalClickListener = function() {
        // on every click, check if it's an href that can be handled by the router
        $(document).on('click', 'a', function(evt) {
            var a = $(this);
            var rel = a.attr('rel');
            var href = a.attr('href');
            var protocol = this.protocol + '//';

            if (rel == 'external') {
                a.attr('target', '_blank');
                return true;
            }

            if (href.slice(protocol.length) !== protocol &&
                protocol !== 'javascript://' &&
                href.substring(0, 1) !== '#'
            ) {
                evt.preventDefault();
                Backbone.history.navigate(href, {trigger: true});
            }
        });
    };

    var attachGlobalScrollListener = function() {
        $(window).scroll(function() {
            var smallLogoHeight = $('.small-logo').height();
            var bigLogoHeight = $('.big-logo-container').height();
            var navbarHeight = $('.navbar-header').height();

            var smallLogoEndPos = 0;
            var smallSpeed = (smallLogoHeight / bigLogoHeight);
            var ySmall = ($(window).scrollTop() * smallSpeed);
            var smallPadding = navbarHeight - ySmall;

            if (smallPadding > navbarHeight) {
                smallPadding = navbarHeight;
            }
            if (smallPadding < smallLogoEndPos) {
                smallPadding = smallLogoEndPos;
            }
            if (smallPadding < 0) {
                smallPadding = 0;
            }

            $('.small-logo-container').css({
                'width': (smallLogoHeight > smallPadding) ? '50px' : 0,
                'padding-top': smallPadding
            });

            var navOpacity = ySmall / smallLogoHeight;
            if (navOpacity > 1) {
                navOpacity = 1;
            }
            if (navOpacity < 0) {
                navOpacity = 0;
            }

            var shadowOpacity = navOpacity * 0.3;
            if (ySmall > 1) {
                $('.navbar').css({'box-shadow': '0 2px 3px rgba(0,0,0,' + shadowOpacity + ')'});
            } else {
                $('.navbar').css({'box-shadow': 'none'});
            }
        });
    };

    var attachGlobalNavMenuToggleListener = function() {
        var originalPadding = parseFloat($('.big-logo-row').css('padding-top'));

        if (_.isNaN(originalPadding)) {
            originalPadding = 0;
        }

        $('.navbar .collapse')
            .on('shown.bs.collapse hidden.bs.collapse', function() {
                $('.big-logo-row').css({
                    'padding-top': originalPadding + $(this).height() + 'px'
                });
            })
            .on('click', 'a', function(evt) {
                $(evt.delegateTarget).collapse('toggle');
            });
    };

    return {
        initialize: initialize
    };
});
<|endoftext|>


<commit_before>var express = require('express');
var router = express.Router();
var querystring = require('querystring');
var path = require('path');
var config = require('../util/settings_handler');
var db = require('../util/YDBHandler')(config.getConfig().path);
var forever = require('forever-monitor');

router.get("/", function(req, res, next) {
    if (req.query.cmd) {
        var cmd = req.query.cmd;
        switch (cmd) {
            case 'restart':
                console.log('restarting server...');
                //TODO: restart server
                forever.restart();
                res.redirect('/comiclist?per_page=10'); //placeholder redirect
                break;
            case 'reset':
                console.log('resetting server');
                //TODO: reset server
                break;
            case 'stop':
                console.log('stopping server');
                //TODO: stop server
                break;
            default:
                next();
        }
    } else {
        next();
    }
});

//TODO: restructure to accept query arguments
router.get('/comiclist', function(req, res) {
    res.type('application/json');
    var queryParams = {};

    queryParams.keyphrase = req.query.keyphrase;
    queryParams.series = req.query.series;
    queryParams.path = req.query.path;
    queryParams.folder = req.query.folder;
    queryParams.title = req.query.title;
    queryParams.start_date = req.query.start_date;
    queryParams.end_date = req.query.end_date;
    queryParams.added_since = req.query.added_since;
    queryParams.modified_since = req.query.modified_since;
    queryParams.lastread_since = req.query.lastread_since;
    queryParams.character = req.query.character;
    queryParams.team = req.query.team;
    queryParams.location = req.query.location;
    queryParams.storyarc = req.query.storyarc;
    queryParams.volume = req.query.volume;
    queryParams.publisher = req.query.publisher;
    queryParams.credit = req.query.credit;
    queryParams.tag = req.query.tag;
    queryParams.genre = req.query.genre;
    queryParams.filename = req.query.filename;

    //paging params
    queryParams.order = req.query.order;
    queryParams.limit = req.query.per_page;
    queryParams.offset = req.query.offset;

    db.getComicInfo(queryParams, function(err, rows) {
        if (err) console.log(err);
        res.send(JSON.stringify({
            total_count: rows.length,
            page_count: rows.length,
            comics: rows
        }));
    });
});

//Searches comics for a specific id
router.get('/comic/:id', function(req, res) {
    //console.log(req.params.id);
    var params = {};
    params.id = req.params.id;
    db.getComicInfo(params, function(err, rows) {
        if (err) {
            console.log(err);
            res.status(404).end();
            return;
        }
        res.send(JSON.stringify({
            total_count: rows.length,
            page_count: rows.length,
            comics: rows
        }));
    });
});

//TODO: convert this to comicstreamer compatible
//This should point to the file to the comic with the ID passed as a parameter
router.get('/comic/:id/file', function(req, res) {
    var id = req.params.id;

    db.getComicFile(id, function(err, row) {

        if (err) {
            console.log(err);
        }
        if (row) {

            row.path = path.normalize(config.getConfig().path + row.path);
            console.log("PATH: " + row.path);

            var mimeType = 'application/octet-stream';
            if (path.extname(row.path) === '.cbz') {
                mimeType = 'application/x-cbz';
            } else if (path.extname(row.path) === '.cbr') {
                mimeType = 'application/x-cdisplay';
            }

            var options = {
                headers: {
                    'x-timestamp': Date.now(),
                    'x-sent': true,
                    'Content-Type': mimeType,
                    'Content-Disposition': 'filename=' + path.basename(row.path)
                }
            };
            res.sendFile(row.path, options, function(err) {
                if (err) {
                    console.log(err);
                    res.status(err.status).end();
                } else {
                    console.log("Sent: " + row.path);
                }

            });
        } else {
            res.status(404).end();
            console.log("Error: Invalid ID - the db query returned nothing");
        }
    });
});

router.get('/comic/:id/thumbnail', function(req, res, next) {
    var queryParams = {};
    queryParams.id = req.params.id;
    db.getComicInfo(queryParams, function(err, rows) {
        if (err) {
            console.log(err);
        }

        if (rows[0]) {
            var thumbnailPath = path.normalize(config.getConfig().path + '/.yacreaderlibrary/covers/' + rows[0].hash + '.jpg');
            console.log(thumbnailPath);
            var mimeType = 'image/jpeg';
            var options = {
                headers: {
                    'x-timestamp': Date.now(),
                    'x-sent': true,
                    'Content-Type': mimeType,
                    'Content-Disposition': 'filename=' + rows[0].hash + '.jpg'
                }
            };
            res.sendFile(thumbnailPath, options, function(err) {
                if (err) {
                    console.log(err);
                    res.status(err.status).end();
                } else {
                    console.log('Sent: ' + thumbnailPath);
                }
            });
        } else {
            next();
        }
    });
});

//Returns a image of page in comic book
router.get('/comic/:id/page/:pagenum', function(req, res) {
    console.log("Comic ID: " + req.params.id + " Page Num: " + req.params.pagenum);
    //TODO: extract comic file and serve up the page
    res.status(404).end();
});

router.get('/folders/0/:path', function(req, res) {
    res.type('application/json');
    var path = "/" + req.params.path;
    console.log("Path: " + path);
    db.getFolders(path, function(result) {
        res.send(JSON.stringify(result));
    });
});

router.get('/folders/0:path', function(req, res) {
    res.type('application/json');
    console.log("stupid path quirks...");
    var path = querystring.unescape(req.params.path);
    console.log("Path: " + path);
    db.getFolders(path, function(result) {
        res.send(JSON.stringify(result));
    });
});

router.get('/folders/0/', function(req, res) {
    res.type('application/json');
    var path = '/';
    console.log("Path: " + path + "0");
    db.getFolders(path, function(result) {
        res.send(JSON.stringify(result));
    });
});

//TEST
router.get('/folders', function(req, res) {
    res.type('application/json');
    //TODO: NOT hardcode this
    var path = '/';
    console.log("Path: root");
    var root = {
        current: "",
        folders: [{
            url_path: "/folders/0",
            name: "root"
        }],
        comics: {
            count: 0,
            url_path: ""
        }
    };
    res.send(JSON.stringify(root));

});

router.get('/version', function(req, res) {
    res.type('application/json');
    res.send(JSON.stringify({
        server: "MgaServer v0.1",
        version: "0.0.7"
    }));
});

router.get('/dbinfo', function(req, res) {
    res.type('application/json');
    //TODO: return DB info.
    db.getDBInfo(function(err, dbinfo) {
        if(err) {
            res.send({error: "There was an error"});
            return;
        }
        res.send(JSON.stringify(dbinfo))
    });
});

module.exports = router;

//Quick check to see if an we have an empty object
function isEmpty(obj) {
    for (var prop in obj) {
        if (obj.hasOwnProperties(prop))
            return false;
    }
    return true;
}<commit_msg>Compatibility with Chunky which uses /dbinfo to check compatibility<commit_after>var express = require('express');
var router = express.Router();
var querystring = require('querystring');
var path = require('path');
var config = require('../util/settings_handler');
//var db = require('../util/YDBHandler')(config.getConfig().path);
var db = require('../util/database');
var forever = require('forever-monitor');

router.get("/", function(req, res, next) {
    if (req.query.cmd) {
        var cmd = req.query.cmd;
        switch (cmd) {
            case 'restart':
                console.log('restarting server...');
                //TODO: restart server
                forever.restart();
                res.redirect('/comiclist?per_page=10'); //placeholder redirect
                break;
            case 'reset':
                console.log('resetting server');
                //TODO: reset server
                break;
            case 'stop':
                console.log('stopping server');
                //TODO: stop server
                break;
            default:
                next();
        }
    } else {
        next();
    }
});

//TODO: restructure to accept query arguments
router.get('/comiclist', function(req, res) {
    res.type('application/json');
    var queryParams = {};

    queryParams.keyphrase = req.query.keyphrase;
    queryParams.series = req.query.series;
    queryParams.path = req.query.path;
    queryParams.folder = req.query.folder;
    queryParams.title = req.query.title;
    queryParams.start_date = req.query.start_date;
    queryParams.end_date = req.query.end_date;
    queryParams.added_since = req.query.added_since;
    queryParams.modified_since = req.query.modified_since;
    queryParams.lastread_since = req.query.lastread_since;
    queryParams.character = req.query.character;
    queryParams.team = req.query.team;
    queryParams.location = req.query.location;
    queryParams.storyarc = req.query.storyarc;
    queryParams.volume = req.query.volume;
    queryParams.publisher = req.query.publisher;
    queryParams.credit = req.query.credit;
    queryParams.tag = req.query.tag;
    queryParams.genre = req.query.genre;
    queryParams.filename = req.query.filename;

    //paging params
    queryParams.order = req.query.order;
    queryParams.limit = req.query.per_page;
    queryParams.offset = req.query.offset;

    db.getComicInfo(queryParams, function(err, rows) {
        if (err) console.log(err);
        res.send(JSON.stringify({
            total_count: rows.length,
            page_count: rows.length,
            comics: rows
        }));
    });
});

//Searches comics for a specific id
router.get('/comic/:id', function(req, res) {
    //console.log(req.params.id);
    var params = {};
    params.id = req.params.id;
    db.getComicInfo(params, function(err, rows) {
        if (err) {
            console.log(err);
            res.status(404).end();
            return;
        }
        res.send(JSON.stringify({
            total_count: rows.length,
            page_count: rows.length,
            comics: rows
        }));
    });
});

//TODO: convert this to comicstreamer compatible
//This should point to the file to the comic with the ID passed as a parameter
router.get('/comic/:id/file', function(req, res) {
    var id = req.params.id;

    db.getComicFile(id, function(err, row) {

        if (err) {
            console.log(err);
        }
        if (row) {

            row.path = path.normalize(config.getConfig().path + row.path);
            console.log("PATH: " + row.path);

            var mimeType = 'application/octet-stream';
            if (path.extname(row.path) === '.cbz') {
                mimeType = 'application/x-cbz';
            } else if (path.extname(row.path) === '.cbr') {
                mimeType = 'application/x-cdisplay';
            }

            var options = {
                headers: {
                    'x-timestamp': Date.now(),
                    'x-sent': true,
                    'Content-Type': mimeType,
                    'Content-Disposition': 'filename=' + path.basename(row.path)
                }
            };
            res.sendFile(row.path, options, function(err) {
                if (err) {
                    console.log(err);
                    res.status(err.status).end();
                } else {
                    console.log("Sent: " + row.path);
                }

            });
        } else {
            res.status(404).end();
            console.log("Error: Invalid ID - the db query returned nothing");
        }
    });
});

router.get('/comic/:id/thumbnail', function(req, res, next) {
    var queryParams = {};
    queryParams.id = req.params.id;
    db.getComicInfo(queryParams, function(err, rows) {
        if (err) {
            console.log(err);
        }

        if (rows[0]) {
            var thumbnailPath = path.normalize(config.getConfig().path + '/.yacreaderlibrary/covers/' + rows[0].hash + '.jpg');
            console.log(thumbnailPath);
            var mimeType = 'image/jpeg';
            var options = {
                headers: {
                    'x-timestamp': Date.now(),
                    'x-sent': true,
                    'Content-Type': mimeType,
                    'Content-Disposition': 'filename=' + rows[0].hash + '.jpg'
                }
            };
            res.sendFile(thumbnailPath, options, function(err) {
                if (err) {
                    console.log(err);
                    res.status(err.status).end();
                } else {
                    console.log('Sent: ' + thumbnailPath);
                }
            });
        } else {
            next();
        }
    });
});

//Returns a image of page in comic book
router.get('/comic/:id/page/:pagenum', function(req, res) {
    console.log("Comic ID: " + req.params.id + " Page Num: " + req.params.pagenum);
    //TODO: extract comic file and serve up the page
    res.status(404).end();
});

router.get('/folders/0/:path', function(req, res) {
    res.type('application/json');
    var path = "/" + req.params.path;
    console.log("Path: " + path);
    db.getFolders(path, function(result) {
        res.send(JSON.stringify(result));
    });
});

router.get('/folders/0:path', function(req, res) {
    res.type('application/json');
    console.log("stupid path quirks...");
    var path = querystring.unescape(req.params.path);
    console.log("Path: " + path);
    db.getFolders(path, function(result) {
        res.send(JSON.stringify(result));
    });
});

router.get('/folders/0/', function(req, res) {
    res.type('application/json');
    var path = '/';
    console.log("Path: " + path + "0");
    db.getFolders(path, function(result) {
        res.send(JSON.stringify(result));
    });
});

//TEST
router.get('/folders', function(req, res) {
    res.type('application/json');
    //TODO: NOT hardcode this
    var path = '/';
    console.log("Path: root");
    var root = {
        current: "",
        folders: [{
            url_path: "/folders/0",
            name: "root"
        }],
        comics: {
            count: 0,
            url_path: ""
        }
    };
    res.send(JSON.stringify(root));

});

router.get('/version', function(req, res) {
    res.type('application/json');
    res.send(JSON.stringify({
        server: "MgaServer v0.1",
        version: "0.0.7"
    }));
});

router.get('/dbinfo', function(req, res) {
    res.type('application/json');
    //TODO: return DB info.
    /*db.getDBInfo(function(err, dbinfo) {
        if(err) {
            res.send({error: "There was an error"});
            return;
        }
        res.send(JSON.stringify(dbinfo))
    });*/
    res.send(JSON.stringify({"comic_count": 12199, "last_updated": "2015-08-13T21:56:37.372000", "id": "f03b53dbd5364377867227e23112d3c7", "created": "2015-06-18T19:13:35.030000"}));
});

router.get('/entities[\\s\\S]*', function(req, res) {
  var params = req.params[0].split('/');
  res.send(JSON.stringify(params));
});

module.exports = router;

//Quick check to see if an we have an empty object
function isEmpty(obj) {
    for (var prop in obj) {
        if (obj.hasOwnProperties(prop))
            return false;
    }
    return true;
}
<|endoftext|>


<commit_before>
/**
 * Created by expro on 14-1-10.
 * 积分管理
 */
function PointsController($scope, Points, Pagination, $timeout, $injector){

    $injector.invoke(BasicController, this, {$scope: $scope});
    $scope.resource = Points;
    $scope.searchOptions.tooltip = "请输入会员ID"
    $scope.editView = "views/points/edit.html"
    $scope.profileAvatar = "img/avatar.jpg"

    // profile
    $scope.profileFields = [
//        {name: "id", title: "id", listHide:true},
        {name: "point", title: "积分数",required:true},
        {name: "memberID", title: "会员ID",required:true },
        {name: "merchantID", title: "商户ID",required:true},
        {name: "createdAt", title: "创建时间",required:true },

        {name: "postPoint", title: "累积积分",required:true},
        {name: "postTotalPoint", title: "累计积分",required:true},
        {name: "agentID", title: "agentID", listHide:true,required:true},
        {name: "shopID", title: "商店ID", listHide:true,required:true},
        {name: "reason", title: "原因", listHide:true,required:true}

    ];

    // bussiness
    $scope.setStatus = function(status) {
        $scope.entity.status = status
        $scope.update(true)
    };
    $scope.update = function(entity) {
        console.log('update=', entity);
        delete entity.merchant;
        delete entity.code;
        delete entity.shop;
        delete entity.createdAt;
        delete entity.userID;
        var resource = new $scope.resource(entity);
        console.log('update=' + JSON.stringify(resource));
        resource.$update(function(err) {
            $scope.showList()
        }, function(err) {
            console.log('update user error:', err)
        })
    };
    $scope.valueOfKeyString = function(entity, keyString) {
        var v = entity
        var keys = keyString.split('.')
        var theKey = keys[0]
        keys.forEach(function(key){
            theKey = key
            v = v[key]
        })
        if(theKey === 'sinceAt' || theKey === 'dueAt' || theKey === 'createdAt' || theKey === 'updateAt') {
            // var d = new Date(v*1000)
            // v = d.getFullYear()+'-'+d.getMonth()+'-'+d.getDate()+' '+d.getHours()+':'+d.getMinutes()+':'+d.getSeconds()
            v = new Date(v*1000).toLocaleString()
        }
        return v
    }

    $scope.fieldOperations = [
        {class: "btn btn-success", icon: "fa fa-file", op: "showProfile"}
        ,	{class: "btn btn-danger", icon: "fa fa-trash-o", op:"remove"}
    ]

    // profile
    $scope.profileShortcuts = [
        {class: "box quick-button-small col-lg-1 col-md-2 col-xs-6", icon: "fa fa-trash-o", text: "删除", op:"remove"}
    ];
    $scope.isHide = true; //隐藏新增按钮
    $scope.params['merchantID'] = $scope.currentMerchant.id;
    $scope.countQs['merchantID'] = $scope.currentMerchant.id;
    $scope.defaultString = "memberID";
}<commit_msg>fixed#120<commit_after>
/**
 * Created by expro on 14-1-10.
 * 积分管理
 */
function PointsController($scope, Points, Pagination, $timeout, $injector){

    $injector.invoke(BasicController, this, {$scope: $scope});
    $scope.resource = Points;
    $scope.searchOptions.tooltip = "请输入会员ID"
    $scope.editView = "views/points/edit.html"
    $scope.profileAvatar = "img/avatar.jpg"

    // profile
    $scope.profileFields = [
//        {name: "id", title: "id", listHide:true},
        {name: "point", title: "积分数",required:true},
        {name: "memberID", title: "会员ID",required:true },
        {name: "merchantID", title: "商户ID",required:true},
        {name: "createdAt", title: "创建时间",required:true },

        {name: "postPoint", title: "当前积分",required:true},
        {name: "postTotalPoint", title: "累计积分",required:true},
        {name: "agentID", title: "agentID", listHide:true,required:true},
        {name: "shopID", title: "商店ID", listHide:true,required:true},
        {name: "reason", title: "原因", listHide:true,required:true}

    ];

    // bussiness
    $scope.setStatus = function(status) {
        $scope.entity.status = status
        $scope.update(true)
    };
    $scope.update = function(entity) {
        console.log('update=', entity);
        delete entity.merchant;
        delete entity.code;
        delete entity.shop;
        delete entity.createdAt;
        delete entity.userID;
        var resource = new $scope.resource(entity);
        console.log('update=' + JSON.stringify(resource));
        resource.$update(function(err) {
            $scope.showList()
        }, function(err) {
            console.log('update user error:', err)
        })
    };
    $scope.valueOfKeyString = function(entity, keyString) {
        var v = entity
        var keys = keyString.split('.')
        var theKey = keys[0]
        keys.forEach(function(key){
            theKey = key
            v = v[key]
        })
        if(theKey === 'sinceAt' || theKey === 'dueAt' || theKey === 'createdAt' || theKey === 'updateAt') {
            // var d = new Date(v*1000)
            // v = d.getFullYear()+'-'+d.getMonth()+'-'+d.getDate()+' '+d.getHours()+':'+d.getMinutes()+':'+d.getSeconds()
            v = new Date(v*1000).toLocaleString()
        }
        return v
    }

    $scope.fieldOperations = [
        {class: "btn btn-success", icon: "fa fa-file", op: "showProfile"}
        ,	{class: "btn btn-danger", icon: "fa fa-trash-o", op:"remove"}
    ]

    // profile
    $scope.profileShortcuts = [
        {class: "box quick-button-small col-lg-1 col-md-2 col-xs-6", icon: "fa fa-trash-o", text: "删除", op:"remove"}
    ];
    $scope.isHide = true; //隐藏新增按钮
    $scope.params['merchantID'] = $scope.currentMerchant.id;
    $scope.countQs['merchantID'] = $scope.currentMerchant.id;
    $scope.defaultString = "memberID";
}<|endoftext|>


<commit_before><commit_msg>api testing<commit_after><|endoftext|>


<commit_before>$(document).ready(function() {
	// this is for custom formating of results
   $.extend( $.ui.autocomplete.prototype, {
    _renderItem: function( ul, item) {
      var list = "

";
      if (item['class']) {
        list = "

";
      }
			// temp until the service actually returns us a type
			else{
				list = "

";
			}
      return $( list )
        .data( "item.autocomplete", item )
        .append( $( "" ).html( item.html || item.label ) )
        .appendTo( ul );
    }
  });

  $.ajax({
    url: "/autocomplete?q=",
    isLocal:true,
    dataType: "json",
    cache: true,
    success: function(data) {
      var results = $.map(data, function(e) {
        return { 'label': e.title, 'url': "/"+e.link, 'class': e.format };
      });
      $('#site-search-text').autocomplete({
        delay: 0,
				width:300,
        source: results,
        select: function(event, ui) {
          location.href = ui.item.link;
        },
				open: function(event, ui){
					// all this just to move the ul to the left by an offset
					var offset = $("#site-search-text").offset(),
						leftoffset = offset.left,
						width = $("#site-search-text").width();

					var newLeft = (leftoffset - width);
					$(".ui-autocomplete").css("left", newLeft+"px").css("width", ((width*2)+4)+"px");

					// quickly add the search value to end of list
					var searchVal = $("#site-search-text").attr("value");
					$(".ui-autocomplete").append("
Search for "+searchVal+"
");
					}
      });

    }

  });
});
<commit_msg>Autosuggest hits JSON feed now<commit_after>$(document).ready(function() {
	// this is for custom formating of results
   $.extend( $.ui.autocomplete.prototype, {
    _renderItem: function( ul, item) {
      var list = "

";
      if (item['class']) {
        list = "

";
      }
			// temp until the service actually returns us a type
			else{
				list = "

";
			}
      return $( list )
        .data( "item.autocomplete", item )
        .append( $( "" ).html( item.html || item.label ) )
        .appendTo( ul );
    }
  });

  $("#site-search-text").autocomplete({
    delay: 300,
    width: 300,
    source: function(req, add){
      $.ajax({
        url: "/autocomplete?q="+req.term,
        dataType: "json",
        cache: true,
        success: function(data) {
          var results = $.map(data, function(e) {
            return { 'label': e.title, 'url': "/"+e.link, 'class': e.format };
          });
          add(results)
        }
      });
    },
    select: function(event, ui) {
      location.href = ui.item.link;
    },
    open: function(event, ui){
      // all this just to move the ul to the left by an offset
      var offset = $("#site-search-text").offset(),
      leftoffset = offset.left,
      width = $("#site-search-text").width();

      var newLeft = (leftoffset - width);
      $(".ui-autocomplete").css("left", newLeft+"px").css("width", ((width*2)+4)+"px");

      // quickly add the search value to end of list
      var searchVal = $("#site-search-text").attr("value");
      $(".ui-autocomplete").append("
Search for "+searchVal+"
");
    }
  });

});
<|endoftext|>


<commit_before>/**
 * This module monitors angularFire's authentication and performs actions based on authentication state.
 *
 * See usage examples here: https://gist.github.com/katowulf/7328023
 */
angular.module('waitForAuth', [])

/**
 * A service that returns a promise object, which is resolved once $firebaseSimpleLogin
 * is initialized (i.e. it returns login, logout, or error)
 */
   .service('waitForAuth', function($rootScope, $q, $timeout) {
      var def = $q.defer(), subs = [];
      subs.push($rootScope.$on('$firebaseSimpleLogin:login', fn));
      subs.push($rootScope.$on('$firebaseSimpleLogin:logout', fn));
      subs.push($rootScope.$on('$firebaseSimpleLogin:error', fn));
      function fn(err) {
         if( $rootScope.auth ) {
            $rootScope.auth.error = err instanceof Error? err.toString() : null;
         }
         for(var i=0; i < subs.length; i++) { subs[i](); }
         $timeout(function() {
            // force $scope.$apply to be re-run after login resolves
            def.resolve();
         });
      }
      return def.promise;
   })

/**
 * A directive that hides the element from view until waitForAuth resolves
 */
   .directive('ngCloakAuth', function(waitForAuth) {
      return {
         restrict: 'A',
         compile: function(el) {
            el.addClass('hide');
            waitForAuth.then(function() {
               el.removeClass('hide');
            })
         }
      }
   })

/**
 * A directive that shows elements only when the given authentication state is in effect
 */
   .directive('ngShowAuth', function($rootScope) {
      var loginState;
      $rootScope.$on("$firebaseSimpleLogin:login",  function() { loginState = 'login' });
      $rootScope.$on("$firebaseSimpleLogin:logout", function() { loginState = 'logout' });
      $rootScope.$on("$firebaseSimpleLogin:error",  function() { loginState = 'error' });
      function inList(needle, list) {
         var res = false;
         angular.forEach(list, function(x) {
            if( x === needle ) {
               res = true;
               return true;
            }
            return false;
         });
         return res;
      }
      function assertValidState(state) {
         if( !state ) {
            throw new Error('ng-show-auth directive must be login, logout, or error (you may use a comma-separated list)');
         }
         var states = (state||'').split(',');
         angular.forEach(states, function(s) {
            if( !inList(s, ['login', 'logout', 'error']) ) {
               throw new Error('Invalid state "'+s+'" for ng-show-auth directive, must be one of login, logout, or error');
            }
         });
         return true;
      }
      return {
         restrict: 'A',
         compile: function(el, attr) {
            assertValidState(attr.ngShowAuth);
            var expState = (attr.ngShowAuth||'').split(',');
            function fn(newState) {
               loginState = newState;
               var hide = !inList(newState, expState);
               el.toggleClass('hide', hide );
            }
            fn(loginState);
            $rootScope.$on("$firebaseSimpleLogin:login",  function() { fn('login') });
            $rootScope.$on("$firebaseSimpleLogin:logout", function() { fn('logout') });
            $rootScope.$on("$firebaseSimpleLogin:error",  function() { fn('error') });
         }
      }
   });<commit_msg>Using a variable for ng-show-auth would fail<commit_after>/**
 * This module monitors angularFire's authentication and performs actions based on authentication state.
 *
 * See usage examples here: https://gist.github.com/katowulf/7328023
 */
angular.module('waitForAuth', [])

/**
 * A service that returns a promise object, which is resolved once $firebaseSimpleLogin
 * is initialized (i.e. it returns login, logout, or error)
 */
   .service('waitForAuth', function($rootScope, $q, $timeout) {
      var def = $q.defer(), subs = [];
      subs.push($rootScope.$on('$firebaseSimpleLogin:login', fn));
      subs.push($rootScope.$on('$firebaseSimpleLogin:logout', fn));
      subs.push($rootScope.$on('$firebaseSimpleLogin:error', fn));
      function fn(err) {
         if( $rootScope.auth ) {
            $rootScope.auth.error = err instanceof Error? err.toString() : null;
         }
         for(var i=0; i < subs.length; i++) { subs[i](); }
         $timeout(function() {
            // force $scope.$apply to be re-run after login resolves
            def.resolve();
         });
      }
      return def.promise;
   })

/**
 * A directive that hides the element from view until waitForAuth resolves
 */
   .directive('ngCloakAuth', function(waitForAuth) {
      return {
         restrict: 'A',
         compile: function(el) {
            el.addClass('hide');
            waitForAuth.then(function() {
               el.removeClass('hide');
            })
         }
      }
   })

/**
 * A directive that shows elements only when the given authentication state is in effect
 */
   .directive('ngShowAuth', function($rootScope) {
      var loginState;
      $rootScope.$on("$firebaseSimpleLogin:login",  function() { loginState = 'login' });
      $rootScope.$on("$firebaseSimpleLogin:logout", function() { loginState = 'logout' });
      $rootScope.$on("$firebaseSimpleLogin:error",  function() { loginState = 'error' });
      function inList(needle, list) {
         var res = false;
         angular.forEach(list, function(x) {
            if( x === needle ) {
               res = true;
               return true;
            }
            return false;
         });
         return res;
      }
      function assertValidState(state) {
         if( !state ) {
            throw new Error('ng-show-auth directive must be login, logout, or error (you may use a comma-separated list)');
         }
         var states = (state||'').split(',');
         angular.forEach(states, function(s) {
            if( !inList(s, ['login', 'logout', 'error']) ) {
               throw new Error('Invalid state "'+s+'" for ng-show-auth directive, must be one of login, logout, or error');
            }
         });
         return true;
      }
      return {
         restrict: 'A',
         link: function(scope, el, attr) {
            var showAuth = scope.$eval(attr.ngShowAuth);
            if (showAuth == null) { showAuth = attr.ngShowAuth; }
            assertValidState(showAuth);
            var expState = (showAuth||'').split(',');
            function fn(newState) {
               loginState = newState;
               var hide = !inList(newState, expState);
               el.toggleClass('hide', hide );
            }
            fn(loginState);
            $rootScope.$on("$firebaseSimpleLogin:login",  function() { fn('login') });
            $rootScope.$on("$firebaseSimpleLogin:logout", function() { fn('logout') });
            $rootScope.$on("$firebaseSimpleLogin:error",  function() { fn('error') });
         }
      }
   });
<|endoftext|>