<commit_before>const {Router} = require('express');
const body = require('body-parser');
const MagicLink = require('@tryghost/magic-link');
const common = require('./common');

const MemberAnalyticsService = require('@tryghost/member-analytics-service');
const MembersAnalyticsIngress = require('@tryghost/members-analytics-ingress');
const PaymentsService = require('@tryghost/members-payments');

const StripeWebhookService = require('./services/stripe-webhook');
const TokenService = require('./services/token');
const GeolocationSerice = require('./services/geolocation');
const MemberBREADService = require('./services/member-bread');
const MemberRepository = require('./repositories/member');
const EventRepository = require('./repositories/event');
const ProductRepository = require('./repositories/product');
const RouterController = require('./controllers/router');
const MemberController = require('./controllers/member');
const WellKnownController = require('./controllers/well-known');
const StripeMigrations = require('./migrations');

module.exports = function MembersAPI({
    tokenConfig: {
        issuer,
        privateKey,
        publicKey
    },
    auth: {
        allowSelfSignup = true,
        getSigninURL,
        tokenProvider
    },
    paymentConfig,
    mail: {
        transporter,
        getText,
        getHTML,
        getSubject
    },
    models: {
        StripeWebhook,
        StripeCustomer,
        StripeCustomerSubscription,
        Member,
        MemberSubscribeEvent,
        MemberLoginEvent,
        MemberPaidSubscriptionEvent,
        MemberPaymentEvent,
        MemberStatusEvent,
        MemberProductEvent,
        MemberEmailChangeEvent,
        MemberAnalyticEvent,
        Offer,
        OfferRedemption,
        StripeProduct,
        StripePrice,
        Product,
        Settings
    },
    stripeAPIService,
    offersAPI,
    logger,
    labsService
}) {
    if (logger) {
        common.logging.setLogger(logger);
    }

    const tokenService = new TokenService({
        privateKey,
        publicKey,
        issuer
    });

    const stripeConfig = paymentConfig && paymentConfig.stripe || {};

    const memberAnalyticsService = MemberAnalyticsService.create(MemberAnalyticEvent);
    memberAnalyticsService.eventHandler.setupSubscribers();

    const stripeMigrations = new StripeMigrations({
        stripeAPIService,
        StripeCustomerSubscription,
        StripeProduct,
        StripePrice,
        Product,
        Settings,
        logger
    });

    const productRepository = new ProductRepository({
        Product,
        StripeProduct,
        StripePrice,
        stripeAPIService
    });

    const memberRepository = new MemberRepository({
        stripeAPIService,
        logger,
        tokenService,
        productRepository,
        Member,
        MemberSubscribeEvent,
        MemberPaidSubscriptionEvent,
        MemberEmailChangeEvent,
        MemberStatusEvent,
        MemberProductEvent,
        OfferRedemption,
        StripeCustomer,
        StripeCustomerSubscription
    });

    const eventRepository = new EventRepository({
        logger,
        MemberSubscribeEvent,
        MemberPaidSubscriptionEvent,
        MemberPaymentEvent,
        MemberStatusEvent,
        MemberLoginEvent
    });

    const memberBREADService = new MemberBREADService({
        offersAPI,
        memberRepository,
        emailService: {
            async sendEmailWithMagicLink({email, requestedType}) {
                return sendEmailWithMagicLink({
                    email,
                    requestedType,
                    options: {
                        forceEmailType: true
                    }
                });
            }
        },
        labsService,
        stripeService: stripeAPIService
    });

    const stripeWebhookService = new StripeWebhookService({
        StripeWebhook,
        stripeAPIService,
        productRepository,
        memberRepository,
        eventRepository,
        /**
         * @param {string} email
         */
        async sendSignupEmail(email) {
            let requestedType = 'signup';
            await sendEmailWithMagicLink({
                email,
                requestedType,
                options: {
                    forceEmailType: true
                },
                tokenData: {}
            });
        }
    });

    const geolocationService = new GeolocationSerice();

    const magicLinkService = new MagicLink({
        transporter,
        tokenProvider,
        getSigninURL,
        getText,
        getHTML,
        getSubject
    });

    const memberController = new MemberController({
        memberRepository,
        StripePrice,
        tokenService,
        sendEmailWithMagicLink,
        allowSelfSignup
    });

    const paymentsService = new PaymentsService({
        Offer,
        offersAPI,
        stripeAPIService
    });

    const routerController = new RouterController({
        offersAPI,
        paymentsService,
        productRepository,
        memberRepository,
        StripePrice,
        allowSelfSignup,
        magicLinkService,
        stripeAPIService,
        tokenService,
        sendEmailWithMagicLink,
        labsService,
        config: {
            checkoutSuccessUrl: stripeConfig.checkoutSuccessUrl,
            checkoutCancelUrl: stripeConfig.checkoutCancelUrl,
            billingSuccessUrl: stripeConfig.billingSuccessUrl,
            billingCancelUrl: stripeConfig.billingCancelUrl
        },
        logging: common.logging
    });

    const wellKnownController = new WellKnownController({
        tokenService,
        logging: common.logging
    });

    async function disconnectStripe() {
        if (stripeConfig && stripeConfig.webhook && stripeConfig.webhook.id) {
            await stripeWebhookService.removeWebhook(stripeConfig.webhook.id);
        }

        await Product.forge().query().update({
            monthly_price_id: null,
            yearly_price_id: null
        });
        await StripePrice.forge().query().del();
        await StripeProduct.forge().query().del();
        await StripeCustomer.forge().query().del();
        await Offer.forge().query().update({
            stripe_coupon_id: null
        });
    }

    const ready = stripeAPIService.configured ? Promise.all([
        stripeMigrations.populateProductsAndPrices().then(() => {
            return stripeMigrations.populateStripePricesFromStripePlansSetting(stripeConfig.plans);
        }).then(() => {
            return stripeMigrations.populateMembersMonthlyPriceIdSettings();
        }).then(() => {
            return stripeMigrations.populateMembersYearlyPriceIdSettings();
        }).then(() => {
            return stripeMigrations.populateDefaultProductMonthlyPriceId();
        }).then(() => {
            return stripeMigrations.populateDefaultProductYearlyPriceId();
        }).then(() => {
            return stripeMigrations.revertPortalPlansSetting();
        }).then(() => {
            return stripeMigrations.removeInvalidSubscriptions();
        }),
        stripeWebhookService.configure({
            webhookSecret: process.env.WEBHOOK_SECRET,
            webhookHandlerUrl: stripeConfig.webhookHandlerUrl,
            webhook: stripeConfig.webhook || {}
        })
    ]) : Promise.resolve();

    async function hasActiveStripeSubscriptions() {
        const firstActiveSubscription = await StripeCustomerSubscription.findOne({
            status: 'active'
        });

        if (firstActiveSubscription) {
            return true;
        }

        const firstTrialingSubscription = await StripeCustomerSubscription.findOne({
            status: 'trialing'
        });

        if (firstTrialingSubscription) {
            return true;
        }

        const firstUnpaidSubscription = await StripeCustomerSubscription.findOne({
            status: 'unpaid'
        });

        if (firstUnpaidSubscription) {
            return true;
        }

        const firstPastDueSubscription = await StripeCustomerSubscription.findOne({
            status: 'past_due'
        });

        if (firstPastDueSubscription) {
            return true;
        }

        return false;
    }

    const users = memberRepository;

    async function sendEmailWithMagicLink({email, requestedType, tokenData, options = {forceEmailType: false}, requestSrc = ''}) {
        let type = requestedType;
        if (!options.forceEmailType) {
            const member = await users.get({email});
            if (member) {
                type = 'signin';
            } else if (type !== 'subscribe') {
                type = 'signup';
            }
        }
        return magicLinkService.sendMagicLink({email, type, requestSrc, tokenData: Object.assign({email}, tokenData)});
    }

    function getMagicLink(email) {
        return magicLinkService.getMagicLink({tokenData: {email}, type: 'signin'});
    }

    async function getMemberDataFromMagicLinkToken(token) {
        const {email, labels = [], name = '', oldEmail} = await magicLinkService.getDataFromToken(token);
        if (!email) {
            return null;
        }

        const member = oldEmail ? await getMemberIdentityData(oldEmail) : await getMemberIdentityData(email);

        if (member) {
            await MemberLoginEvent.add({member_id: member.id});
            if (oldEmail) {
                // user exists but wants to change their email address
                if (oldEmail) {
                    member.email = email;
                }
                await users.update(member, {id: member.id});
                return getMemberIdentityData(email);
            }
            return member;
        }

        const newMember = await users.create({name, email, labels});
        await MemberLoginEvent.add({member_id: newMember.id});
        return getMemberIdentityData(email);
    }

    async function getMemberIdentityData(email) {
        return memberBREADService.read({email});
    }

    async function getMemberIdentityToken(email) {
        const member = await getMemberIdentityData(email);
        if (!member) {
            return null;
        }
        return tokenService.encodeIdentityToken({sub: member.email});
    }

    async function setMemberGeolocationFromIp(email, ip) {
        if (!email || !ip) {
            throw new common.errors.IncorrectUsageError({
                message: 'setMemberGeolocationFromIp() expects email and ip arguments to be present'
            });
        }

        // toJSON() is needed here otherwise users.update() will pick methods off
        // the model object rather than data and fail to edit correctly
        const member = (await users.get({email})).toJSON();

        if (!member) {
            throw new common.errors.NotFoundError({
                message: `Member with email address ${email} does not exist`
            });
        }

        // max request time is 500ms so shouldn't slow requests down too much
        let geolocation = JSON.stringify(await geolocationService.getGeolocationFromIP(ip));
        if (geolocation) {
            member.geolocation = geolocation;
            await users.update(member, {id: member.id});
        }

        return getMemberIdentityData(email);
    }

    const middleware = {
        sendMagicLink: Router().use(
            body.json(),
            (req, res) => routerController.sendMagicLink(req, res)
        ),
        createCheckoutSession: Router().use(
            body.json(),
            (req, res) => routerController.createCheckoutSession(req, res)
        ),
        createCheckoutSetupSession: Router().use(
            body.json(),
            (req, res) => routerController.createCheckoutSetupSession(req, res)
        ),
        createEvents: Router().use(
            body.json(),
            (req, res) => MembersAnalyticsIngress.createEvents(req, res)
        ),
        updateEmailAddress: Router().use(
            body.json(),
            (req, res) => memberController.updateEmailAddress(req, res)
        ),
        updateSubscription: Router({mergeParams: true}).use(
            body.json(),
            (req, res) => memberController.updateSubscription(req, res)
        ),
        handleStripeWebhook: Router(),
        wellKnown: Router()
            .get('/jwks.json',
                (req, res) => wellKnownController.getPublicKeys(req, res)
            )
    };

    middleware.handleStripeWebhook.use(body.raw({type: 'application/json'}), async function (req, res) {
        if (!stripeAPIService.configured) {
            common.logging.error(`Stripe not configured, not handling webhook`);
            res.writeHead(400);
            return res.end();
        }

        if (!req.body || !req.headers['stripe-signature']) {
            res.writeHead(400);
            return res.end();
        }
        let event;
        try {
            event = stripeWebhookService.parseWebhook(req.body, req.headers['stripe-signature']);
        } catch (err) {
            common.logging.error(err);
            res.writeHead(401);
            return res.end();
        }
        common.logging.info(`Handling webhook ${event.type}`);
        try {
            await stripeWebhookService.handleWebhook(event);
            res.writeHead(200);
            res.end();
        } catch (err) {
            common.logging.error(`Error handling webhook ${event.type}`, err);
            res.writeHead(err.statusCode || 500);
            res.end();
        }
    });

    const getPublicConfig = function () {
        return Promise.resolve({
            publicKey,
            issuer
        });
    };

    const bus = new (require('events').EventEmitter)();

    ready.then(() => {
        bus.emit('ready');
    }).catch((err) => {
        bus.emit('error', err);
    });

    return {
        middleware,
        getMemberDataFromMagicLinkToken,
        getMemberIdentityToken,
        getMemberIdentityData,
        setMemberGeolocationFromIp,
        disconnectStripe,
        getPublicConfig,
        bus,
        sendEmailWithMagicLink,
        getMagicLink,
        hasActiveStripeSubscriptions,
        members: users,
        memberBREADService,
        events: eventRepository,
        productRepository
    };
};
<commit_msg>Used the paid signup email when auto login is enabled<commit_after>const {Router} = require('express');
const body = require('body-parser');
const MagicLink = require('@tryghost/magic-link');
const common = require('./common');

const MemberAnalyticsService = require('@tryghost/member-analytics-service');
const MembersAnalyticsIngress = require('@tryghost/members-analytics-ingress');
const PaymentsService = require('@tryghost/members-payments');

const StripeWebhookService = require('./services/stripe-webhook');
const TokenService = require('./services/token');
const GeolocationSerice = require('./services/geolocation');
const MemberBREADService = require('./services/member-bread');
const MemberRepository = require('./repositories/member');
const EventRepository = require('./repositories/event');
const ProductRepository = require('./repositories/product');
const RouterController = require('./controllers/router');
const MemberController = require('./controllers/member');
const WellKnownController = require('./controllers/well-known');
const StripeMigrations = require('./migrations');

module.exports = function MembersAPI({
    tokenConfig: {
        issuer,
        privateKey,
        publicKey
    },
    auth: {
        allowSelfSignup = true,
        getSigninURL,
        tokenProvider
    },
    paymentConfig,
    mail: {
        transporter,
        getText,
        getHTML,
        getSubject
    },
    models: {
        StripeWebhook,
        StripeCustomer,
        StripeCustomerSubscription,
        Member,
        MemberSubscribeEvent,
        MemberLoginEvent,
        MemberPaidSubscriptionEvent,
        MemberPaymentEvent,
        MemberStatusEvent,
        MemberProductEvent,
        MemberEmailChangeEvent,
        MemberAnalyticEvent,
        Offer,
        OfferRedemption,
        StripeProduct,
        StripePrice,
        Product,
        Settings
    },
    stripeAPIService,
    offersAPI,
    logger,
    labsService
}) {
    if (logger) {
        common.logging.setLogger(logger);
    }

    const tokenService = new TokenService({
        privateKey,
        publicKey,
        issuer
    });

    const stripeConfig = paymentConfig && paymentConfig.stripe || {};

    const memberAnalyticsService = MemberAnalyticsService.create(MemberAnalyticEvent);
    memberAnalyticsService.eventHandler.setupSubscribers();

    const stripeMigrations = new StripeMigrations({
        stripeAPIService,
        StripeCustomerSubscription,
        StripeProduct,
        StripePrice,
        Product,
        Settings,
        logger
    });

    const productRepository = new ProductRepository({
        Product,
        StripeProduct,
        StripePrice,
        stripeAPIService
    });

    const memberRepository = new MemberRepository({
        stripeAPIService,
        logger,
        tokenService,
        productRepository,
        Member,
        MemberSubscribeEvent,
        MemberPaidSubscriptionEvent,
        MemberEmailChangeEvent,
        MemberStatusEvent,
        MemberProductEvent,
        OfferRedemption,
        StripeCustomer,
        StripeCustomerSubscription
    });

    const eventRepository = new EventRepository({
        logger,
        MemberSubscribeEvent,
        MemberPaidSubscriptionEvent,
        MemberPaymentEvent,
        MemberStatusEvent,
        MemberLoginEvent
    });

    const memberBREADService = new MemberBREADService({
        offersAPI,
        memberRepository,
        emailService: {
            async sendEmailWithMagicLink({email, requestedType}) {
                return sendEmailWithMagicLink({
                    email,
                    requestedType,
                    options: {
                        forceEmailType: true
                    }
                });
            }
        },
        labsService,
        stripeService: stripeAPIService
    });

    const stripeWebhookService = new StripeWebhookService({
        StripeWebhook,
        stripeAPIService,
        productRepository,
        memberRepository,
        eventRepository,
        /**
         * @param {string} email
         */
        async sendSignupEmail(email) {
            let requestedType = 'signup';
            if (labsService.isSet('membersAutoLogin')) {
                requestedType = 'signup-paid';
            }
            await sendEmailWithMagicLink({
                email,
                requestedType,
                options: {
                    forceEmailType: true
                },
                tokenData: {}
            });
        }
    });

    const geolocationService = new GeolocationSerice();

    const magicLinkService = new MagicLink({
        transporter,
        tokenProvider,
        getSigninURL,
        getText,
        getHTML,
        getSubject
    });

    const memberController = new MemberController({
        memberRepository,
        StripePrice,
        tokenService,
        sendEmailWithMagicLink,
        allowSelfSignup
    });

    const paymentsService = new PaymentsService({
        Offer,
        offersAPI,
        stripeAPIService
    });

    const routerController = new RouterController({
        offersAPI,
        paymentsService,
        productRepository,
        memberRepository,
        StripePrice,
        allowSelfSignup,
        magicLinkService,
        stripeAPIService,
        tokenService,
        sendEmailWithMagicLink,
        labsService,
        config: {
            checkoutSuccessUrl: stripeConfig.checkoutSuccessUrl,
            checkoutCancelUrl: stripeConfig.checkoutCancelUrl,
            billingSuccessUrl: stripeConfig.billingSuccessUrl,
            billingCancelUrl: stripeConfig.billingCancelUrl
        },
        logging: common.logging
    });

    const wellKnownController = new WellKnownController({
        tokenService,
        logging: common.logging
    });

    async function disconnectStripe() {
        if (stripeConfig && stripeConfig.webhook && stripeConfig.webhook.id) {
            await stripeWebhookService.removeWebhook(stripeConfig.webhook.id);
        }

        await Product.forge().query().update({
            monthly_price_id: null,
            yearly_price_id: null
        });
        await StripePrice.forge().query().del();
        await StripeProduct.forge().query().del();
        await StripeCustomer.forge().query().del();
        await Offer.forge().query().update({
            stripe_coupon_id: null
        });
    }

    const ready = stripeAPIService.configured ? Promise.all([
        stripeMigrations.populateProductsAndPrices().then(() => {
            return stripeMigrations.populateStripePricesFromStripePlansSetting(stripeConfig.plans);
        }).then(() => {
            return stripeMigrations.populateMembersMonthlyPriceIdSettings();
        }).then(() => {
            return stripeMigrations.populateMembersYearlyPriceIdSettings();
        }).then(() => {
            return stripeMigrations.populateDefaultProductMonthlyPriceId();
        }).then(() => {
            return stripeMigrations.populateDefaultProductYearlyPriceId();
        }).then(() => {
            return stripeMigrations.revertPortalPlansSetting();
        }).then(() => {
            return stripeMigrations.removeInvalidSubscriptions();
        }),
        stripeWebhookService.configure({
            webhookSecret: process.env.WEBHOOK_SECRET,
            webhookHandlerUrl: stripeConfig.webhookHandlerUrl,
            webhook: stripeConfig.webhook || {}
        })
    ]) : Promise.resolve();

    async function hasActiveStripeSubscriptions() {
        const firstActiveSubscription = await StripeCustomerSubscription.findOne({
            status: 'active'
        });

        if (firstActiveSubscription) {
            return true;
        }

        const firstTrialingSubscription = await StripeCustomerSubscription.findOne({
            status: 'trialing'
        });

        if (firstTrialingSubscription) {
            return true;
        }

        const firstUnpaidSubscription = await StripeCustomerSubscription.findOne({
            status: 'unpaid'
        });

        if (firstUnpaidSubscription) {
            return true;
        }

        const firstPastDueSubscription = await StripeCustomerSubscription.findOne({
            status: 'past_due'
        });

        if (firstPastDueSubscription) {
            return true;
        }

        return false;
    }

    const users = memberRepository;

    async function sendEmailWithMagicLink({email, requestedType, tokenData, options = {forceEmailType: false}, requestSrc = ''}) {
        let type = requestedType;
        if (!options.forceEmailType) {
            const member = await users.get({email});
            if (member) {
                type = 'signin';
            } else if (type !== 'subscribe') {
                type = 'signup';
            }
        }
        return magicLinkService.sendMagicLink({email, type, requestSrc, tokenData: Object.assign({email}, tokenData)});
    }

    function getMagicLink(email) {
        return magicLinkService.getMagicLink({tokenData: {email}, type: 'signin'});
    }

    async function getMemberDataFromMagicLinkToken(token) {
        const {email, labels = [], name = '', oldEmail} = await magicLinkService.getDataFromToken(token);
        if (!email) {
            return null;
        }

        const member = oldEmail ? await getMemberIdentityData(oldEmail) : await getMemberIdentityData(email);

        if (member) {
            await MemberLoginEvent.add({member_id: member.id});
            if (oldEmail) {
                // user exists but wants to change their email address
                if (oldEmail) {
                    member.email = email;
                }
                await users.update(member, {id: member.id});
                return getMemberIdentityData(email);
            }
            return member;
        }

        const newMember = await users.create({name, email, labels});
        await MemberLoginEvent.add({member_id: newMember.id});
        return getMemberIdentityData(email);
    }

    async function getMemberIdentityData(email) {
        return memberBREADService.read({email});
    }

    async function getMemberIdentityToken(email) {
        const member = await getMemberIdentityData(email);
        if (!member) {
            return null;
        }
        return tokenService.encodeIdentityToken({sub: member.email});
    }

    async function setMemberGeolocationFromIp(email, ip) {
        if (!email || !ip) {
            throw new common.errors.IncorrectUsageError({
                message: 'setMemberGeolocationFromIp() expects email and ip arguments to be present'
            });
        }

        // toJSON() is needed here otherwise users.update() will pick methods off
        // the model object rather than data and fail to edit correctly
        const member = (await users.get({email})).toJSON();

        if (!member) {
            throw new common.errors.NotFoundError({
                message: `Member with email address ${email} does not exist`
            });
        }

        // max request time is 500ms so shouldn't slow requests down too much
        let geolocation = JSON.stringify(await geolocationService.getGeolocationFromIP(ip));
        if (geolocation) {
            member.geolocation = geolocation;
            await users.update(member, {id: member.id});
        }

        return getMemberIdentityData(email);
    }

    const middleware = {
        sendMagicLink: Router().use(
            body.json(),
            (req, res) => routerController.sendMagicLink(req, res)
        ),
        createCheckoutSession: Router().use(
            body.json(),
            (req, res) => routerController.createCheckoutSession(req, res)
        ),
        createCheckoutSetupSession: Router().use(
            body.json(),
            (req, res) => routerController.createCheckoutSetupSession(req, res)
        ),
        createEvents: Router().use(
            body.json(),
            (req, res) => MembersAnalyticsIngress.createEvents(req, res)
        ),
        updateEmailAddress: Router().use(
            body.json(),
            (req, res) => memberController.updateEmailAddress(req, res)
        ),
        updateSubscription: Router({mergeParams: true}).use(
            body.json(),
            (req, res) => memberController.updateSubscription(req, res)
        ),
        handleStripeWebhook: Router(),
        wellKnown: Router()
            .get('/jwks.json',
                (req, res) => wellKnownController.getPublicKeys(req, res)
            )
    };

    middleware.handleStripeWebhook.use(body.raw({type: 'application/json'}), async function (req, res) {
        if (!stripeAPIService.configured) {
            common.logging.error(`Stripe not configured, not handling webhook`);
            res.writeHead(400);
            return res.end();
        }

        if (!req.body || !req.headers['stripe-signature']) {
            res.writeHead(400);
            return res.end();
        }
        let event;
        try {
            event = stripeWebhookService.parseWebhook(req.body, req.headers['stripe-signature']);
        } catch (err) {
            common.logging.error(err);
            res.writeHead(401);
            return res.end();
        }
        common.logging.info(`Handling webhook ${event.type}`);
        try {
            await stripeWebhookService.handleWebhook(event);
            res.writeHead(200);
            res.end();
        } catch (err) {
            common.logging.error(`Error handling webhook ${event.type}`, err);
            res.writeHead(err.statusCode || 500);
            res.end();
        }
    });

    const getPublicConfig = function () {
        return Promise.resolve({
            publicKey,
            issuer
        });
    };

    const bus = new (require('events').EventEmitter)();

    ready.then(() => {
        bus.emit('ready');
    }).catch((err) => {
        bus.emit('error', err);
    });

    return {
        middleware,
        getMemberDataFromMagicLinkToken,
        getMemberIdentityToken,
        getMemberIdentityData,
        setMemberGeolocationFromIp,
        disconnectStripe,
        getPublicConfig,
        bus,
        sendEmailWithMagicLink,
        getMagicLink,
        hasActiveStripeSubscriptions,
        members: users,
        memberBREADService,
        events: eventRepository,
        productRepository
    };
};
<|endoftext|>


<commit_before>import * as util from '../util';

let rendererDefaults = util.defaults({
  motionBlur: false,
  motionBlurOpacity: 0.05,
  pixelRatio: undefined,
  desktopTapThreshold: 4,
  touchTapThreshold: 8,
  wheelSensitivity: 1
});

let corefn = ({

  renderTo: function( context, zoom, pan, pxRatio ){
    let r = this._private.renderer;

    r.renderTo( context, zoom, pan, pxRatio );
    return this;
  },

  renderer: function(){
    return this._private.renderer;
  },

  forceRender: function(){
    this.notify('draw');

    return this;
  },

  resize: function(){
    this.invalidateSize();

    this.emitAndNotify('resize');

    return this;
  },

  initRenderer: function( options ){
    let cy = this;

    let RendererProto = cy.extension( 'renderer', options.name );
    if( RendererProto == null ){
      util.error( `Can not initialise: No such renderer \`${options.name}\` found. Did you forget to import it and \`cytoscape.use()\` it?` );
      return;
    }

    if( options.wheelSensitivity !== undefined ){
      util.warn(`You have set a custom wheel sensitivity.  This will make your app zoom unnaturally when using mainstream mice.  You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine.`);
    }

    let rOpts = rendererDefaults(options);

    rOpts.cy = cy;

    cy._private.renderer = new RendererProto( rOpts );

    this.notify('init');
  },

  destroyRenderer: function(){
    let cy = this;

    cy.notify('destroy'); // destroy the renderer

    let domEle = cy.container();
    if( domEle ){
      domEle._cyreg = null;

      while( domEle.childNodes.length > 0 ){
        domEle.removeChild( domEle.childNodes[0] );
      }
    }

    cy._private.renderer = null; // to be extra safe, remove the ref
    cy.mutableElements().forEach(function( ele ){
      let _p = ele._private;
      _p.rscratch = {};
      _p.rstyle = {};
      _p.animation.current = [];
      _p.animation.queue = [];
    });
  },

  onRender: function( fn ){
    return this.on('render', fn);
  },

  offRender: function( fn ){
    return this.off('render', fn);
  }

});

corefn.invalidateDimensions = corefn.resize;

export default corefn;
<commit_msg>Add `hideEdgesOnViewport` and `textureOnViewport` to the list of options passed to the renderer.<commit_after>import * as util from '../util';

let rendererDefaults = util.defaults({
  hideEdgesOnViewport: false,
  textureOnViewport: false,
  motionBlur: false,
  motionBlurOpacity: 0.05,
  pixelRatio: undefined,
  desktopTapThreshold: 4,
  touchTapThreshold: 8,
  wheelSensitivity: 1
});

let corefn = ({

  renderTo: function( context, zoom, pan, pxRatio ){
    let r = this._private.renderer;

    r.renderTo( context, zoom, pan, pxRatio );
    return this;
  },

  renderer: function(){
    return this._private.renderer;
  },

  forceRender: function(){
    this.notify('draw');

    return this;
  },

  resize: function(){
    this.invalidateSize();

    this.emitAndNotify('resize');

    return this;
  },

  initRenderer: function( options ){
    let cy = this;

    let RendererProto = cy.extension( 'renderer', options.name );
    if( RendererProto == null ){
      util.error( `Can not initialise: No such renderer \`${options.name}\` found. Did you forget to import it and \`cytoscape.use()\` it?` );
      return;
    }

    if( options.wheelSensitivity !== undefined ){
      util.warn(`You have set a custom wheel sensitivity.  This will make your app zoom unnaturally when using mainstream mice.  You should change this value from the default only if you can guarantee that all your users will use the same hardware and OS configuration as your current machine.`);
    }

    let rOpts = rendererDefaults(options);

    rOpts.cy = cy;

    cy._private.renderer = new RendererProto( rOpts );

    this.notify('init');
  },

  destroyRenderer: function(){
    let cy = this;

    cy.notify('destroy'); // destroy the renderer

    let domEle = cy.container();
    if( domEle ){
      domEle._cyreg = null;

      while( domEle.childNodes.length > 0 ){
        domEle.removeChild( domEle.childNodes[0] );
      }
    }

    cy._private.renderer = null; // to be extra safe, remove the ref
    cy.mutableElements().forEach(function( ele ){
      let _p = ele._private;
      _p.rscratch = {};
      _p.rstyle = {};
      _p.animation.current = [];
      _p.animation.queue = [];
    });
  },

  onRender: function( fn ){
    return this.on('render', fn);
  },

  offRender: function( fn ){
    return this.off('render', fn);
  }

});

corefn.invalidateDimensions = corefn.resize;

export default corefn;
<|endoftext|>


<commit_before>/**
 * Taken, CommonJS-ified, and heavily modified from:
 * https://github.com/flyingsparx/NodeDirectUploader
 */

S3Upload.prototype.signingUrl = '/sign-s3';
S3Upload.prototype.fileElement = null;

S3Upload.prototype.onFinishS3Put = function(signResult) {
    return console.log('base.onFinishS3Put()', signResult.publicUrl);
};

S3Upload.prototype.onProgress = function(percent, status) {
    return console.log('base.onProgress()', percent, status);
};

S3Upload.prototype.onError = function(status) {
    return console.log('base.onError()', status);
};

function S3Upload(options) {
    if (options == null) {
        options = {};
    }
    for (option in options) {
        if (options.hasOwnProperty(option)) {
            this[option] = options[option];
        }
    }
    this.handleFileSelect(this.fileElement);
}

S3Upload.prototype.handleFileSelect = function(fileElement) {
    this.onProgress(0, 'Upload started.');
    var files = fileElement.files;
    var result = [];
    for (var i=0; i < files.length; i++) {
        var f = files[i];
        result.push(this.uploadFile(f));
    }
    return result;
};

S3Upload.prototype.createCORSRequest = function(method, url) {
    var xhr = new XMLHttpRequest();

    if (xhr.withCredentials != null) {
        xhr.open(method, url, true);
    }
    else if (typeof XDomainRequest !== "undefined") {
        xhr = new XDomainRequest();
        xhr.open(method, url);
    }
    else {
        xhr = null;
    }
    return xhr;
};

S3Upload.prototype.executeOnSignedUrl = function(file, callback) {
    var xhr = new XMLHttpRequest();
    var fileName = file.name.replace(/\s+/g, "_");
    xhr.open('GET', this.signingUrl + '?objectName=' + fileName, true);
    xhr.overrideMimeType && xhr.overrideMimeType('text/plain; charset=x-user-defined');
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
            var result;
            try {
                result = JSON.parse(xhr.responseText);
            } catch (error) {
                this.onError('Invalid signing server response JSON: ' + xhr.responseText);
                return false;
            }
            return callback(result);
        } else if (xhr.readyState === 4 && xhr.status !== 200) {
            return this.onError('Could not contact request signing server. Status = ' + xhr.status);
        }
    }.bind(this);
    return xhr.send();
};

S3Upload.prototype.uploadToS3 = function(file, signResult) {
    var xhr = this.createCORSRequest('PUT', signResult.signedUrl);
    if (!xhr) {
        this.onError('CORS not supported');
    } else {
        xhr.onload = function() {
            if (xhr.status === 200) {
                this.onProgress(100, 'Upload completed.');
                return this.onFinishS3Put(signResult);
            } else {
                return this.onError('Upload error: ' + xhr.status);
            }
        }.bind(this);
        xhr.onerror = function() {
            return this.onError('XHR error.');
        }.bind(this);
        xhr.upload.onprogress = function(e) {
            var percentLoaded;
            if (e.lengthComputable) {
                percentLoaded = Math.round((e.loaded / e.total) * 100);
                return this.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing.' : 'Uploading.');
            }
        }.bind(this);
    }
    xhr.setRequestHeader('Content-Type', file.type);
    xhr.setRequestHeader('x-amz-acl', 'public-read');
    return xhr.send(file);
};

S3Upload.prototype.uploadFile = function(file) {
    return this.executeOnSignedUrl(file, function(signResult) {
        return this.uploadToS3(file, signResult);
    }.bind(this));
};

module.exports = S3Upload;
<commit_msg>Pass mime type with get signed url. Added option to use access control list (acl) from signResult.<commit_after>/**
 * Taken, CommonJS-ified, and heavily modified from:
 * https://github.com/flyingsparx/NodeDirectUploader
 */

S3Upload.prototype.signingUrl = '/sign-s3';
S3Upload.prototype.fileElement = null;

S3Upload.prototype.onFinishS3Put = function(signResult) {
    return console.log('base.onFinishS3Put()', signResult.publicUrl);
};

S3Upload.prototype.onProgress = function(percent, status) {
    return console.log('base.onProgress()', percent, status);
};

S3Upload.prototype.onError = function(status) {
    return console.log('base.onError()', status);
};

function S3Upload(options) {
    if (options == null) {
        options = {};
    }
    for (option in options) {
        if (options.hasOwnProperty(option)) {
            this[option] = options[option];
        }
    }
    this.handleFileSelect(this.fileElement);
}

S3Upload.prototype.handleFileSelect = function(fileElement) {
    this.onProgress(0, 'Upload started.');
    var files = fileElement.files;
    var result = [];
    for (var i=0; i < files.length; i++) {
        var f = files[i];
        result.push(this.uploadFile(f));
    }
    return result;
};

S3Upload.prototype.createCORSRequest = function(method, url) {
    var xhr = new XMLHttpRequest();

    if (xhr.withCredentials != null) {
        xhr.open(method, url, true);
    }
    else if (typeof XDomainRequest !== "undefined") {
        xhr = new XDomainRequest();
        xhr.open(method, url);
    }
    else {
        xhr = null;
    }
    return xhr;
};

S3Upload.prototype.executeOnSignedUrl = function(file, callback) {
    var xhr = new XMLHttpRequest();
    var fileName = file.name.replace(/\s+/g, "_");
    xhr.open('GET', this.signingUrl + '?objectName=' + fileName + '&mimeType=' + this.mimeType(fileName);, true);
    xhr.overrideMimeType && xhr.overrideMimeType('text/plain; charset=x-user-defined');
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
            var result;
            try {
                result = JSON.parse(xhr.responseText);
            } catch (error) {
                this.onError('Invalid signing server response JSON: ' + xhr.responseText);
                return false;
            }
            return callback(result);
        } else if (xhr.readyState === 4 && xhr.status !== 200) {
            return this.onError('Could not contact request signing server. Status = ' + xhr.status);
        }
    }.bind(this);
    return xhr.send();
};

S3Upload.prototype.uploadToS3 = function(file, signResult) {
    var xhr = this.createCORSRequest('PUT', signResult.signedUrl);
    if (!xhr) {
        this.onError('CORS not supported');
    } else {
        xhr.onload = function() {
            if (xhr.status === 200) {
                this.onProgress(100, 'Upload completed.');
                return this.onFinishS3Put(signResult);
            } else {
                return this.onError('Upload error: ' + xhr.status);
            }
        }.bind(this);
        xhr.onerror = function() {
            return this.onError('XHR error.');
        }.bind(this);
        xhr.upload.onprogress = function(e) {
            var percentLoaded;
            if (e.lengthComputable) {
                percentLoaded = Math.round((e.loaded / e.total) * 100);
                return this.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing.' : 'Uploading.');
            }
        }.bind(this);
    }
    var mimeType = signResult.mimeType || this.getMimeType(file.name);
    xhr.setRequestHeader('Content-Type', mimeType);

    var acl = signResult.acl || 'public-read';
    xhr.setRequestHeader('x-amz-acl', acl);

    return xhr.send(file);
};

S3Upload.prototype.uploadFile = function(file) {
    return this.executeOnSignedUrl(file, function(signResult) {
        return this.uploadToS3(file, signResult);
    }.bind(this));
};

module.exports = S3Upload;
<|endoftext|>


<commit_before>const {Router} = require('express');
const body = require('body-parser');
const MagicLink = require('@tryghost/magic-link');
const errors = require('@tryghost/errors');
const logging = require('@tryghost/logging');

const MemberAnalyticsService = require('@tryghost/member-analytics-service');
const MembersAnalyticsIngress = require('@tryghost/members-analytics-ingress');
const PaymentsService = require('@tryghost/members-payments');

const StripeWebhookService = require('./services/stripe-webhook');
const TokenService = require('./services/token');
const GeolocationSerice = require('./services/geolocation');
const MemberBREADService = require('./services/member-bread');
const MemberRepository = require('./repositories/member');
const EventRepository = require('./repositories/event');
const ProductRepository = require('./repositories/product');
const RouterController = require('./controllers/router');
const MemberController = require('./controllers/member');
const WellKnownController = require('./controllers/well-known');
const StripeMigrations = require('./migrations');

module.exports = function MembersAPI({
    tokenConfig: {
        issuer,
        privateKey,
        publicKey
    },
    auth: {
        allowSelfSignup = () => true,
        getSigninURL,
        tokenProvider
    },
    paymentConfig,
    mail: {
        transporter,
        getText,
        getHTML,
        getSubject
    },
    models: {
        StripeWebhook,
        StripeCustomer,
        StripeCustomerSubscription,
        Member,
        MemberSubscribeEvent,
        MemberLoginEvent,
        MemberPaidSubscriptionEvent,
        MemberPaymentEvent,
        MemberStatusEvent,
        MemberProductEvent,
        MemberEmailChangeEvent,
        MemberAnalyticEvent,
        Offer,
        OfferRedemption,
        StripeProduct,
        StripePrice,
        Product,
        Settings
    },
    stripeAPIService,
    offersAPI,
    labsService
}) {
    const tokenService = new TokenService({
        privateKey,
        publicKey,
        issuer
    });

    const stripeConfig = paymentConfig && paymentConfig.stripe || {};

    const memberAnalyticsService = MemberAnalyticsService.create(MemberAnalyticEvent);
    memberAnalyticsService.eventHandler.setupSubscribers();

    const stripeMigrations = new StripeMigrations({
        stripeAPIService,
        StripeCustomerSubscription,
        StripeProduct,
        StripePrice,
        Product,
        Settings
    });

    const productRepository = new ProductRepository({
        Product,
        StripeProduct,
        StripePrice,
        stripeAPIService
    });

    const memberRepository = new MemberRepository({
        stripeAPIService,
        tokenService,
        productRepository,
        Member,
        MemberSubscribeEvent,
        MemberPaidSubscriptionEvent,
        MemberEmailChangeEvent,
        MemberStatusEvent,
        MemberProductEvent,
        OfferRedemption,
        StripeCustomer,
        StripeCustomerSubscription
    });

    const eventRepository = new EventRepository({
        MemberSubscribeEvent,
        MemberPaidSubscriptionEvent,
        MemberPaymentEvent,
        MemberStatusEvent,
        MemberLoginEvent
    });

    const memberBREADService = new MemberBREADService({
        offersAPI,
        memberRepository,
        emailService: {
            async sendEmailWithMagicLink({email, requestedType}) {
                return sendEmailWithMagicLink({
                    email,
                    requestedType,
                    options: {
                        forceEmailType: true
                    }
                });
            }
        },
        labsService,
        stripeService: stripeAPIService
    });

    const stripeWebhookService = new StripeWebhookService({
        StripeWebhook,
        stripeAPIService,
        productRepository,
        memberRepository,
        eventRepository,
        /**
         * @param {string} email
         */
        async sendSignupEmail(email) {
            const requestedType = 'signup-paid';
            await sendEmailWithMagicLink({
                email,
                requestedType,
                options: {
                    forceEmailType: true
                },
                tokenData: {}
            });
        }
    });

    const geolocationService = new GeolocationSerice();

    const magicLinkService = new MagicLink({
        transporter,
        tokenProvider,
        getSigninURL,
        getText,
        getHTML,
        getSubject
    });

    const memberController = new MemberController({
        memberRepository,
        StripePrice,
        tokenService,
        sendEmailWithMagicLink
    });

    const paymentsService = new PaymentsService({
        Offer,
        offersAPI,
        stripeAPIService
    });

    const routerController = new RouterController({
        offersAPI,
        paymentsService,
        productRepository,
        memberRepository,
        StripePrice,
        allowSelfSignup,
        magicLinkService,
        stripeAPIService,
        tokenService,
        sendEmailWithMagicLink,
        labsService,
        config: {
            checkoutSuccessUrl: stripeConfig.checkoutSuccessUrl,
            checkoutCancelUrl: stripeConfig.checkoutCancelUrl,
            billingSuccessUrl: stripeConfig.billingSuccessUrl,
            billingCancelUrl: stripeConfig.billingCancelUrl
        }
    });

    const wellKnownController = new WellKnownController({
        tokenService
    });

    async function disconnectStripe() {
        if (stripeConfig && stripeConfig.webhook && stripeConfig.webhook.id) {
            await stripeWebhookService.removeWebhook(stripeConfig.webhook.id);
        }

        await Product.forge().query().update({
            monthly_price_id: null,
            yearly_price_id: null
        });
        await StripePrice.forge().query().del();
        await StripeProduct.forge().query().del();
        await StripeCustomer.forge().query().del();
        await Offer.forge().query().update({
            stripe_coupon_id: null
        });
    }

    const ready = stripeAPIService.configured ? Promise.all([
        stripeMigrations.populateProductsAndPrices().then(() => {
            return stripeMigrations.populateStripePricesFromStripePlansSetting(stripeConfig.plans);
        }).then(() => {
            return stripeMigrations.populateMembersMonthlyPriceIdSettings();
        }).then(() => {
            return stripeMigrations.populateMembersYearlyPriceIdSettings();
        }).then(() => {
            return stripeMigrations.populateDefaultProductMonthlyPriceId();
        }).then(() => {
            return stripeMigrations.populateDefaultProductYearlyPriceId();
        }).then(() => {
            return stripeMigrations.revertPortalPlansSetting();
        }).then(() => {
            return stripeMigrations.removeInvalidSubscriptions();
        }),
        stripeWebhookService.configure({
            webhookSecret: process.env.WEBHOOK_SECRET,
            webhookHandlerUrl: stripeConfig.webhookHandlerUrl,
            webhook: stripeConfig.webhook || {}
        })
    ]) : Promise.resolve();

    async function hasActiveStripeSubscriptions() {
        const firstActiveSubscription = await StripeCustomerSubscription.findOne({
            status: 'active'
        });

        if (firstActiveSubscription) {
            return true;
        }

        const firstTrialingSubscription = await StripeCustomerSubscription.findOne({
            status: 'trialing'
        });

        if (firstTrialingSubscription) {
            return true;
        }

        const firstUnpaidSubscription = await StripeCustomerSubscription.findOne({
            status: 'unpaid'
        });

        if (firstUnpaidSubscription) {
            return true;
        }

        const firstPastDueSubscription = await StripeCustomerSubscription.findOne({
            status: 'past_due'
        });

        if (firstPastDueSubscription) {
            return true;
        }

        return false;
    }

    const users = memberRepository;

    async function sendEmailWithMagicLink({email, requestedType, tokenData, options = {forceEmailType: false}, requestSrc = ''}) {
        let type = requestedType;
        if (!options.forceEmailType) {
            const member = await users.get({email});
            if (member) {
                type = 'signin';
            } else if (type !== 'subscribe') {
                type = 'signup';
            }
        }
        return magicLinkService.sendMagicLink({email, type, requestSrc, tokenData: Object.assign({email}, tokenData)});
    }

    function getMagicLink(email) {
        return magicLinkService.getMagicLink({tokenData: {email}, type: 'signin'});
    }

    async function getMemberDataFromMagicLinkToken(token) {
        const {email, labels = [], name = '', oldEmail} = await magicLinkService.getDataFromToken(token);
        if (!email) {
            return null;
        }

        const member = oldEmail ? await getMemberIdentityData(oldEmail) : await getMemberIdentityData(email);

        if (member) {
            await MemberLoginEvent.add({member_id: member.id});
            if (oldEmail) {
                // user exists but wants to change their email address
                if (oldEmail) {
                    member.email = email;
                }
                await users.update(member, {id: member.id});
                return getMemberIdentityData(email);
            }
            return member;
        }

        const newMember = await users.create({name, email, labels});
        await MemberLoginEvent.add({member_id: newMember.id});
        return getMemberIdentityData(email);
    }

    async function getMemberIdentityData(email) {
        return memberBREADService.read({email});
    }

    async function getMemberIdentityToken(email) {
        const member = await getMemberIdentityData(email);
        if (!member) {
            return null;
        }
        return tokenService.encodeIdentityToken({sub: member.email});
    }

    async function setMemberGeolocationFromIp(email, ip) {
        if (!email || !ip) {
            throw new errors.IncorrectUsageError({
                message: 'setMemberGeolocationFromIp() expects email and ip arguments to be present'
            });
        }

        // toJSON() is needed here otherwise users.update() will pick methods off
        // the model object rather than data and fail to edit correctly
        const member = (await users.get({email})).toJSON();

        if (!member) {
            throw new errors.NotFoundError({
                message: `Member with email address ${email} does not exist`
            });
        }

        // max request time is 500ms so shouldn't slow requests down too much
        let geolocation = JSON.stringify(await geolocationService.getGeolocationFromIP(ip));
        if (geolocation) {
            member.geolocation = geolocation;
            await users.update(member, {id: member.id});
        }

        return getMemberIdentityData(email);
    }

    const middleware = {
        sendMagicLink: Router().use(
            body.json(),
            (req, res) => routerController.sendMagicLink(req, res)
        ),
        createCheckoutSession: Router().use(
            body.json(),
            (req, res) => routerController.createCheckoutSession(req, res)
        ),
        createCheckoutSetupSession: Router().use(
            body.json(),
            (req, res) => routerController.createCheckoutSetupSession(req, res)
        ),
        createEvents: Router().use(
            body.json(),
            (req, res) => MembersAnalyticsIngress.createEvents(req, res)
        ),
        updateEmailAddress: Router().use(
            body.json(),
            (req, res) => memberController.updateEmailAddress(req, res)
        ),
        updateSubscription: Router({mergeParams: true}).use(
            body.json(),
            (req, res) => memberController.updateSubscription(req, res)
        ),
        handleStripeWebhook: Router(),
        wellKnown: Router()
            .get('/jwks.json',
                (req, res) => wellKnownController.getPublicKeys(req, res)
            )
    };

    middleware.handleStripeWebhook.use(body.raw({type: 'application/json'}), async function (req, res) {
        if (!stripeAPIService.configured) {
            logging.error(`Stripe not configured, not handling webhook`);
            res.writeHead(400);
            return res.end();
        }

        if (!req.body || !req.headers['stripe-signature']) {
            res.writeHead(400);
            return res.end();
        }
        let event;
        try {
            event = stripeWebhookService.parseWebhook(req.body, req.headers['stripe-signature']);
        } catch (err) {
            logging.error(err);
            res.writeHead(401);
            return res.end();
        }
        logging.info(`Handling webhook ${event.type}`);
        try {
            await stripeWebhookService.handleWebhook(event);
            res.writeHead(200);
            res.end();
        } catch (err) {
            logging.error(`Error handling webhook ${event.type}`, err);
            res.writeHead(err.statusCode || 500);
            res.end();
        }
    });

    const getPublicConfig = function () {
        return Promise.resolve({
            publicKey,
            issuer
        });
    };

    const bus = new (require('events').EventEmitter)();

    ready.then(() => {
        bus.emit('ready');
    }).catch((err) => {
        bus.emit('error', err);
    });

    return {
        middleware,
        getMemberDataFromMagicLinkToken,
        getMemberIdentityToken,
        getMemberIdentityData,
        setMemberGeolocationFromIp,
        disconnectStripe,
        getPublicConfig,
        bus,
        sendEmailWithMagicLink,
        getMagicLink,
        hasActiveStripeSubscriptions,
        members: users,
        memberBREADService,
        events: eventRepository,
        productRepository
    };
};
<commit_msg>Removed Migrations&disconnectStripe<commit_after>const {Router} = require('express');
const body = require('body-parser');
const MagicLink = require('@tryghost/magic-link');
const errors = require('@tryghost/errors');
const logging = require('@tryghost/logging');

const MemberAnalyticsService = require('@tryghost/member-analytics-service');
const MembersAnalyticsIngress = require('@tryghost/members-analytics-ingress');
const PaymentsService = require('@tryghost/members-payments');

const StripeWebhookService = require('./services/stripe-webhook');
const TokenService = require('./services/token');
const GeolocationSerice = require('./services/geolocation');
const MemberBREADService = require('./services/member-bread');
const MemberRepository = require('./repositories/member');
const EventRepository = require('./repositories/event');
const ProductRepository = require('./repositories/product');
const RouterController = require('./controllers/router');
const MemberController = require('./controllers/member');
const WellKnownController = require('./controllers/well-known');

module.exports = function MembersAPI({
    tokenConfig: {
        issuer,
        privateKey,
        publicKey
    },
    auth: {
        allowSelfSignup = () => true,
        getSigninURL,
        tokenProvider
    },
    paymentConfig,
    mail: {
        transporter,
        getText,
        getHTML,
        getSubject
    },
    models: {
        StripeWebhook,
        StripeCustomer,
        StripeCustomerSubscription,
        Member,
        MemberSubscribeEvent,
        MemberLoginEvent,
        MemberPaidSubscriptionEvent,
        MemberPaymentEvent,
        MemberStatusEvent,
        MemberProductEvent,
        MemberEmailChangeEvent,
        MemberAnalyticEvent,
        Offer,
        OfferRedemption,
        StripeProduct,
        StripePrice,
        Product
    },
    stripeAPIService,
    offersAPI,
    labsService
}) {
    const tokenService = new TokenService({
        privateKey,
        publicKey,
        issuer
    });

    const stripeConfig = paymentConfig && paymentConfig.stripe || {};

    const memberAnalyticsService = MemberAnalyticsService.create(MemberAnalyticEvent);
    memberAnalyticsService.eventHandler.setupSubscribers();

    const productRepository = new ProductRepository({
        Product,
        StripeProduct,
        StripePrice,
        stripeAPIService
    });

    const memberRepository = new MemberRepository({
        stripeAPIService,
        tokenService,
        productRepository,
        Member,
        MemberSubscribeEvent,
        MemberPaidSubscriptionEvent,
        MemberEmailChangeEvent,
        MemberStatusEvent,
        MemberProductEvent,
        OfferRedemption,
        StripeCustomer,
        StripeCustomerSubscription
    });

    const eventRepository = new EventRepository({
        MemberSubscribeEvent,
        MemberPaidSubscriptionEvent,
        MemberPaymentEvent,
        MemberStatusEvent,
        MemberLoginEvent
    });

    const memberBREADService = new MemberBREADService({
        offersAPI,
        memberRepository,
        emailService: {
            async sendEmailWithMagicLink({email, requestedType}) {
                return sendEmailWithMagicLink({
                    email,
                    requestedType,
                    options: {
                        forceEmailType: true
                    }
                });
            }
        },
        labsService,
        stripeService: stripeAPIService
    });

    const stripeWebhookService = new StripeWebhookService({
        StripeWebhook,
        stripeAPIService,
        productRepository,
        memberRepository,
        eventRepository,
        /**
         * @param {string} email
         */
        async sendSignupEmail(email) {
            const requestedType = 'signup-paid';
            await sendEmailWithMagicLink({
                email,
                requestedType,
                options: {
                    forceEmailType: true
                },
                tokenData: {}
            });
        }
    });

    const geolocationService = new GeolocationSerice();

    const magicLinkService = new MagicLink({
        transporter,
        tokenProvider,
        getSigninURL,
        getText,
        getHTML,
        getSubject
    });

    const memberController = new MemberController({
        memberRepository,
        StripePrice,
        tokenService,
        sendEmailWithMagicLink
    });

    const paymentsService = new PaymentsService({
        Offer,
        offersAPI,
        stripeAPIService
    });

    const routerController = new RouterController({
        offersAPI,
        paymentsService,
        productRepository,
        memberRepository,
        StripePrice,
        allowSelfSignup,
        magicLinkService,
        stripeAPIService,
        tokenService,
        sendEmailWithMagicLink,
        labsService,
        config: {
            checkoutSuccessUrl: stripeConfig.checkoutSuccessUrl,
            checkoutCancelUrl: stripeConfig.checkoutCancelUrl,
            billingSuccessUrl: stripeConfig.billingSuccessUrl,
            billingCancelUrl: stripeConfig.billingCancelUrl
        }
    });

    const wellKnownController = new WellKnownController({
        tokenService
    });

    async function hasActiveStripeSubscriptions() {
        const firstActiveSubscription = await StripeCustomerSubscription.findOne({
            status: 'active'
        });

        if (firstActiveSubscription) {
            return true;
        }

        const firstTrialingSubscription = await StripeCustomerSubscription.findOne({
            status: 'trialing'
        });

        if (firstTrialingSubscription) {
            return true;
        }

        const firstUnpaidSubscription = await StripeCustomerSubscription.findOne({
            status: 'unpaid'
        });

        if (firstUnpaidSubscription) {
            return true;
        }

        const firstPastDueSubscription = await StripeCustomerSubscription.findOne({
            status: 'past_due'
        });

        if (firstPastDueSubscription) {
            return true;
        }

        return false;
    }

    const users = memberRepository;

    async function sendEmailWithMagicLink({email, requestedType, tokenData, options = {forceEmailType: false}, requestSrc = ''}) {
        let type = requestedType;
        if (!options.forceEmailType) {
            const member = await users.get({email});
            if (member) {
                type = 'signin';
            } else if (type !== 'subscribe') {
                type = 'signup';
            }
        }
        return magicLinkService.sendMagicLink({email, type, requestSrc, tokenData: Object.assign({email}, tokenData)});
    }

    function getMagicLink(email) {
        return magicLinkService.getMagicLink({tokenData: {email}, type: 'signin'});
    }

    async function getMemberDataFromMagicLinkToken(token) {
        const {email, labels = [], name = '', oldEmail} = await magicLinkService.getDataFromToken(token);
        if (!email) {
            return null;
        }

        const member = oldEmail ? await getMemberIdentityData(oldEmail) : await getMemberIdentityData(email);

        if (member) {
            await MemberLoginEvent.add({member_id: member.id});
            if (oldEmail) {
                // user exists but wants to change their email address
                if (oldEmail) {
                    member.email = email;
                }
                await users.update(member, {id: member.id});
                return getMemberIdentityData(email);
            }
            return member;
        }

        const newMember = await users.create({name, email, labels});
        await MemberLoginEvent.add({member_id: newMember.id});
        return getMemberIdentityData(email);
    }

    async function getMemberIdentityData(email) {
        return memberBREADService.read({email});
    }

    async function getMemberIdentityToken(email) {
        const member = await getMemberIdentityData(email);
        if (!member) {
            return null;
        }
        return tokenService.encodeIdentityToken({sub: member.email});
    }

    async function setMemberGeolocationFromIp(email, ip) {
        if (!email || !ip) {
            throw new errors.IncorrectUsageError({
                message: 'setMemberGeolocationFromIp() expects email and ip arguments to be present'
            });
        }

        // toJSON() is needed here otherwise users.update() will pick methods off
        // the model object rather than data and fail to edit correctly
        const member = (await users.get({email})).toJSON();

        if (!member) {
            throw new errors.NotFoundError({
                message: `Member with email address ${email} does not exist`
            });
        }

        // max request time is 500ms so shouldn't slow requests down too much
        let geolocation = JSON.stringify(await geolocationService.getGeolocationFromIP(ip));
        if (geolocation) {
            member.geolocation = geolocation;
            await users.update(member, {id: member.id});
        }

        return getMemberIdentityData(email);
    }

    const middleware = {
        sendMagicLink: Router().use(
            body.json(),
            (req, res) => routerController.sendMagicLink(req, res)
        ),
        createCheckoutSession: Router().use(
            body.json(),
            (req, res) => routerController.createCheckoutSession(req, res)
        ),
        createCheckoutSetupSession: Router().use(
            body.json(),
            (req, res) => routerController.createCheckoutSetupSession(req, res)
        ),
        createEvents: Router().use(
            body.json(),
            (req, res) => MembersAnalyticsIngress.createEvents(req, res)
        ),
        updateEmailAddress: Router().use(
            body.json(),
            (req, res) => memberController.updateEmailAddress(req, res)
        ),
        updateSubscription: Router({mergeParams: true}).use(
            body.json(),
            (req, res) => memberController.updateSubscription(req, res)
        ),
        handleStripeWebhook: Router(),
        wellKnown: Router()
            .get('/jwks.json',
                (req, res) => wellKnownController.getPublicKeys(req, res)
            )
    };

    middleware.handleStripeWebhook.use(body.raw({type: 'application/json'}), async function (req, res) {
        if (!stripeAPIService.configured) {
            logging.error(`Stripe not configured, not handling webhook`);
            res.writeHead(400);
            return res.end();
        }

        if (!req.body || !req.headers['stripe-signature']) {
            res.writeHead(400);
            return res.end();
        }
        let event;
        try {
            event = stripeWebhookService.parseWebhook(req.body, req.headers['stripe-signature']);
        } catch (err) {
            logging.error(err);
            res.writeHead(401);
            return res.end();
        }
        logging.info(`Handling webhook ${event.type}`);
        try {
            await stripeWebhookService.handleWebhook(event);
            res.writeHead(200);
            res.end();
        } catch (err) {
            logging.error(`Error handling webhook ${event.type}`, err);
            res.writeHead(err.statusCode || 500);
            res.end();
        }
    });

    const getPublicConfig = function () {
        return Promise.resolve({
            publicKey,
            issuer
        });
    };

    const bus = new (require('events').EventEmitter)();

    bus.emit('ready');

    return {
        middleware,
        getMemberDataFromMagicLinkToken,
        getMemberIdentityToken,
        getMemberIdentityData,
        setMemberGeolocationFromIp,
        getPublicConfig,
        bus,
        sendEmailWithMagicLink,
        getMagicLink,
        hasActiveStripeSubscriptions,
        members: users,
        memberBREADService,
        events: eventRepository,
        productRepository
    };
};
<|endoftext|>


<commit_before>import React from 'react';
import Title from 'react-title-component';
import { connect } from 'react-redux';
import { FormattedMessage, injectIntl } from 'react-intl';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import TopicSearchContainer from './search/TopicSearchContainer';
import FavoriteTopicsContainer from './list/FavoriteTopicsContainer';
import TopicListContainer from './list/TopicListContainer';
import { updateFeedback } from '../../actions/appActions';
import { setTopicFavorite, fetchFavoriteTopics } from '../../actions/topicActions';
import TopicIcon from '../common/icons/TopicIcon';
import messages from '../../resources/messages';

const localMessages = {
  homeTitle: { id: 'home.title', defaultMessage: 'Explore Topics' },
};

const HomeContainer = (props) => {
  const { handleSetFavorited } = props;
  const { formatMessage } = props.intl;
  const title = formatMessage(localMessages.homeTitle);
  const titleHandler = parentTitle => `${title} | ${parentTitle}`;
  return (

      <Title render={titleHandler} />
      <Grid>
        <Row>
          <Col lg={8} xs={12}>

<TopicIcon height={32} /><FormattedMessage {...localMessages.homeTitle} />

          </Col>
          <Col lg={4} xs={12}>

<TopicSearchContainer />

          </Col>
        </Row>
        <FavoriteTopicsContainer onSetFavorited={handleSetFavorited} />
        <TopicListContainer onSetFavorited={handleSetFavorited} />
      </Grid>

  );
};

HomeContainer.propTypes = {
  // from context
  intl: React.PropTypes.object.isRequired,
  // from dispatch
  handleSetFavorited: React.PropTypes.func.isRequired,
};

const mapDispatchToProps = (dispatch, ownProps) => ({
  handleSetFavorited: (topicId, isFavorite) => {
    dispatch(setTopicFavorite(topicId, isFavorite))
      .then(() => {
        const msg = (isFavorite) ? messages.topicFavorited : messages.topicUnfavorited;
        dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(msg) }));
        dispatch(fetchFavoriteTopics());  // to update the list of favorites
      });
  },
});

export default
  injectIntl(
    connect(null, mapDispatchToProps)(
      HomeContainer
    )
  );
<commit_msg>move topic search into control bar display format<commit_after>import React from 'react';
import Title from 'react-title-component';
import { connect } from 'react-redux';
import { FormattedMessage, injectIntl } from 'react-intl';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import TopicSearchContainer from './search/TopicSearchContainer';
import FavoriteTopicsContainer from './list/FavoriteTopicsContainer';
import TopicListContainer from './list/TopicListContainer';
import { updateFeedback } from '../../actions/appActions';
import { setTopicFavorite, fetchFavoriteTopics } from '../../actions/topicActions';
import TopicIcon from '../common/icons/TopicIcon';
import messages from '../../resources/messages';

const localMessages = {
  homeTitle: { id: 'home.title', defaultMessage: 'Explore Topics' },
};

const HomeContainer = (props) => {
  const { handleSetFavorited } = props;
  const { formatMessage } = props.intl;
  const title = formatMessage(localMessages.homeTitle);
  const titleHandler = parentTitle => `${title} | ${parentTitle}`;
  return (

      <Title render={titleHandler} />

          <Grid>
            <Row>
              <Col lg={8} />
              <Col lg={4}>
                <TopicSearchContainer />
              </Col>
            </Row>
          </Grid>

      <Grid>
        <Row>
          <Col lg={12}>

<TopicIcon height={32} /><FormattedMessage {...localMessages.homeTitle} />

          </Col>
        </Row>
        <FavoriteTopicsContainer onSetFavorited={handleSetFavorited} />
        <TopicListContainer onSetFavorited={handleSetFavorited} />
      </Grid>

  );
};

HomeContainer.propTypes = {
  // from context
  intl: React.PropTypes.object.isRequired,
  // from dispatch
  handleSetFavorited: React.PropTypes.func.isRequired,
};

const mapDispatchToProps = (dispatch, ownProps) => ({
  handleSetFavorited: (topicId, isFavorite) => {
    dispatch(setTopicFavorite(topicId, isFavorite))
      .then(() => {
        const msg = (isFavorite) ? messages.topicFavorited : messages.topicUnfavorited;
        dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(msg) }));
        dispatch(fetchFavoriteTopics());  // to update the list of favorites
      });
  },
});

export default
  injectIntl(
    connect(null, mapDispatchToProps)(
      HomeContainer
    )
  );
<|endoftext|>


<commit_before>/**
 * Taken, CommonJS-ified, and heavily modified from:
 * https://github.com/flyingsparx/NodeDirectUploader
 */

S3Upload.prototype.server = '';
S3Upload.prototype.signingUrl = '/sign-s3';
S3Upload.prototype.signingUrlMethod = 'GET';
S3Upload.prototype.fileElement = null;
S3Upload.prototype.files = null;

S3Upload.prototype.onFinishS3Put = function(signResult, file) {
    return console.log('base.onFinishS3Put()', signResult.publicUrl);
};

S3Upload.prototype.preprocess = function(file, next) {
    console.log('base.preprocess()', file);
    return next(file);
};

S3Upload.prototype.onProgress = function(percent, status, file) {
    return console.log('base.onProgress()', percent, status);
};

S3Upload.prototype.onError = function(status, file) {
    return console.log('base.onError()', status);
};

S3Upload.prototype.scrubFilename = function(filename) {
    return filename.replace(/[^\w\d_\-\.]+/ig, '');
};

function S3Upload(options) {
    if (options == null) {
        options = {};
    }
    for (var option in options) {
        if (options.hasOwnProperty(option)) {
            this[option] = options[option];
        }
    }
    var files = this.fileElement ? this.fileElement.files : this.files || [];
    this.handleFileSelect(files);
}

S3Upload.prototype.handleFileSelect = function(files) {
    var result = [];
    for (var i=0; i < files.length; i++) {
        var file = files[i];
        this.preprocess(file, function(processedFile){
          this.onProgress(0, 'Waiting', processedFile);
          result.push(this.uploadFile(processedFile));
          return result;
        }.bind(this));
    }
};

S3Upload.prototype.createCORSRequest = function(method, url, opts) {
    var opts = opts || {};
    var xhr = new XMLHttpRequest();

    if (xhr.withCredentials != null) {
        xhr.open(method, url, true);
        if (opts.withCredentials != null) {
            xhr.withCredentials = opts.withCredentials;
        }
    }
    else if (typeof XDomainRequest !== "undefined") {
        xhr = new XDomainRequest();
        xhr.open(method, url);
    }
    else {
        xhr = null;
    }
    return xhr;
};

S3Upload.prototype.executeOnSignedUrl = function(file, callback) {
    var fileName = this.scrubFilename(file.name);
    var queryString = '?objectName=' + fileName + '&contentType=' + encodeURIComponent(file.type);
    if (this.signingUrlQueryParams) {
        var signingUrlQueryParams = typeof this.signingUrlQueryParams === 'function' ? this.signingUrlQueryParams() : this.signingUrlQueryParams;
        Object.keys(signingUrlQueryParams).forEach(function(key) {
            var val = signingUrlQueryParams[key];
            queryString += '&' + key + '=' + val;
        });
    }
    var xhr = this.createCORSRequest(this.signingUrlMethod,
        this.server + this.signingUrl + queryString, { withCredentials: this.signingUrlWithCredentials });
    if (this.signingUrlHeaders) {
        var signingUrlHeaders = this.signingUrlHeaders;
        Object.keys(signingUrlHeaders).forEach(function(key) {
            var val = signingUrlHeaders[key];
            xhr.setRequestHeader(key, val);
        });
    }
    xhr.overrideMimeType && xhr.overrideMimeType('text/plain; charset=x-user-defined');
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
            var result;
            try {
                result = JSON.parse(xhr.responseText);
            } catch (error) {
                this.onError('Invalid response from server', file);
                return false;
            }
            return callback(result);
        } else if (xhr.readyState === 4 && xhr.status !== 200) {
            return this.onError('Could not contact request signing server. Status = ' + xhr.status, file);
        }
    }.bind(this);
    return xhr.send();
};

S3Upload.prototype.uploadToS3 = function(file, signResult) {
    var xhr = this.createCORSRequest('PUT', signResult.signedUrl);
    if (!xhr) {
        this.onError('CORS not supported', file);
    } else {
        xhr.onload = function() {
            if (xhr.status === 200) {
                this.onProgress(100, 'Upload completed', file);
                return this.onFinishS3Put(signResult, file);
            } else {
                return this.onError('Upload error: ' + xhr.status, file);
            }
        }.bind(this);
        xhr.onerror = function() {
            return this.onError('XHR error', file);
        }.bind(this);
        xhr.upload.onprogress = function(e) {
            var percentLoaded;
            if (e.lengthComputable) {
                percentLoaded = Math.round((e.loaded / e.total) * 100);
                return this.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing' : 'Uploading', file);
            }
        }.bind(this);
    }
    xhr.setRequestHeader('Content-Type', file.type);
    if (this.contentDisposition) {
        var disposition = this.contentDisposition;
        if (disposition === 'auto') {
            if (file.type.substr(0, 6) === 'image/') {
                disposition = 'inline';
            } else {
                disposition = 'attachment';
            }
        }

        var fileName = this.scrubFilename(file.name)
        xhr.setRequestHeader('Content-Disposition', disposition + '; filename="' + fileName + '"');
    }
    if (this.uploadRequestHeaders) {
        var uploadRequestHeaders = this.uploadRequestHeaders;
        Object.keys(uploadRequestHeaders).forEach(function(key) {
            var val = uploadRequestHeaders[key];
            xhr.setRequestHeader(key, val);
        });
    } else {
        xhr.setRequestHeader('x-amz-acl', 'public-read');
    }
    this.httprequest = xhr;
    return xhr.send(file);
};

S3Upload.prototype.uploadFile = function(file) {
    var uploadToS3Callback = this.uploadToS3.bind(this, file);

    if(this.getSignedUrl) return this.getSignedUrl(file, uploadToS3Callback);
    return this.executeOnSignedUrl(file, uploadToS3Callback);
};

S3Upload.prototype.abortUpload = function() {
    this.httprequest && this.httprequest.abort();
};

module.exports = S3Upload;
<commit_msg>Support setting headers in signResult callback<commit_after>/**
 * Taken, CommonJS-ified, and heavily modified from:
 * https://github.com/flyingsparx/NodeDirectUploader
 */

S3Upload.prototype.server = '';
S3Upload.prototype.signingUrl = '/sign-s3';
S3Upload.prototype.signingUrlMethod = 'GET';
S3Upload.prototype.fileElement = null;
S3Upload.prototype.files = null;

S3Upload.prototype.onFinishS3Put = function(signResult, file) {
    return console.log('base.onFinishS3Put()', signResult.publicUrl);
};

S3Upload.prototype.preprocess = function(file, next) {
    console.log('base.preprocess()', file);
    return next(file);
};

S3Upload.prototype.onProgress = function(percent, status, file) {
    return console.log('base.onProgress()', percent, status);
};

S3Upload.prototype.onError = function(status, file) {
    return console.log('base.onError()', status);
};

S3Upload.prototype.scrubFilename = function(filename) {
    return filename.replace(/[^\w\d_\-\.]+/ig, '');
};

function S3Upload(options) {
    if (options == null) {
        options = {};
    }
    for (var option in options) {
        if (options.hasOwnProperty(option)) {
            this[option] = options[option];
        }
    }
    var files = this.fileElement ? this.fileElement.files : this.files || [];
    this.handleFileSelect(files);
}

S3Upload.prototype.handleFileSelect = function(files) {
    var result = [];
    for (var i=0; i < files.length; i++) {
        var file = files[i];
        this.preprocess(file, function(processedFile){
          this.onProgress(0, 'Waiting', processedFile);
          result.push(this.uploadFile(processedFile));
          return result;
        }.bind(this));
    }
};

S3Upload.prototype.createCORSRequest = function(method, url, opts) {
    var opts = opts || {};
    var xhr = new XMLHttpRequest();

    if (xhr.withCredentials != null) {
        xhr.open(method, url, true);
        if (opts.withCredentials != null) {
            xhr.withCredentials = opts.withCredentials;
        }
    }
    else if (typeof XDomainRequest !== "undefined") {
        xhr = new XDomainRequest();
        xhr.open(method, url);
    }
    else {
        xhr = null;
    }
    return xhr;
};

S3Upload.prototype.executeOnSignedUrl = function(file, callback) {
    var fileName = this.scrubFilename(file.name);
    var queryString = '?objectName=' + fileName + '&contentType=' + encodeURIComponent(file.type);
    if (this.signingUrlQueryParams) {
        var signingUrlQueryParams = typeof this.signingUrlQueryParams === 'function' ? this.signingUrlQueryParams() : this.signingUrlQueryParams;
        Object.keys(signingUrlQueryParams).forEach(function(key) {
            var val = signingUrlQueryParams[key];
            queryString += '&' + key + '=' + val;
        });
    }
    var xhr = this.createCORSRequest(this.signingUrlMethod,
        this.server + this.signingUrl + queryString, { withCredentials: this.signingUrlWithCredentials });
    if (this.signingUrlHeaders) {
        var signingUrlHeaders = this.signingUrlHeaders;
        Object.keys(signingUrlHeaders).forEach(function(key) {
            var val = signingUrlHeaders[key];
            xhr.setRequestHeader(key, val);
        });
    }
    xhr.overrideMimeType && xhr.overrideMimeType('text/plain; charset=x-user-defined');
    xhr.onreadystatechange = function() {
        if (xhr.readyState === 4 && xhr.status === 200) {
            var result;
            try {
                result = JSON.parse(xhr.responseText);
            } catch (error) {
                this.onError('Invalid response from server', file);
                return false;
            }
            return callback(result);
        } else if (xhr.readyState === 4 && xhr.status !== 200) {
            return this.onError('Could not contact request signing server. Status = ' + xhr.status, file);
        }
    }.bind(this);
    return xhr.send();
};

S3Upload.prototype.uploadToS3 = function(file, signResult) {
    var xhr = this.createCORSRequest('PUT', signResult.signedUrl);
    if (!xhr) {
        this.onError('CORS not supported', file);
    } else {
        xhr.onload = function() {
            if (xhr.status === 200) {
                this.onProgress(100, 'Upload completed', file);
                return this.onFinishS3Put(signResult, file);
            } else {
                return this.onError('Upload error: ' + xhr.status, file);
            }
        }.bind(this);
        xhr.onerror = function() {
            return this.onError('XHR error', file);
        }.bind(this);
        xhr.upload.onprogress = function(e) {
            var percentLoaded;
            if (e.lengthComputable) {
                percentLoaded = Math.round((e.loaded / e.total) * 100);
                return this.onProgress(percentLoaded, percentLoaded === 100 ? 'Finalizing' : 'Uploading', file);
            }
        }.bind(this);
    }
    xhr.setRequestHeader('Content-Type', file.type);
    if (this.contentDisposition) {
        var disposition = this.contentDisposition;
        if (disposition === 'auto') {
            if (file.type.substr(0, 6) === 'image/') {
                disposition = 'inline';
            } else {
                disposition = 'attachment';
            }
        }

        var fileName = this.scrubFilename(file.name)
        xhr.setRequestHeader('Content-Disposition', disposition + '; filename="' + fileName + '"');
    }
    if (signResult.headers) {
        var signResultHeaders = signResult.headers
        Object.keys(signResultHeaders).forEach(function(key) {
            var val = signResultHeaders[key];
            xhr.setRequestHeader(key, val);
        })
    }
    if (this.uploadRequestHeaders) {
        var uploadRequestHeaders = this.uploadRequestHeaders;
        Object.keys(uploadRequestHeaders).forEach(function(key) {
            var val = uploadRequestHeaders[key];
            xhr.setRequestHeader(key, val);
        });
    } else {
        xhr.setRequestHeader('x-amz-acl', 'public-read');
    }
    this.httprequest = xhr;
    return xhr.send(file);
};

S3Upload.prototype.uploadFile = function(file) {
    var uploadToS3Callback = this.uploadToS3.bind(this, file);

    if(this.getSignedUrl) return this.getSignedUrl(file, uploadToS3Callback);
    return this.executeOnSignedUrl(file, uploadToS3Callback);
};

S3Upload.prototype.abortUpload = function() {
    this.httprequest && this.httprequest.abort();
};

module.exports = S3Upload;
<|endoftext|>


<commit_before>var jsdiff = require("diff");

function TopicLogPlugin(bot) {
	var self = this;
	self.name = "topiclog";
	self.help = "Topic logger plugin";
	self.depend = ["cmd"];

	self.topiclog = {};

	self.load = function(data) {
		if (data) {
			self.topiclog = data;
			for (var channel in self.topiclog) {
				var chanlog = self.topiclog[channel];
				for (var i = 0; i < chanlog.length; i++)
					chanlog[i].time = new Date(chanlog[i].time);
			}
		}
	};

	self.save = function() {
		return self.topiclog;
	};

	self.colordiff = function(str1, str2) {
		var diff = jsdiff.diffWords(str1, str2);
		var str = "";

		diff.forEach(function(part) {
			if (part.added)
				str += "\x033{+" + part.value + "+}";
			else if (part.removed)
				str += "\x034[-" + part.value + "-]";
			else
				str += "\x0f" + part.value;
		});

		return str;
	};

	self.events = {
		"topic": function(channel, topic, nick, message) {
			var chanlog = self.topiclog[channel];
			if (chanlog === undefined || chanlog[chanlog.length - 1].topic != topic) {
				var entry = {};
				if (message.command == "333") {
					var match = message.args[2].match(/^([_a-zA-Z0-9\[\]\\`^{}|-]*)(!([^@]+)@(.*))?$/);
					entry = {
						"nick": match[1],
						"user": match[3],
						"host": match[4],
						"topic": topic,
						"time": new Date(parseInt(message.args[3]) * 1000)
					};
				}
				else
				{
					entry = {
						"nick": nick,
						"user": message.user,
						"host": message.host,
						"topic": topic,
						"time": new Date()
					};
				}

				if (chanlog === undefined)
					self.topiclog[channel] = [entry];
				else
					self.topiclog[channel].push(entry);
			}
		},

		"join": function(channel, nick) {
			if (nick == bot.nick)
				bot.send("TOPIC", channel);
		},

		"cmd#topics": function(nick, to, args) {
			var cnt = Math.min(Math.max(args[1], 1) || 3, 5);
			var chanlog = self.topiclog[to];

			var lastentry = chanlog[chanlog.length - cnt - 1];
			for (var i = -cnt; i < 0; i++) {
				var entry = chanlog[chanlog.length + i];
				if (entry)
				{
					var str;
					if (lastentry)
						str = self.colordiff(lastentry.topic, entry.topic);
					else
						str = entry.topic;

					bot.notice(nick, "Topic in " + to + " by " + entry.nick + " at " + entry.time.toUTCString() + ": " + str);
					lastentry = entry;
				}
			}
		},

		"cmd#topicdiff": function(nick, to, args) {
			var chanlog = self.topiclog[to];
			if (chanlog.length >= 2)
			{
				var str = self.colordiff(chanlog[chanlog.length - 2].topic, chanlog[chanlog.length - 1].topic);
				bot.say(to, "Topic diff: " + str);
			}
			else
				bot.say(to, "Cannot find topic diff with less than 2 known topics");
		}
	}
}

module.exports = TopicLogPlugin;
<commit_msg>Bold pretext of topiclog<commit_after>var jsdiff = require("diff");

function TopicLogPlugin(bot) {
	var self = this;
	self.name = "topiclog";
	self.help = "Topic logger plugin";
	self.depend = ["cmd"];

	self.topiclog = {};

	self.load = function(data) {
		if (data) {
			self.topiclog = data;
			for (var channel in self.topiclog) {
				var chanlog = self.topiclog[channel];
				for (var i = 0; i < chanlog.length; i++)
					chanlog[i].time = new Date(chanlog[i].time);
			}
		}
	};

	self.save = function() {
		return self.topiclog;
	};

	self.colordiff = function(str1, str2) {
		var diff = jsdiff.diffWords(str1, str2);
		var str = "";

		diff.forEach(function(part) {
			if (part.added)
				str += "\x033{+" + part.value + "+}";
			else if (part.removed)
				str += "\x034[-" + part.value + "-]";
			else
				str += "\x0f" + part.value;
		});

		return str;
	};

	self.events = {
		"topic": function(channel, topic, nick, message) {
			var chanlog = self.topiclog[channel];
			if (chanlog === undefined || chanlog[chanlog.length - 1].topic != topic) {
				var entry = {};
				if (message.command == "333") {
					var match = message.args[2].match(/^([_a-zA-Z0-9\[\]\\`^{}|-]*)(!([^@]+)@(.*))?$/);
					entry = {
						"nick": match[1],
						"user": match[3],
						"host": match[4],
						"topic": topic,
						"time": new Date(parseInt(message.args[3]) * 1000)
					};
				}
				else
				{
					entry = {
						"nick": nick,
						"user": message.user,
						"host": message.host,
						"topic": topic,
						"time": new Date()
					};
				}

				if (chanlog === undefined)
					self.topiclog[channel] = [entry];
				else
					self.topiclog[channel].push(entry);
			}
		},

		"join": function(channel, nick) {
			if (nick == bot.nick)
				bot.send("TOPIC", channel);
		},

		"cmd#topics": function(nick, to, args) {
			var cnt = Math.min(Math.max(args[1], 1) || 3, 5);
			var chanlog = self.topiclog[to];

			var lastentry = chanlog[chanlog.length - cnt - 1];
			for (var i = -cnt; i < 0; i++) {
				var entry = chanlog[chanlog.length + i];
				if (entry)
				{
					var str;
					if (lastentry)
						str = self.colordiff(lastentry.topic, entry.topic);
					else
						str = entry.topic;

					bot.notice(nick, "\x02Topic in " + to + " by " + entry.nick + " at " + entry.time.toUTCString() + ":\x02 " + str);
					lastentry = entry;
				}
			}
		},

		"cmd#topicdiff": function(nick, to, args) {
			var chanlog = self.topiclog[to];
			if (chanlog.length >= 2)
			{
				var str = self.colordiff(chanlog[chanlog.length - 2].topic, chanlog[chanlog.length - 1].topic);
				bot.say(to, "\x02Topic diff:\x02 " + str);
			}
			else
				bot.say(to, "Cannot find topic diff with less than 2 known topics");
		}
	}
}

module.exports = TopicLogPlugin;
<|endoftext|>


<commit_before>Meteor.methods({
  removeComment: function (comment) {
    if (!isAdmin(Meteor.user()))
      throw new Meteor.Error('no-permission', 'This user does not have permission to continue.');

    var commentId = comment._id;
    var topicId = comment.topicId;
    var userId = comment.userId;
    var upvotes = comment.upvotes;
    var upvoters = comment.upvoters;

    // subtract comment count from topic
    Topics.update(topicId, {
      $inc: { 'commentsCount': -1 }
    });
    // mark comment as deleted
    Comments.update(commentId, {
      $set: {
        'isDeleted': true,
        'upvotes': 0,
        'content': 'Sorry, this comment has been deleted.'
      }
    });
    // subtract comments count and reputation
    Meteor.users.update(userId, {
      $inc: { 'stats.commentsCount': -1, 'stats.reputation': -upvotes },
      $pull: { 'activity.discussedTopics': topicId }
    });
    // remove comment from users upvotedComments
    Meteor.users.update(upvoters, {
      $pull: { 'activity.upvotedComments': commentId }
    }, { multi: true });
  },
  removeTopic: function (topic) {
    if (!isAdmin(Meteor.user()))
      throw new Meteor.Error('no-permission', 'This user does not have permission to continue.');

    var topicId = topic._id;
    var userId = topic.userId;

    var commentIds = [];
    var commenters = {}; // all users related to the topic comments (creators, upvoters, etc)
    var opts = { // the fields that could be changed
      'commentsCount': 0,
      'reputation': 0,
      'upvotedComments': []
    };

    Comments.find({ 'topicId': topicId }).forEach(function (comment) {
      commenters[comment.userId] = commenters[comment.userId] || opts;
      commenters[comment.userId].commentsCount++;
      commenters[comment.userId].reputation += comment.upvotes;

      _.each(comment.upvoters, function (upvoter) {
        commenters[upvoter] = commenters[upvoter] || opts;
        commenters[upvoter].upvotedComments.push(comment._id);
      });

      commentIds.push(comment._id);
    });

    var commenterIds = _.keys(commenters);
    _.each(commenterIds, function (commenterId) {
      var commenter = commenters[commenterId];
      // subtract comments count & reputation, remove upvoted comments
      Meteor.users.update({ '_id': commenterId }, {
        $inc: {
          'stats.commentsCount': -commenter.commentsCount,
          'stats.reputation': -commenter.reputation
        },
        $pullAll: { 'activity.upvotedComments': commenter.upvotedComments }
      });
    });

    Comments.remove({ '_id': { $in: commentIds } });

    // remove topic from users following
    Meteor.users.update({ '_id': { $in: topic.followers } }, {
      $pull: { 'activity.followingTopics': topicId }
    }, { multi: true });

    // remove topic from users discussed
    Meteor.users.update({ '_id': { $in: topic.commenters } }, {
      $pull: { 'activity.discussedTopics': topicId }
    }, { multi: true });

    // subtract topics count for creator
    Meteor.users.update(userId, { $inc: { 'stats.topicsCount': -1 } });

    // delete the topic
    Topics.remove(topicId);
  }
});<commit_msg>i18n for deleted comments<commit_after>Meteor.methods({
  removeComment: function (comment) {
    if (!isAdmin(Meteor.user()))
      throw new Meteor.Error('no-permission', 'This user does not have permission to continue.');

    var commentId = comment._id;
    var topicId = comment.topicId;
    var userId = comment.userId;
    var upvotes = comment.upvotes;
    var upvoters = comment.upvoters;

    // subtract comment count from topic
    Topics.update(topicId, {
      $inc: { 'commentsCount': -1 }
    });
    // mark comment as deleted
    Comments.update(commentId, {
      $set: {
        'isDeleted': true,
        'upvotes': 0,
        'content': i18n.t('comment_deleted')
      }
    });
    // subtract comments count and reputation
    Meteor.users.update(userId, {
      $inc: { 'stats.commentsCount': -1, 'stats.reputation': -upvotes },
      $pull: { 'activity.discussedTopics': topicId }
    });
    // remove comment from users upvotedComments
    Meteor.users.update(upvoters, {
      $pull: { 'activity.upvotedComments': commentId }
    }, { multi: true });
  },
  removeTopic: function (topic) {
    if (!isAdmin(Meteor.user()))
      throw new Meteor.Error('no-permission', 'This user does not have permission to continue.');

    var topicId = topic._id;
    var userId = topic.userId;

    var commentIds = [];
    var commenters = {}; // all users related to the topic comments (creators, upvoters, etc)
    var opts = { // the fields that could be changed
      'commentsCount': 0,
      'reputation': 0,
      'upvotedComments': []
    };

    Comments.find({ 'topicId': topicId }).forEach(function (comment) {
      commenters[comment.userId] = commenters[comment.userId] || opts;
      commenters[comment.userId].commentsCount++;
      commenters[comment.userId].reputation += comment.upvotes;

      _.each(comment.upvoters, function (upvoter) {
        commenters[upvoter] = commenters[upvoter] || opts;
        commenters[upvoter].upvotedComments.push(comment._id);
      });

      commentIds.push(comment._id);
    });

    var commenterIds = _.keys(commenters);
    _.each(commenterIds, function (commenterId) {
      var commenter = commenters[commenterId];
      // subtract comments count & reputation, remove upvoted comments
      Meteor.users.update({ '_id': commenterId }, {
        $inc: {
          'stats.commentsCount': -commenter.commentsCount,
          'stats.reputation': -commenter.reputation
        },
        $pullAll: { 'activity.upvotedComments': commenter.upvotedComments }
      });
    });

    Comments.remove({ '_id': { $in: commentIds } });

    // remove topic from users following
    Meteor.users.update({ '_id': { $in: topic.followers } }, {
      $pull: { 'activity.followingTopics': topicId }
    }, { multi: true });

    // remove topic from users discussed
    Meteor.users.update({ '_id': { $in: topic.commenters } }, {
      $pull: { 'activity.discussedTopics': topicId }
    }, { multi: true });

    // subtract topics count for creator
    Meteor.users.update(userId, { $inc: { 'stats.topicsCount': -1 } });

    // delete the topic
    Topics.remove(topicId);
  }
});
<|endoftext|>