-
-
Notifications
You must be signed in to change notification settings - Fork 115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
new referral scheme #1255
new referral scheme #1255
Changes from 4 commits
b2941bd
8bb868e
576352d
5800520
970caa9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,77 @@ | ||
import { NextResponse } from 'next/server' | ||
import { NextResponse, URLPattern } from 'next/server' | ||
|
||
const referrerRegex = /(\/.*)?\/r\/([\w_]+)/ | ||
const referrerPattern = new URLPattern({ pathname: ':pathname(*)/r/:referrer([\\w_]+)' }) | ||
const itemPattern = new URLPattern({ pathname: '/items/:id(\\d+)' }) | ||
const profilePattern = new URLPattern({ pathname: '/:name([\\w_]+){/:type(\\w+)}?' }) | ||
huumn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const territoryPattern = new URLPattern({ pathname: '/~:name([\\w_]+){/*}?' }) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These are waayyyyyyyyyy better than using regex for urls. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mhh, interesting! I wish we could use this in the browser, too, but it's not supported by every major browser yet according to MDN: It's also not implemented in node yet, see nodejs/node#40844. So cool that |
||
// key for /r/... link referrers | ||
const SN_REFERRER = 'sn_referrer' | ||
// we use this to hold /r/... referrers through the redirect | ||
const SN_REFERRER_NONCE = 'sn_referrer_nonce' | ||
|
||
// we store the referrers in cookies for a future signup event | ||
// we pass the referrers in the request headers so we can use them in referral rewards for logged in stackers | ||
function referrerMiddleware (request) { | ||
const m = referrerRegex.exec(request.nextUrl.pathname) | ||
if (referrerPattern.test(request.url)) { | ||
const { pathname, referrer } = referrerPattern.exec(request.url).pathname.groups | ||
|
||
const url = new URL(m[1] || '/', request.url) | ||
url.search = request.nextUrl.search | ||
url.hash = request.nextUrl.hash | ||
const url = new URL(pathname || '/', request.url) | ||
url.search = request.nextUrl.search | ||
url.hash = request.nextUrl.hash | ||
|
||
const resp = NextResponse.redirect(url) | ||
resp.cookies.set('sn_referrer', m[2]) | ||
return resp | ||
const response = NextResponse.redirect(url) | ||
// explicit referrers are set for a day and can only be overriden by other explicit | ||
// referrers. Content referrers do not override explicit referrers because | ||
// explicit referees might click around before signing up. | ||
response.cookies.set(SN_REFERRER, referrer, { maxAge: 60 * 60 * 24 }) | ||
// store the explicit referrer for one page load | ||
// this allows us to attribute both explicit and implicit referrers after the redirect | ||
// e.g. items/<num>/r/<referrer> links should attribute both the item op and the referrer | ||
// without this the /r/<referrer> would be lost on redirect | ||
response.cookies.set(SN_REFERRER_NONCE, referrer, { maxAge: 1 }) | ||
return response | ||
} | ||
|
||
let contentReferrer | ||
if (itemPattern.test(request.url)) { | ||
if (request.nextUrl.searchParams.has('commentId')) { | ||
contentReferrer = `comment-${request.nextUrl.searchParams.get('commentId')}` | ||
} else { | ||
const { id } = itemPattern.exec(request.url).pathname.groups | ||
contentReferrer = `item-${id}` | ||
} | ||
} else if (profilePattern.test(request.url)) { | ||
const { name } = profilePattern.exec(request.url).pathname.groups | ||
contentReferrer = `profile-${name}` | ||
} else if (territoryPattern.test(request.url)) { | ||
const { name } = territoryPattern.exec(request.url).pathname.groups | ||
contentReferrer = `territory-${name}` | ||
} | ||
|
||
// pass the referrers to SSR in the request headers | ||
const requestHeaders = new Headers(request.headers) | ||
const referrers = [request.cookies.get(SN_REFERRER_NONCE)?.value, contentReferrer].filter(Boolean) | ||
if (referrers.length) { | ||
requestHeaders.set('x-stacker-news-referrer', referrers.join('; ')) | ||
} | ||
|
||
const response = NextResponse.next({ | ||
request: { | ||
headers: requestHeaders | ||
} | ||
}) | ||
|
||
// if we don't already have an explicit referrer, give them the content referrer as one | ||
if (!request.cookies.has(SN_REFERRER) && contentReferrer) { | ||
response.cookies.set(SN_REFERRER, contentReferrer, { maxAge: 60 * 60 * 24 }) | ||
} | ||
|
||
return response | ||
} | ||
|
||
export function middleware (request) { | ||
let resp = NextResponse.next() | ||
if (referrerRegex.test(request.nextUrl.pathname)) { | ||
resp = referrerMiddleware(request) | ||
} | ||
const resp = referrerMiddleware(request) | ||
|
||
const isDev = process.env.NODE_ENV === 'development' | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
-- CreateEnum | ||
CREATE TYPE "OneDayReferralType" AS ENUM ('REFERRAL', 'ITEM', 'COMMENT', 'PROFILE', 'TERRITORY'); | ||
huumn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
-- CreateTable | ||
CREATE TABLE "OneDayReferral" ( | ||
"id" SERIAL NOT NULL, | ||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
"referrerId" INTEGER NOT NULL, | ||
"refereeId" INTEGER NOT NULL, | ||
"type" "OneDayReferralType" NOT NULL, | ||
"typeId" TEXT NOT NULL, | ||
|
||
CONSTRAINT "OneDayReferral_pkey" PRIMARY KEY ("id") | ||
); | ||
|
||
-- CreateIndex | ||
CREATE INDEX "OneDayReferral_created_at_idx" ON "OneDayReferral"("created_at"); | ||
|
||
-- CreateIndex | ||
CREATE INDEX "OneDayReferral_referrerId_idx" ON "OneDayReferral"("referrerId"); | ||
|
||
-- CreateIndex | ||
CREATE INDEX "OneDayReferral_refereeId_idx" ON "OneDayReferral"("refereeId"); | ||
|
||
-- CreateIndex | ||
CREATE INDEX "OneDayReferral_type_typeId_idx" ON "OneDayReferral"("type", "typeId"); | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "OneDayReferral" ADD CONSTRAINT "OneDayReferral_referrerId_fkey" FOREIGN KEY ("referrerId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||
|
||
-- AddForeignKey | ||
ALTER TABLE "OneDayReferral" ADD CONSTRAINT "OneDayReferral_refereeId_fkey" FOREIGN KEY ("refereeId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was throwing errors in development.