0

I have created the following user schema, including two methods: getSnapshot() getLastTweetId()

user.js

    const mongoose = require('mongoose')
    const getLastTweetId = require('../utilities/getLastTweetId')
    const getFollowers = require('../utilities/getFollowers')
    
    const userSchema = new mongoose.Schema({
        twitterId: {
            type: String,
            required: true
        },
        screenName: {
            type: String    
        },
        snapshots: {
          type: [snapshotSchema],
          default: []
        },
        createdAt: {
            type: Date
        },
    })
    
    userSchema.method('getSnapshot', async function () {
      const { user, snapshot } = await getFollowers({user: this})
      await user.save()
      return snapshot
    })
    
    userSchema.method('getLastTweetId', async function () {
      const tweetId = await getLastTweetId({user: this})
      return tweetId
    })
    
    const User = mongoose.model('User', userSchema)
    
    module.exports = User

When I define a user instance in passport.js, I can call getSnapshot() on user with no problems. (see below)

passport.js

    const passport = require('passport')
    const mongoose = require('mongoose')
    const needle = require('needle')
    const { DateTime } = require('luxon')
    const User = mongoose.model('User')
    
    // Setup Twitter Strategy
    passport.use(new TwitterStrategy({
        consumerKey: process.env.TWITTER_CONSUMER_API_KEY,
        consumerSecret: process.env.TWITTER_CONSUMER_API_SECRET_KEY,
        callbackURL: process.env.CALLBACK_URL,
        proxy: trustProxy
      },
      async (token, tokenSecret, profile, cb) => {
        const twitterId = profile.id
        const screenName = profile.screen_name
    
        const existingUser = await User.findOne({ twitterId })
        if (existingUser) {
          // Track if this is a new login from an existing user
          if (existingUser.screenName !== screenName) {
              existingUser.screenName = screenName
              await existingUser.save()
          }
          // we already have a record with the given profile ID
          cb(undefined, existingUser)
        } else {
          // we don't have a user record with this ID, make a new record
          const user = await new User ({
            twitterId ,
            screenName,
          }).save()
          **user.getSnapshot()**
          cb(undefined, user)
        }
      }
    )

However, when I call getLastTweetId() on a user instance in tweet.js, I receive the following error in my terminal: TypeError: user.getLastTweetId is not a function

Then my app crashes.

tweets.js

    const express = require('express')
    const mongoose = require('mongoose')
    const User = mongoose.model('User')
    const Tweet = mongoose.model('Tweet')
    const { DateTime } = require('luxon')
    const auth = require('../middleware/auth')
    const requestTweets = require('../utilities/requestTweets')
    
    const router = new express.Router()
    
    const getRecentTweets = async (req, res) => {
        const twitterId = req.user.twitterId
        const user = await User.find({twitterId})
    
        *const sinceId = user.getLastTweetId()*
    
        let params = {
          'start_time': `${DateTime.now().plus({ month: -2 }).toISO({ includeOffset: false })}Z`,
          'end_time': `${DateTime.now().toISO({ includeOffset: false })}Z`,
          'max_results': 100,
          'tweet.fields': "created_at,entities"
        }
      
        if (sinceId) {
         params.since_id = sinceId
        }
    
        let options = {
          headers: {
            'Authorization': `Bearer ${process.env.TWITTER_BEARER_TOKEN}`
          }
        }
        const content = await requestTweets(twitterId, params, options)
        const data = content.data
    
        const tweets = data.map((tweet) => (
          new Tweet({
            twitterId,
            tweetId: tweet.id,
            text: tweet.text,
          })
        ))
        tweets.forEach(async (tweet) => await tweet.save())
    }
    
    // Get all tweets of one user either since last retrieved tweet or for specified month
    router.get('/tweets/user/recent', auth, getRecentTweets)
    
    module.exports = router

I would really appreciate some support to figure out what is going on here. Thank you for bearing with me!

My first guess was that the user instance is not created properly in tweets.js, but then I verified via log messages that the user instance is what I expect it to be in both passport.js as well as tweets.js

My second guess was that the problem is that the user instance in the database was created before I added the new method to the schema, but deleting and reinstantiating the entire collection in the db changed nothing.

Next I went about checking if the issue is related to instantiating the schema itself or just importing it and it seems to be the latter, since when I call getLastTweetId in passport.js it also works, when I call getSnapshot() in tweets.js it also fails.

This is where I'm stuck, because as far as I can tell, I am requiring the User model exactly the same way in both files.

Even when I print User.schema.methods in either file, it shows the following:

[0] {
[0] getSnapshot: [AsyncFunction (anonymous)],
[0] getLastTweetId: [AsyncFunction (anonymous)]
[0] }
gibital
  • 1
  • 2
  • It looks like my first guess regarding what was wrong was on point, and I was just sloppy in verifying that I'm instantiating the user correctly. The line `const user = await User.find({twitterId})` was returning an array of users, and I should have called `const user = await User.findOne({twitterId})` instead. I did not detect the bug at first, because logging an array with just one object looks almost exactly the same as just logging user object. Changing that single line fixed it. – gibital Nov 18 '22 at 00:48

1 Answers1

0

It looks like my first guess regarding what was wrong was on point, and I was just sloppy in verifying that I'm instantiating the user correctly.

   const user = await User.find({twitterId})

The above line was returning an array of users.

Instead, I should have called:

   const user = await User.findOne({twitterId})

I did not detect the bug at first, because logging an array that contains only one object looks nearly the same as just logging the object itself, I simply overlooked the square brackets.

Changing that single line fixed it.

gibital
  • 1
  • 2