-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: Refactor and migrate Paywall to
app
router, INTER-911, INTER…
…-459 (#161) * chore: move everything to `app` wip * chore: fix build and tests * chore: simplify messages * chore: fix embeds
- Loading branch information
Showing
25 changed files
with
197 additions
and
313 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { getAndValidateFingerprintResult, Severity } from '../../../../../server/checks'; | ||
import { NextResponse } from 'next/server'; | ||
import { ArticleData, ARTICLES } from '../../articles'; | ||
import { ArticleViewDbModel } from '../../database'; | ||
import { getTodayDateRange } from '../../../../../shared/utils/date'; | ||
import { Op } from 'sequelize'; | ||
import { PAYWALL_COPY } from '../../copy'; | ||
|
||
export type ArticleResponse = { | ||
message: string; | ||
severity: Severity; | ||
article?: ArticleData; | ||
remainingViews?: number; | ||
viewedArticles?: number; | ||
}; | ||
|
||
export type ArticleRequestPayload = { | ||
requestId: string; | ||
}; | ||
|
||
const ARTICLE_VIEW_LIMIT = 2; | ||
|
||
/** | ||
* Fetches article by its ID. Supports paywall logic, which means that we keep track of how many articles were viewed by a given user. | ||
* If a user has exceeded limit of articles that he can view for free, we return an error. | ||
*/ | ||
export async function POST( | ||
req: Request, | ||
{ params }: { params: { id: string } }, | ||
): Promise<NextResponse<ArticleResponse>> { | ||
const { requestId } = (await req.json()) as ArticleRequestPayload; | ||
|
||
// Get the full Identification result from Fingerprint Server API and validate its authenticity | ||
const fingerprintResult = await getAndValidateFingerprintResult({ requestId, req }); | ||
if (!fingerprintResult.okay) { | ||
return NextResponse.json({ severity: 'error', message: fingerprintResult.error }, { status: 403 }); | ||
} | ||
|
||
// Get visitorId from the Server API Identification event | ||
const visitorId = fingerprintResult.data.products?.identification?.data?.visitorId; | ||
if (!visitorId) { | ||
return NextResponse.json({ severity: 'error', message: 'Visitor ID not found.' }, { status: 403 }); | ||
} | ||
|
||
const articleId = params.id; | ||
const article = ARTICLES.find((article) => article.id === articleId); | ||
if (!article) { | ||
return NextResponse.json({ severity: 'error', message: 'Article not found' }, { status: 404 }); | ||
} | ||
|
||
// Check how many articles were viewed by this visitor ID today | ||
const oldViewCount = await ArticleViewDbModel.count({ | ||
where: { visitorId, timestamp: { [Op.between]: getTodayDateRange() } }, | ||
}); | ||
|
||
// Check if this visitor has already viewed this specific article | ||
const viewedThisArticleBefore = await ArticleViewDbModel.findOne({ | ||
where: { | ||
visitorId, | ||
articleId, | ||
timestamp: { [Op.between]: getTodayDateRange() }, | ||
}, | ||
}); | ||
|
||
// If the visitor is trying to view a new article beyond the daily limit, return an error | ||
if (oldViewCount >= ARTICLE_VIEW_LIMIT && !viewedThisArticleBefore) { | ||
return NextResponse.json({ severity: 'error', message: PAYWALL_COPY.limitReached }, { status: 403 }); | ||
} | ||
|
||
// Otherwise, save the article view and return the article | ||
await saveArticleView(articleId, visitorId); | ||
const newViewCount = viewedThisArticleBefore ? oldViewCount : oldViewCount + 1; | ||
const articlesRemaining = ARTICLE_VIEW_LIMIT - newViewCount; | ||
return NextResponse.json({ | ||
severity: 'warning', | ||
message: articlesRemaining > 0 ? PAYWALL_COPY.nArticlesRemaining(articlesRemaining) : PAYWALL_COPY.lastArticle, | ||
article, | ||
articlesRemaining, | ||
articlesViewed: newViewCount, | ||
}); | ||
} | ||
|
||
/** | ||
* Saves article view into the database. If it already exists, we update its timestamp. | ||
*/ | ||
async function saveArticleView(articleId: string, visitorId: string) { | ||
const [view, created] = await ArticleViewDbModel.findOrCreate({ | ||
where: { articleId, visitorId, timestamp: { [Op.between]: getTodayDateRange() } }, | ||
defaults: { articleId, visitorId, timestamp: new Date() }, | ||
}); | ||
|
||
if (!created) { | ||
view.timestamp = new Date(); | ||
await view.save(); | ||
} | ||
return view; | ||
} |
1 change: 0 additions & 1 deletion
1
src/server/paywall/articles.ts → src/app/paywall/api/articles.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
2 changes: 1 addition & 1 deletion
2
src/server/paywall/database.ts → src/app/paywall/api/database.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes
File renamed without changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { ARTICLES } from '../../../api/articles'; | ||
import { USE_CASES } from '../../../../../client/components/common/content'; | ||
import { generateUseCaseMetadata } from '../../../../../client/components/common/seo'; | ||
import { Article } from '../Article'; | ||
|
||
export async function generateStaticParams() { | ||
return ARTICLES.map((article) => ({ id: article.id })); | ||
} | ||
|
||
export const metadata = generateUseCaseMetadata(USE_CASES.paywall); | ||
|
||
export default function ArticlePage({ params }: { params: { id: string } }) { | ||
return <Article articleId={params.id} embed={true} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { ARTICLES } from '../../api/articles'; | ||
import { USE_CASES } from '../../../../client/components/common/content'; | ||
import { generateUseCaseMetadata } from '../../../../client/components/common/seo'; | ||
import { Article } from './Article'; | ||
|
||
export async function generateStaticParams() { | ||
return ARTICLES.map((article) => ({ id: article.id })); | ||
} | ||
|
||
export const metadata = generateUseCaseMetadata(USE_CASES.paywall); | ||
|
||
export default function ArticlePage({ params }: { params: { id: string } }) { | ||
return <Article articleId={params.id} embed={false} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { USE_CASES } from '../../../client/components/common/content'; | ||
import { generateUseCaseMetadata } from '../../../client/components/common/seo'; | ||
import Paywall from '../Paywall'; | ||
|
||
export const metadata = generateUseCaseMetadata(USE_CASES.paywall); | ||
|
||
export default function PaywallPage() { | ||
return <Paywall embed={true} />; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { USE_CASES } from '../../client/components/common/content'; | ||
import { generateUseCaseMetadata } from '../../client/components/common/seo'; | ||
import Paywall from './Paywall'; | ||
|
||
export const metadata = generateUseCaseMetadata(USE_CASES.paywall); | ||
|
||
export default function PaywallPage() { | ||
return <Paywall embed={false} />; | ||
} |
File renamed without changes.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.