Skip to content

Commit

Permalink
feat(nomad-badges): Digital Nomad program
Browse files Browse the repository at this point in the history
  • Loading branch information
Ubuntu authored and Ubuntu committed Nov 30, 2023
1 parent d26b7d2 commit 8423e3c
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 0 deletions.
11 changes: 11 additions & 0 deletions bin/check-nomad-badge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env -S node --trace-warnings --loader ts-node/esm

import { checkNomadBadge } from "../lib/check-nomad-badge.js";

async function main() {
const args = process.argv.slice(2);

await checkNomadBadge();
}

main().catch((err) => console.error(new Date(), "ERROR:", err));
26 changes: 26 additions & 0 deletions handlers/check-nomad-badge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Context, APIGatewayProxyResult, APIGatewayEvent } from "aws-lambda";
import { checkNomadBadge } from "../lib/check-nomad-badge.js";

export const handler = async (
event: APIGatewayEvent & {
campaignTagIds?: number[];
campaignBegins?: Date | string;
campaignEnds?: Date | string;
},
context: Context
): Promise<APIGatewayProxyResult> => {
console.log(`Event: %o`, event);
console.log(`Context: %o`, context);

const { campaignTagIds, campaignBegins, campaignEnds } = event;
const retBadges = await checkNomadBadge({
campaignTagIds,
campaignBegins,
campaignEnds,
});

return {
statusCode: 200,
body: JSON.stringify({ message: "done.", retBadges }),
};
};
111 changes: 111 additions & 0 deletions lib/check-nomad-badge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { sql, sqlSIW } from "../lib/db.js";

const MATTERS_CAMPAIGN_BEGINS =
process.env.MATTERS_CAMPAIGN_BEGINS || "2023-11-14T16:00:00.000Z"; // 00:00 in UTC+8
const MATTERS_CAMPAIGN_ENDS =
process.env.MATTERS_CAMPAIGN_ENDS || "2024-01-14T15:59:59.999Z";

const ARRAY_TYPE = 1009; // Postgres internal value shouldn't be here;

export async function checkNomadBadge({
campaignTagIds = [157201], // https://matters.town/tags/157201-nomadmatters?type=latest
campaignBegins,
campaignEnds,
}: {
campaignTagIds?: number[];
campaignBegins?: Date | string;
campaignEnds?: Date | string;
} = {}): Promise<any> {
// const [{ version, now }] = await sql` SELECT VERSION(), NOW() `; console.log("pgres:", { version, now });

if (!campaignBegins) campaignBegins = new Date(MATTERS_CAMPAIGN_BEGINS);
if (!campaignEnds) campaignEnds = new Date(MATTERS_CAMPAIGN_ENDS);
// const nomadTagId = 157201; // https://matters.town/tags/157201-nomadmatters?type=latest

const referrals = await sql`-- select all referred users
SELECT extra->>'referralCode' AS "referralCode", COUNT(*) ::int
FROM public.user
WHERE created_at BETWEEN ${campaignBegins} AND ${campaignEnds}
AND extra->'referralCode' IS NOT NULL
GROUP BY 1
HAVING COUNT(*) >=5
ORDER BY count DESC -- LIMIT 13 `;
console.log("log user by referrals:", referrals);
const referralsMap = new Map<string, number>(
referrals.map(({ referralCode, count }) => [referralCode, count])
);

const allApplicants = await sql`-- query all campaign applicants
SELECT * FROM (
SELECT DISTINCT ON (author_id)
user_name, display_name, author_id ::int, article.id ::int, article.title, article.created_at,
(ub.extra->'level')::int AS level -- current nomad badge level
FROM article_tag
LEFT JOIN public.article ON article_id=article.id
LEFT JOIN public.user u ON author_id=u.id
LEFT JOIN public.user_badge ub ON user_id=u.id AND ub.type='nomad'
WHERE created_at BETWEEN ${campaignBegins} AND ${campaignEnds}
AND tag_id =ANY(${campaignTagIds})
ORDER BY author_id, article.created_at DESC
) t
ORDER BY created_at DESC ; `;

console.log("consider all applicants:", allApplicants);

const newNomadBadges: Array<{
userId: string | number;
type: string;
extra: any;
}> = [];
allApplicants.forEach(({ userName, authorId, level: currentLevel }) => {
const referralCount = referralsMap.get(userName) || 0;

let level: number;
if (referralCount >= 20) level = 4;
else if (referralCount >= 10) level = 3;
else if (referralCount >= 5) level = 2;
else level = 1;

if (currentLevel >= level) {
// skip if there's existing level override
return;
}

newNomadBadges.push({
userId: authorId,
type: "nomad",
extra: { level },
});
});

console.log("consider new nomad badges:", newNomadBadges);
if (newNomadBadges.length === 0) return;

const retBadges = await sqlSIW`-- upsert all new badges
INSERT INTO public.user_badge AS ub(user_id, type, extra)
SELECT * FROM UNNEST(
${sqlSIW.array(
newNomadBadges.map(({ userId }) => userId),
ARRAY_TYPE
)} ::int[],
${sqlSIW.array(
newNomadBadges.map(({ type }) => type),
ARRAY_TYPE
)} ::text[],
${sqlSIW.array(
newNomadBadges.map(({ extra }) => JSON.stringify(extra)),
ARRAY_TYPE
)} ::jsonb[]
)
ON CONFLICT (user_id, type)
DO UPDATE
SET extra=((COALESCE(ub.extra, '{}' ::jsonb) - ${[
"removeKeys",
]} ::text[]) || EXCLUDED.extra)
RETURNING * ;
`;

console.log("upsert'ed new badges:", retBadges);

return retBadges;
}

0 comments on commit 8423e3c

Please sign in to comment.