javascript - Apostrope CMS 中的文章作者不会向未登录的用户显示

标签 javascript node.js debugging content-management-system apostrophe-cms

我使用 Apostrophe CMS 创建了一个项目并不断开发新页面和文章。 我们发布的内容(文章和页面)将向公众(未登录用户)和登录用户(管理员)开放。

我已经了解了撇号 CMS 中架构的基础知识,并一直在尝试调试以下行为,即本问题的主题:文章的作者不会向未登录的用户显示

我不确定这是一个错误还是默认行为,但我确实需要一些帮助,因为我已经花了几个小时研究如何更改/修复这个问题。


到目前为止我调试过的内容

  1. 这是为登录用户显示文章缩略图的方式:

< logged-in article thumb >

  • 这就是对于登录用户的显示方式:
  • < non-logged-in article thumb >

    请注意上面的内容,如果我未登录,则不会显示作者。 这是呈现作者拇指的 HTML 部分:

    <div class="wrap-content">
                            
      {% if piece._author.thumbnail.items.length %}
        <img src="{{apos.attachments.url(apos.images.first(piece._author.thumbnail))}}" class="image-user" alt="">
      {% endif %}
    
      <h2 class="title">{{piece.title}}</h2>
      <p class="description">{{ piece.description }} </p>
    
  • 我发现req.user是在passport包中设置的,如下图所示:
  • node_modules/passport/lib/strategies/session.js

    < passport-set-req-user >

  • 我归结为一个“if”条件,用于检查用户是否已登录。在下图中,我尚未登录,因此 req.user 未定义。
  • node_modules/撇号/lib/modules/撇号-schemas/index.js

    < apostrophe-cms-logged-off >

  • 登录后,用户将被设置:
  • node_modules/撇号/lib/modules/撇号-schemas/index.js

    < apostrophe-cms-logged-in >

  • 登录后,apostrope-schemas 会执行 joinByOne 和其他类型的连接。我要加入的是:
  • lib/modules/apostrope-blog/index.js

    addFields: [
      {
        name: '_author',
        label: 'Author',
        type: 'joinByOne',
        withType: 'apostrophe-user',
        idField: 'userId',
      },
    

    其中userId是创建文章时登录的用户id。它以_author 身份加入。

  • 在我上面显示的html文件中,如果有“piece._author”,则显示作者,否则不显示。并不是因为我们从未加入,所以从来没有作者,因此如果我未登录,则不会显示作者。
  • 我已经尝试过注释“if”条件,但没有任何效果,因此没有成功(我可以看到正常列出的文章,但没有作者)。

    我们确实需要向所有人展示作者。我该怎么做?提前谢谢您。

    最佳答案

    因此,在@boutell的帮助下并在文档中进行研究,我解决了这个问题。

    本教程不太长,而且非常简单。

    概述

    1. 创建了新的个人资料片段类型 - 每个用户 1 个个人资料。
    2. 每个个人资料都有一个描述字段(例如“关于作者”部分)
    3. 每个 apostrope-blog 实体都有 1 个 _author(个人资料 ID)
    4. 插入用户后设置一个 Hook ,以创建并关联其个人资料
    5. 在插入撇号博客之前设置一个 Hook ,以关联登录用户的个人资料
    6. 创建迁移,为每个现有用户生成个人资料
    7. 创建迁移以交换apostrope-blog中的字段和值:
      • 发现问题时,我在 apostrope-blog 中已有一个 userId 字段,因此 migration 更改userIduserProfileId,该值是不言自明的。 如果您没有此字段或还没有任何apostrope-blog,则不需要此迁移。

    我使用的撇号模块的版本如下:

    "apostrophe": "^2.37.2",
    "apostrophe-blog": "^2.1.1",
    

    解决方案

    1. 创建了新的profiles单品类型。

    /app.js

        modules: {
            'apostrophe-blog': {},
            'apostrophe-tags': {},
            'profiles': {}, // <-- add this
    
  • 创建以下文件夹和文件:
  • /lib/modules/profiles/index.js

        /**
         * Module for adding "profile" entity. Every user has its profile. Before article (apostrophe-blog)
         * entities are inserted, they have the logged in user's profile id userprofile._id attached to
         * the article, as userProfileId field. This way, author of articles are always shown, even to
         * non-logged in users. In this file two migrations are included - add profile to existing
         * users and change from old "userId" that were in articles to new "userProfileId".
         * 
         * Run migration with node app.js apostrophe-migrations:migrate.
         * 
         * @author Alexandre Duarte (github.com/duartealexf)
         */
    
        const async = require('async');
    
        module.exports = {
    
            /**
             * Default stuff required by ApostropheCMS.
             */
            extend: 'apostrophe-pieces',
            name: 'profile',
            label: 'Profile',
            pluralLabel: 'Profiles',
            searchable: false,
    
            afterConstruct: function(self, callback) {
    
                /**
                 * Ensure collection is set and add migrations to DB.
                 */
                return async.series([
                    self.ensureCollection,
                    self.addUserProfileMigration,
                    self.addBlogPageAuthorMigration,
                ], callback);
            },
    
            beforeConstruct: function(self, options) {
    
                options.addFields = [
                    /**
                     * User of profile.
                     */
                    {
                        name: '_user',
                        label: 'User',
                        type: 'joinByOne',
                        withType: 'apostrophe-user',
                        idField: 'userId',
                    },
    
                    /**
                     * Optional profile description.
                     */
                    {
                        type: 'string',
                        textarea: true,
                        name: 'description',
                        label: 'Description',
                    },
    
                    /**
                     * Whether profile is published.
                     * Does not affect whether author is shown.
                     */
                    {
                        type: 'boolean',
                        name: 'published',
                        label: 'Published',
                        def: true,
                    },
    
                    /**
                     * Profile thumbnail.
                     */
                    {
                        name: 'thumbnail',
                        type: 'singleton',
                        widgetType: 'apostrophe-images',
                        label: 'Picture',
                        options: {
                            limit: 1,
                            aspectRatio: [100,100]
                        }
                    }
                ].concat(options.addFields || []);
            },
    
            construct: function(self, options) {
    
                /**
                 * Ensure collection variable is set.
                 * 
                 * @param {Function} callback 
                 */
                self.ensureCollection = function(callback) {
                    return self.apos.db.collection('aposDocs', function(err, collection) {
                        self.db = collection;
                        return callback(err);
                    });
                };
    
                /**
                 * Hook after inserting user. Actually watches on any doc insert,
                 * so we need the 'if' statement below.
                 *
                 * @param {any} req Request.
                 * @param {any} doc Doc being inserted.
                 * @param {any} options Options from hook.
                 * @param {any} callback
                 */
                self.docAfterInsert = function(req, doc, options, callback) {
    
                    /**
                     * No doc id, no change.
                     */
                    if (!doc._id) {
                        return setImmediate(callback);
                    }
    
                    /**
                     * If it is an user, we add the profile.
                     */
                    if (doc.type === 'apostrophe-user') {
                        return self.addUserProfile(req, doc, options, callback);
                    }
                    return setImmediate(callback);
                }
    
                /**
                 * Hook before inserting article.
                 * 
                 * @param {any} req Request.
                 * @param {any} doc Doc being inserted.
                 * @param {any} options Options from hook.
                 * @param {any} callback
                 */
                self.docBeforeInsert = function(req, doc, options, callback) {
    
                    /**
                     * No doc id, no change.
                     */
                    if (!doc._id) {
                        return setImmediate(callback);
                    }
    
                    /**
                     * If it is a apostrophe-blog, we associate the profile
                     */
                    if (doc.type === 'apostrophe-blog') {
                        return self.addProfileToArticle(req, doc, options, callback);
                    }
                    return setImmediate(callback);
                }
    
                /**
                 * Method for creating user profile.
                 * 
                 * @param {any} req Request.
                 * @param {any} user User having profile added.
                 * @param {any} options Options from hook.
                 * @param {any} callback
                 */
                self.addUserProfile = function(req, user, options, callback) {
    
                    /**
                     * Our profile entity.
                     */
                    const profile = {
                        description: '',
                        published: true,
                        userId: user._id,
                        title: user.title,
                        slug: user.slug.replace(/^(user\-)?/, 'profile-'),
                        thumbnail: user.thumbnail
                    }
    
                    /**
                     * Insert async.
                     */
                    return async.series({
                        save: function(callback) {
                            return self.insert(req, profile, {}, callback);
                        }
                    });
                }
    
                /**
                 * Method to add userProfileId to article.
                 * 
                 * @param {any} req Request.
                 * @param {any} article Article having profile associated.
                 * @param {any} options Options from hook.
                 * @param {any} callback
                 */
                self.addProfileToArticle = async function(req, article, options, callback) {
    
                    /**
                     * Currently logged in user.
                     */
                    const user = req.user;
    
                    /**
                     * Extra check.
                     */
                    if (!user) {
                        return setImmediate(callback);
                    }
    
                    /**
                     * This promise should resolve to the
                     * currently logged in user's profile id.
                     */
                    const profileId = await new Promise(resolve => {
    
                        // Get profile of logged in user.
                        self.db.find({ type: self.name, userId: user._id }, async function(err, cursor) {
                            if (err) {
                                resolve();
                            }
    
                            const profile = await cursor.next();
    
                            resolve(profile ? profile._id : undefined);
                        });
                    });
    
                    /**
                     * No profile, no association.
                     */
                    if (!profileId) {
                        return setImmediate(callback);
                    }
    
                    /**
                     * Attach the userProfileId and callback (ApostropheCMS will save the entity).
                     */
                    article.userProfileId = profileId;
    
                    return setImmediate(callback);
                }
    
                /**
                 * Method to add migration that adds profile to already existing users.
                 * 
                 * @param {Function} callback 
                 */
                self.addUserProfileMigration = function(callback) {
    
                    /**
                     * Add migration to DB. The callback function will be called
                     * when running ApostropheCMS's CLI 'migration' command.
                     */
                    self.apos.migrations.add(self.__meta.name + '.addUserProfile', function(callback) {
    
                        /**
                         * The users that need migrating.
                         */
                        let usersToMigrate = [];
    
                        /**
                         * Run 'docs' and 'migrate' functions async.
                         */
                        return async.series([ docs, migrate ], callback);
    
                        /**
                         * Get the users that need migrating.
                         */
                        function docs(callback) {
    
                            /**
                             * Get all profiles.
                             */
                            return self.db.find({ type: self.name }, async function(err, profiles) {
                                if (err) {
                                    return callback(err);
                                }
    
                                let userIds = [], profile;
    
                                /**
                                 * Fill array of userIds from already existing profiles.
                                 */
                                while (profile = await profiles.next()) {
                                    userIds.push(profile.userId);
                                }
    
                                /**
                                 * Get all users not in userIds (users that have no profile).
                                 * These are the usersToMigrate.
                                 */
                                self.db.find({ type: 'apostrophe-user', _id: { $nin: userIds } }, async function(err, users) {
                                    if (err) {
                                        return callback(err);
                                    }
    
                                    while (user = await users.next()) {
                                        usersToMigrate.push(user);
                                    }
    
                                    return callback(null);
                                });
                            })
                        }
    
                        /**
                         * Run migration.
                         * 
                         * @param {Function} callback 
                         */
                        async function migrate(callback) {
    
                            /**
                             * Iterate on usersToMigrate and create a profile for each user.
                             */
                            for (let i = 0; i < usersToMigrate.length; i++) {
                                const user = usersToMigrate[i];
    
                                /**
                                 * Our profile entity.
                                 */
                                const profile = {
                                    _id: self.apos.utils.generateId(),
                                    description: '',
                                    published: true,
                                    userId: user._id,
                                    title: user.title,
                                    type: self.name,
                                    createdAt: user.updatedAt,
                                    slug: user.slug.replace(/^(user\-)?/, 'profile-'),
                                    docPermissions: [],
                                    thumbnail: user.thumbnail,
                                }
    
                                await new Promise(resolve => self.db.insert(profile, resolve));
                            }
    
                            return setImmediate(callback);
                        }
                    }, {
                        safe: true
                    });
    
                    return setImmediate(callback);
    
                }
    
    
                /**
                 * Migration to swap from userId to userProfileId to
                 * already existing apostrophe-blog entities.
                 */
                self.addBlogPageAuthorMigration = function(callback) {
    
                    /**
                     * Add migration to DB. The callback function will be called
                     * when running ApostropheCMS's CLI 'migration' command.
                     */
                    self.apos.migrations.add(self.__meta.name + '.addBlogPageAuthor', function(callback) {
    
                        /**
                         * Mapping of profile id by user id.
                         */
                        const profileMapByUserId = new Map();
    
                        /**
                         * Posts (apostrophe-blog entities) that need migrating.
                         */
                        const postsToMigrate = [];
    
                        /**
                         * Run 'posts', 'profiles' and 'migrate' functions async.
                         */
                        return async.series([ posts, profiles, migrate ], callback);
    
                        /**
                         * Get the posts that need migrating.
                         * 
                         * @param {Function} callback
                         */
                        function posts(callback) {
    
                            /**
                             * Get all posts having an userId set (not yet migrated ones).
                             */
                            return self.db.find({ type: 'apostrophe-blog', userId: { $exists: true }}, async function(err, blogPosts) {
                                if (err) {
                                    return callback(err);
                                }
    
                                let post;
    
                                /**
                                 * Add found posts to postsToMigrate.
                                 */
                                while (post = await blogPosts.next()) {
                                    postsToMigrate.push(post);
                                }
    
                                return callback(null);
                            });
                        }
    
                        /**
                        * Create the profiles mapping by user id.
                        * 
                        * @param {Function} callback
                        */
                        function profiles(callback) {
    
                            /**
                            * As this function is running async, we need to set immediate
                            * callback to not migrate if there are no posts to migrate.
                            */
                            if (!postsToMigrate.length) {
                                setImmediate(callback);
                            }
    
                            /**
                            * Get all profiles.
                            */
                            return self.db.find({ type: self.name }, async function(err, profiles) {
                                if (err) {
                                    return callback(err);
                                }
    
                                let profile;
    
                                /**
                                * Build mapping.
                                */
                                while (profile = await profiles.next()) {
                                    profileMapByUserId.set(profile.userId, profile);
                                }
    
                                return callback(null);
                            });
                        }
    
                        /**
                        * Run migration.
                        * 
                        * @param {Function} callback 
                        */
                        async function migrate(callback) {
    
                            let userId, profile, post;
    
                            for (let i = 0; i < postsToMigrate.length; i++) {
    
                                /**
                                * Get userId of post.
                                */
                                post = postsToMigrate[i];
                                userId = post.userId;
    
                                if (!userId) {
                                    continue;
                                }
    
                                /**
                                * Get profile of user.
                                */
                                profile = profileMapByUserId.get(userId);
    
                                if (!profile) {
                                    continue;
                                }
    
                                /**
                                * Swap from userId to userProfileId.
                                */
                                delete post.userId;
                                post.userProfileId = profile._id;
    
                                /**
                                * Replace the post to the new one having userProfileId.
                                */
                                await new Promise(resolve => self.db.replaceOne({ _id: post._id }, post, resolve));
                            }
    
                            return callback(null);
                        }
                    }, {
                        safe: true
                    });
    
                    return setImmediate(callback);
    
                }
            }
        }
    

    注意:上面的文件包含一个迁移,假设您在 apostrope-blog 实体中已有 userId 字段。同样,如果您没有此字段或还没有任何撇号博客,则不需要此迁移。此外,它假设您有用户的缩略图。

    代码有详细记录,因此您知道需要时在哪里进行更改。

  • 将 _author 字段添加到 apostrope-blog,删除 userId 字段(如果有)(在迁移之前删除是安全的)。
  • /lib/modules/apostrope-blog/index.js

        module.exports = {
    
          addFields: [
            {                           // <-- add this
              name: '_author',          // <-- add this
              label: 'Author',          // <-- add this
              type: 'joinByOne',        // <-- add this
              withType: 'profile',      // <-- add this
              idField: 'userProfileId'  // <-- add this
            }                           // <-- add this
            // ...
          ],
          beforeConstruct: function(self, options) {
            // ...
    
  • 启动/重新启动 ApostropeCMS 进程以添加迁移。
  • 在 CLI 中运行迁移(不记得是否需要运行 ApostropeCMS 来执行此操作)。
  • 从项目的根文件夹:

    Node app.js 撇号-迁移:迁移

  • html 文件进行必要的更改,以显示作者姓名和缩略图(如果适用)。
  • 瞧!您现在应该能够看到作者。

    注意:本教程不会记录您的应用是否需要 100% 时间运行。您最终需要将其关闭/重新启动才能添加迁移。首先在开发环境中运行测试。

    关于javascript - Apostrope CMS 中的文章作者不会向未登录的用户显示,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47837933/

    相关文章:

    javascript - 使用 Multer 上传多个文件? ( Node )

    javascript - Node.Js 找不到模块,module.js :471

    javascript - 如何让事件通过可见的DOM元素

    node.js - ExpressJS + EJS : Avoid "variable is not defined" globally

    javascript - 如何使用 Javascript 获取树叶的所有路径?

    php - 如何通过cakephp记录mysql错误?

    javascript - 将 angularjs 模块导出为 es6 模块

    node.js - 在 Typescript Node+Express 项目中使用模块中的 TypeScript 文件的正确方法?当前抛出错误 : Cannot find module

    java - 关于如何调试 ComboBoxModel 的建议

    java - 调试纯 Java 时获取 OID