academind / yt-graphql-react-event-booking-api

Code for GraphQL + React Event Booking API Series: https://academind.com/learn/node-js/graphql-with-node-react-full-app/
394 stars 302 forks source link

Splitting resolvers into separate files, causes a circular dependency. #17

Open KennethJoris opened 3 years ago

KennethJoris commented 3 years ago

Thanks a lot for the free course.

Splitting up the resolvers index.js from branch/video #6, into folders like user.js and event.js causes a circular dependency, since users requires logic from events, and visa versa.

How do you solve these kind of circular dependency issues when different models and their "Polulators" require one another? (getUserById => getEventsById => getUserById => getEventsById => ...)

file: user.js Warning: Accessing non-existent property 'getEventsById' of module exports inside circular dependency createdEvents: getEventsById(createdEvents) => TypeError: getEventsById is not a function

// @ts-check
const { User } = require('../models');
const { hash } = require('bcryptjs');
const { getEventsById } = require('./event');

module.exports = {
    // Poplulators
    getUserById: async (userID) => {
        try {
            const {
                _doc: { createdEvents, ...rest }
            } = await User.findById(userID);
            return {
                createdEvents: getEventsById(createdEvents),
                ...rest
            };
        } catch (error) {
            throw error;
        }
    },

    // Resolvers
    users: async () => {
        try {
            const users = await User.find();
            return users.map(({ _doc: { password, createdEvents, ...rest } }) => {
                return {
                    password: null, // Security: Hide user password in result
                    createdEvents: getEventsById(createdEvents), // populate the user.createdEvents field with Event data
                    ...rest
                };
            });
        } catch (error) {
            throw error;
        }
    },
    user: async ({ email }) => {
        try {
            const {
                _doc: { password, createdEvents, ...rest }
            } = await User.findOne({ email: email });
            return {
                password: null,
                createdEvents: getEventsById(createdEvents),
                ...rest
            };
        } catch (error) {
            throw error;
        }
    },
    createUser: async ({ userInput: { email, password } }) => {
        try {
            // Check if user already exists
            const foundUser = await User.findOne({ email: email });

            // User with email found
            if (foundUser) {
                throw new Error('User already exists');
            }

            // New user
            // Security: Hash user password
            const hashedPassword = await hash(password, 12);

            // Create a new MongoDB user model with data
            const user = new User({
                email: email,
                password: hashedPassword
            });

            // Save user to MongoDB and return result
            const { email: userEmail, password: userPassword } = await user.save();

            // Security: Hide user password in result
            return { email: userEmail, password: null };
        } catch (error) {
            throw error;
        }
    }
};

file: event.js Warning: Accessing non-existent property 'getUserById' of module exports inside circular dependency creator: getUserById(creator) => TypeError: getUserById is not a function

// @ts-check
const { User, Event } = require('../models');
const { getUserById } = require('./user');

module.exports = {
    // Poplulators
    getEventsById: async (eventIDs) => {
        try {
            const events = await Event.find({ _id: { $in: eventIDs } });
            return events.map(({ _doc: { creator, date, ...rest } }) => {
                return {
                    date: new Date(date).toISOString(),
                    creator: getUserById(creator),
                    ...rest
                };
            });
        } catch (error) {
            throw error;
        }
    },

    // Resolvers
    events: async () => {
        try {
            const events = await Event.find();
            return events.map(({ _doc: { creator, date, ...rest } }) => {
                return {
                    date: new Date(date).toISOString(),
                    creator: getUserById(creator),
                    ...rest
                };
            });
        } catch (error) {
            throw error;
        }
    },
    createEvent: async ({ eventInput: { title, description, price, date } }) => {
        // Create a new MongoDB event model with data
        const event = new Event({
            title: title,
            description: description,
            price: +price,
            date: new Date(date),
            creator: '60ecc8c9b01258b09dc23aa8'
        });

        // eventDate temp store
        let storedEvent;

        // Save event to MongoDB and return result
        // Add event to user.createdEvents
        try {
            const { _doc: eventData } = await event.save();

            // Store even data
            // Reference Event creator to User
            storedEvent = {
                ...eventData,
                date: new Date(eventData.date).toISOString(),
                creator: getUserById(eventData.creator)
            };

            // Find creator
            // To add event to user.createdEvents
            const foundUser = await User.findById(eventData.creator);

            // no User found
            if (!foundUser) {
                throw new Error('No user found');
            }

            // Add event _id to user.createdEVents
            foundUser.createdEvents.push(eventData._id);
            // Save (same as update) user
            await foundUser.save();

            // return the stored event
            return storedEvent;
        } catch (error) {
            throw error;
        }
    }
};