-1

I'm building Full Stack, Social media app using Sequelize ORM.

sequelize: 6.6.5 
sequelize-cli: 6.2.0

My database is built of tables Users, Posts and ReadPosts - which has two foreign keys - UserId and PostId.

One of the features of the app, is that user can easily see new posts, that have not been read by him/her yet, so every time user reads a post it generates a row in ReadPost table, made of UserId (who read the post) and PostId (which was read by the user).

What I'm trying to do now, is to display all posts that have not been read, so it would be some kind of excluding left join, that would get all existing posts, and exclude those pots from ReadPost with given userId, but I can't figure out how to do it with Sequlize.

ReadPost model:

module.exports = (sequelize) => {
    const readPost = sequelize.define("ReadPost", {})

    readPost.associate = models => {
        readPost.belongsTo(models.User, {
            foreignKey: {
                allowNull: false
            },
            onDelete: "CASCADE",
            foreignKeyConstrains: true
        })
        readPost.belongsTo(models.Post, {
            foreignKey: {
                allowNull: false
            },
            onDelete: "CASCADE",
            foreignKeyConstrains: true
        })
    }
    return readPost
}

I know I could do it virtually and just run findAll() on posts, and not display those that have not been read yet, depending on some javascript flag or simply class, but this is a project for my portfolio so I want to do it properly. Can I have some help please?

@Anatoly

I had to play a bit with your code, as I'm using newer version of sequelize and got something like this:

 exports.showAllUnreadPosts = (req, res, next) => {
    db.Post.findAll({
        where: {
            "ReadPost.id": null,
            userId: res.locals.userId //userId extracted from authorization middleware
        },
        include: [{
            model: db.ReadPost,
            required: false,
            attributes: []
        }]
    }) 

with that it retuns

"error": "Error SequelizeDatabaseError: Unknown column 'Post.ReadPost.id' in 'where clause'"

I tried to understand the line '"ReadPost.id": null', but as far as I understand sql syntax it would be looking for that column in Post table? I don't have such column, relation exists in ReadPost table, where it gathers userIds and postIds, not sure if my implementation is clear

Just in summary - I need to get all existing posts from Post table and compare it with ReadPost table, where postId and userId are stored. So probably I'd have to run findAll on Posts, findAll on ReadPost with current userId, and exclude all those postIds recorded in ReadPost from Post.findAll

Have a look screenshoot of how currently data looks like in those tables: picture of DB tables

So baisically I need Post.findAll() + ReadPost.findAll() where userId: res.locals.userId and return all posts from Post table but do not exist with that ReadPost query.

I hope that makes it more clear.

@Anatoly 11/03/22 Query works now, but returns only posts that have not been read by ANY user (row doesn't exist) and the user is the author of the post.

What I managed to do for now, is get all posts that have been read by the user (code below). I need exact opposite of it (row doesn't exist or it exists, but not with this userId)

So, from all posts that exist, Select those read by user, and exclude those else from all of the posts in the DB

exports.showAllUnreadPosts = (req, res, next) => {
    db.Post.findAll({
        where: {
            '$readposts.UserId$': res.locals.userId // User Id extracted from auth middleware
        },
        include: [{
            model: db.ReadPost,
            required: false,
            attributes: [],
        }]
    }).then(unreadPosts => {
        res.status(200).json(unreadPosts);
    })
        .catch((error) => {
            res.status(500).json({
                error: 'Error ' + error
            })
        })
}

Can you please advise?

Bart S
  • 1
  • 4

2 Answers2

0

You can query all posts that doesn't have a link record in ReadPost by adding a condition Readpost.id is null like this:

const unreadPosts = await Post.findAll({
  where: {
    '$ReadPost.id$': null,
    userId: userId
  },
  include: [{
    model: ReadPost,
    required: false,
    attributes: [],
    where: {
      UserId: userId
    },
  }]
})

And of course, you need to add an association from Post to ReadPost:

Post.hasMany(ReadPost, <some_options_here>);
Anatoly
  • 20,799
  • 3
  • 28
  • 42
  • Hey, thanks for the answer, but I'd also need to filter somehow by userId - as each user would have different posts marked as read. E.g. Posts of ID 1,2,3,4,5 would be in ReadPosts table, but user of Id 3 would only read PostId: 3, 4, so I'd like to display all other ones - 1, 2, 5 for this individual user relation Post.hasMany, and User.hasMany already exists. Thank you, relations are working fine, is just now idea of building Where query – Bart S Feb 18 '22 at 16:58
  • I added userId in conditions, please look at it – Anatoly Feb 18 '22 at 17:04
  • Hey I edited main post as had long response, thanks – Bart S Feb 18 '22 at 18:11
  • My bad, I fixed the `where` condition, forgot wrapping `$` – Anatoly Feb 18 '22 at 22:38
  • After wrapping in $ It still throwing an error ```"error": "Error! SequelizeDatabaseError: Unknown column 'ReadPost.id' in 'where clause'"``` there is no ReadPost.id column in the Post model sequelize: 6.6.5 sequelize-cli: 6.2.0 – Bart S Feb 19 '22 at 07:13
  • Look at SQL to find out the real alias for `ReadPost` table and use it there – Anatoly Feb 19 '22 at 09:32
  • Hey, sorry I late response, but I was away for long time and couldn't work. It works, but still not solving the problem, I commented the main post – Bart S Mar 11 '22 at 09:29
  • You need to wrap `ReadPost.id` into `$` characters so Sequelize can figure out that it's a reference to included model's field – Anatoly Mar 11 '22 at 11:08
  • I have it wrapped, there is no problem with this anymore, just the matter of what is being returned. $readpost.id$ was returning any posts that don't exist in this table, but there will be a matter of existing rows, when post is being read by other users. I need to prevent this – Bart S Mar 11 '22 at 11:46
  • I added a condition in `include` option, try it – Anatoly Mar 11 '22 at 13:42
0

Right, it seems like I found solution with a great help from @Anatoly.

I'm not sure, if it's a good idea, since I added a second method in the THEN block, I'm happy to get any feedback on it.

exports.showAllUnreadPosts = (req, res, next) => {
    db.Post.findAll({
        where: {
            '$readposts.UserId$': res.locals.userId // User Id extracted from auth middleware
        },
        attributes: ['id'],
        include: [{
            model: db.ReadPost,
            required: false,
            attributes: [],
        }]
    }).then(readPosts => {
        db.Post.findAll({
            where: {
                id: {
                    [Op.not]: (() => readPosts.map(readPost => readPost.id))()
                }
            }
        })
            .then((unreadPosts) => {
                res.status(200).json(unreadPosts);
            })
            .catch((error) => {
                res.status(500).json({
                    error: 'Error' + error
                })
            })

    })
        .catch((error) => {
            res.status(500).json({
                error: 'Error ' + error
            })
        })
}

First of all, there is a method that checks all the posts that are already read by the user in readpost table and returns post ids. Secondly in the THEN block, it gets all the existing posts in the db, and excludes those with ids from above method (by [OP.not]). I hope it makes sense, not sure about performance.

Bart S
  • 1
  • 4