diff --git a/packages/api/src/services/subscriptions.ts b/packages/api/src/services/subscriptions.ts index 8aaea89b8..3a30469a8 100644 --- a/packages/api/src/services/subscriptions.ts +++ b/packages/api/src/services/subscriptions.ts @@ -1,4 +1,5 @@ import axios from 'axios' +import { DeleteResult } from 'typeorm' import { appDataSource } from '../data_source' import { NewsletterEmail } from '../entity/newsletter_email' import { Subscription } from '../entity/subscription' @@ -189,7 +190,8 @@ export const createSubscription = async ( newsletterEmail?: NewsletterEmail, status = SubscriptionStatus.Active, unsubscribeMailTo?: string, - subscriptionType = SubscriptionType.Newsletter + subscriptionType = SubscriptionType.Newsletter, + url?: string ): Promise => { return getRepository(Subscription).save({ user: { id: userId }, @@ -199,5 +201,17 @@ export const createSubscription = async ( unsubscribeMailTo, lastFetchedAt: new Date(), type: subscriptionType, + url, }) } + +export const deleteSubscription = async ( + id: string, + userId: string +): Promise => { + return authTrx( + (tx) => tx.getRepository(Subscription).delete(id), + undefined, + userId + ) +} diff --git a/packages/api/src/utils/createTask.ts b/packages/api/src/utils/createTask.ts index 483921bc9..f7cc4c829 100644 --- a/packages/api/src/utils/createTask.ts +++ b/packages/api/src/utils/createTask.ts @@ -602,6 +602,7 @@ export const enqueueRssFeedFetch = async ( feedUrl: rssFeedSubscription.url, lastFetchedAt: rssFeedSubscription.lastFetchedAt?.getTime() || 0, // unix timestamp in milliseconds lastFetchedChecksum: rssFeedSubscription.lastFetchedChecksum || null, + scheduledAt: new Date().getTime(), // unix timestamp in milliseconds } const headers = { diff --git a/packages/api/test/resolvers/subscriptions.test.ts b/packages/api/test/resolvers/subscriptions.test.ts index 7a9db963d..22ee107b1 100644 --- a/packages/api/test/resolvers/subscriptions.test.ts +++ b/packages/api/test/resolvers/subscriptions.test.ts @@ -1,5 +1,6 @@ import chai, { expect } from 'chai' import 'mocha' +import Parser from 'rss-parser' import sinon from 'sinon' import sinonChai from 'sinon-chai' import { NewsletterEmail } from '../../src/entity/newsletter_email' @@ -9,13 +10,13 @@ import { SubscriptionStatus, SubscriptionType, } from '../../src/generated/graphql' -import { - createSubscription, - unsubscribe, - UNSUBSCRIBE_EMAIL_TEXT -} from '../../src/services/subscriptions' import { getRepository } from '../../src/repository' import { createNewsletterEmail } from '../../src/services/newsletters' +import { + createSubscription, + deleteSubscription, + UNSUBSCRIBE_EMAIL_TEXT, +} from '../../src/services/subscriptions' import { deleteUser } from '../../src/services/user' import * as sendEmail from '../../src/utils/sendEmail' import { createTestUser } from '../db' @@ -347,4 +348,91 @@ describe('Subscriptions API', () => { await getRepository(Subscription).remove(subscription) }) }) + + describe('Subscribe API', () => { + const query = ( + name: string | null, + url: string | null, + subscriptionType: SubscriptionType | null + ) => ` + mutation { + subscribe(input: { + name: ${name ? `"${name}"` : null} + url: ${url ? `"${url}"` : null} + subscriptionType: ${subscriptionType} + }) { + ... on SubscribeSuccess { + subscriptions { + id + } + } + ... on SubscribeError { + errorCodes + } + } + } + ` + + context('when subscribing to a rss feed', () => { + const url = 'https://www.omnivore.app/rss' + const subscriptionType = SubscriptionType.Rss + + before(async () => { + // fake rss parser + sinon.replace(Parser.prototype, 'parseURL', sinon.fake.resolves({ + title: 'RSS Feed', + description: 'RSS Feed Description', + })) + }) + + after(() => { + sinon.restore() + }) + + context('when the user is subscribed to the feed', () => { + let existingSubscription: Subscription + + before(async () => { + existingSubscription = await createSubscription( + user.id, + 'RSS Feed', + undefined, + SubscriptionStatus.Active, + url, + subscriptionType, + url + ) + }) + + after(async () => { + await deleteSubscription(existingSubscription.id, user.id) + }) + + it('returns an error', async () => { + const res = await graphqlRequest( + query(null, url, subscriptionType), + authToken + ).expect(200) + expect(res.body.data.subscribe.errorCodes).to.eql([ + 'ALREADY_SUBSCRIBED', + ]) + }) + }) + + it('creates a rss subscription', async () => { + const res = await graphqlRequest( + query(null, url, subscriptionType), + authToken + ).expect(200) + expect(res.body.data.subscribe.subscriptions).to.have.lengthOf(1) + expect(res.body.data.subscribe.subscriptions[0].id).to.be.a('string') + + // clean up + await deleteSubscription( + res.body.data.subscribe.subscriptions[0].id, + user.id + ) + }) + }) + }) })