var _ = require('lodash'), xml = require('xml'), moment = require('moment'), api = require('../../api'), config = require('../../config'), utils = require('./utils'), Promise = require('bluebird'), CHANGE_FREQ = 'weekly', XMLNS_DECLS; // Sitemap specific xml namespace declarations that should not change XMLNS_DECLS = { _attr: { xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9', 'xmlns:image': 'http://www.google.com/schemas/sitemap-image/1.1' } }; function BaseSiteMapGenerator() { this.lastModified = 0; this.nodeLookup = {}; this.nodeTimeLookup = {}; this.siteMapContent = ''; } _.extend(BaseSiteMapGenerator.prototype, { init: function () { return this.refreshAll(); }, getData: function () { return Promise.resolve([]); }, refreshAll: function () { var self = this; // Load all data return this.getData().then(function (data) { // Generate SiteMap from data return self.generateXmlFromData(data); }).then(function (generatedXml) { self.siteMapContent = generatedXml; }); }, generateXmlFromData: function (data) { // This has to be async because of the permalinks retrieval var self = this; // Fetch the permalinks value only once for all the urlFor calls return this.getPermalinksValue().then(function (permalinks) { // Create all the url elements in JSON return _.map(data, function (datum) { var node = self.createUrlNodeFromDatum(datum, permalinks); self.updateLastModified(datum); self.updateLookups(datum, node); return node; }); }).then(this.generateXmlFromNodes.bind(this)); }, getPermalinksValue: function () { var self = this; if (this.permalinks) { return Promise.resolve(this.permalinks); } return api.settings.read('permalinks').then(function (response) { self.permalinks = response.settings[0]; return self.permalinks; }); }, updatePermalinksValue: function (permalinks) { this.permalinks = permalinks; // Re-generate xml with new permalinks values this.updateXmlFromNodes(); }, generateXmlFromNodes: function () { var self = this, // Get a mapping of node to timestamp timedNodes = _.map(this.nodeLookup, function (node, id) { return { id: id, // Using negative here to sort newest to oldest ts: -(self.nodeTimeLookup[id] || 0), node: node }; }, []), // Sort nodes by timestamp sortedNodes = _.sortBy(timedNodes, 'ts'), // Grab just the nodes urlElements = _.pluck(sortedNodes, 'node'), data = { // Concat the elements to the _attr declaration urlset: [XMLNS_DECLS].concat(urlElements) }; // Return the xml return utils.getDeclarations() + xml(data); }, updateXmlFromNodes: function (urlElements) { var content = this.generateXmlFromNodes(urlElements); this.setSiteMapContent(content); return content; }, addUrl: function (datum) { var self = this; return this.getPermalinksValue().then(function (permalinks) { var node = self.createUrlNodeFromDatum(datum, permalinks); self.updateLastModified(datum); self.updateLookups(datum, node); return self.updateXmlFromNodes(); }); }, removeUrl: function (datum) { this.removeFromLookups(datum); this.lastModified = Date.now(); return this.updateXmlFromNodes(); }, updateUrl: function (datum) { var self = this; return this.getPermalinksValue().then(function (permalinks) { var node = self.createUrlNodeFromDatum(datum, permalinks); self.updateLastModified(datum); // TODO: Check if the node values changed, and if not don't regenerate self.updateLookups(datum, node); return self.updateXmlFromNodes(); }); }, getUrlForDatum: function () { return config.urlFor('home', true); }, getUrlForImage: function (image) { return config.urlFor('image', {image: image}, true); }, getPriorityForDatum: function () { return 1.0; }, getLastModifiedForDatum: function (datum) { return datum.updated_at || datum.published_at || datum.created_at; }, createUrlNodeFromDatum: function (datum, permalinks) { var url = this.getUrlForDatum(datum, permalinks), priority = this.getPriorityForDatum(datum); return { url: [ {loc: url}, {lastmod: moment(this.getLastModifiedForDatum(datum)).toISOString()}, {changefreq: CHANGE_FREQ}, {priority: priority} ] }; }, setSiteMapContent: function (content) { this.siteMapContent = content; }, updateLastModified: function (datum) { var lastModified = this.getLastModifiedForDatum(datum); if (lastModified > this.lastModified) { this.lastModified = lastModified; } }, updateLookups: function (datum, node) { this.nodeLookup[datum.id] = node; this.nodeTimeLookup[datum.id] = this.getLastModifiedForDatum(datum); }, removeFromLookups: function (datum) { var lookup = this.nodeLookup; delete lookup[datum.id]; lookup = this.nodeTimeLookup; delete lookup[datum.id]; } }); module.exports = BaseSiteMapGenerator;