diff --git a/api/resolvers/user.js b/api/resolvers/user.js index 91c13968..90e9c22c 100644 --- a/api/resolvers/user.js +++ b/api/resolvers/user.js @@ -1,3 +1,5 @@ +import { readFile } from 'fs/promises' +import { join, resolve } from 'path' import { GraphQLError } from 'graphql' import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor' import { msatsToSats } from '../../lib/format' @@ -5,6 +7,20 @@ import { bioSchema, emailSchema, settingsSchema, ssValidate, userSchema } from ' import { getItem, updateItem, filterClause, createItem } from './item' import { datePivot } from '../../lib/time' +const contributors = new Set() + +const loadContributors = async (set) => { + try { + const fileContent = await readFile(resolve(join(process.cwd(), 'contributors.txt')), 'utf-8') + fileContent.split('\n') + .map(line => line.trim()) + .filter(line => !!line) + .forEach(name => set.add(name)) + } catch (err) { + console.error('Error loading contributors', err) + } +} + export function within (table, within) { let interval = ' AND "' + table + '".created_at >= $1 - INTERVAL ' switch (within) { @@ -817,6 +833,16 @@ export default { }) return !!subscription?.commentsSubscribedAt + }, + isContributor: async (user, args, { me }) => { + // lazy init contributors only once + if (contributors.size === 0) { + await loadContributors(contributors) + } + if (me?.id === user.id) { + return contributors.has(user.name) + } + return !user.hideIsContributor && contributors.has(user.name) } } } diff --git a/api/typeDefs/user.js b/api/typeDefs/user.js index 95b102e6..3cb97a9f 100644 --- a/api/typeDefs/user.js +++ b/api/typeDefs/user.js @@ -25,7 +25,7 @@ export default gql` noteInvites: Boolean!, noteJobIndicator: Boolean!, noteCowboyHat: Boolean!, hideInvoiceDesc: Boolean!, hideFromTopUsers: Boolean!, hideCowboyHat: Boolean!, clickToLoadImg: Boolean!, wildWestMode: Boolean!, greeterMode: Boolean!, nostrPubkey: String, nostrRelays: [String!], hideBookmarks: Boolean!, - noteForwardedSats: Boolean!, hideWalletBalance: Boolean!): User + noteForwardedSats: Boolean!, hideWalletBalance: Boolean!, hideIsContributor: Boolean!): User setPhoto(photoId: ID!): Int! upsertBio(bio: String!): User! setWalkthrough(tipPopover: Boolean, upvotePopover: Boolean): Boolean @@ -92,6 +92,8 @@ export default gql` greeterMode: Boolean! lastCheckedJobs: String authMethods: AuthMethods! + isContributor: Boolean! + hideIsContributor: Boolean! meSubscriptionPosts: Boolean! meSubscriptionComments: Boolean! } diff --git a/components/user-header.js b/components/user-header.js index aaa0f1bf..6bd88da6 100644 --- a/components/user-header.js +++ b/components/user-header.js @@ -212,6 +212,7 @@ function HeaderHeader ({ user }) { : never} longest cowboy streak: {user.maxStreak !== null ? user.maxStreak : 'none'} + {user.isContributor && 🧑‍💻✅ verified stacker.news contributor} diff --git a/contributors.txt b/contributors.txt new file mode 100644 index 00000000..5f59b521 --- /dev/null +++ b/contributors.txt @@ -0,0 +1,4 @@ +k00b +kr +ekzyis +WeAreAllSatoshi diff --git a/fragments/users.js b/fragments/users.js index d0d17e54..6871ceac 100644 --- a/fragments/users.js +++ b/fragments/users.js @@ -36,6 +36,8 @@ export const ME = gql` lastCheckedJobs hideWelcomeBanner hideWalletBalance + isContributor + hideIsContributor } }` @@ -57,6 +59,7 @@ export const SETTINGS_FIELDS = gql` hideFromTopUsers hideCowboyHat hideBookmarks + hideIsContributor clickToLoadImg hideWalletBalance nostrPubkey @@ -88,14 +91,14 @@ mutation setSettings($tipDefault: Int!, $turboTipping: Boolean!, $fiatCurrency: $noteInvites: Boolean!, $noteJobIndicator: Boolean!, $noteCowboyHat: Boolean!, $hideInvoiceDesc: Boolean!, $hideFromTopUsers: Boolean!, $hideCowboyHat: Boolean!, $clickToLoadImg: Boolean!, $wildWestMode: Boolean!, $greeterMode: Boolean!, $nostrPubkey: String, $nostrRelays: [String!], $hideBookmarks: Boolean!, - $noteForwardedSats: Boolean!, $hideWalletBalance: Boolean!) { + $noteForwardedSats: Boolean!, $hideWalletBalance: Boolean!, $hideIsContributor: Boolean!) { setSettings(tipDefault: $tipDefault, turboTipping: $turboTipping, fiatCurrency: $fiatCurrency, noteItemSats: $noteItemSats, noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants, noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites, noteJobIndicator: $noteJobIndicator, noteCowboyHat: $noteCowboyHat, hideInvoiceDesc: $hideInvoiceDesc, hideFromTopUsers: $hideFromTopUsers, hideCowboyHat: $hideCowboyHat, clickToLoadImg: $clickToLoadImg, wildWestMode: $wildWestMode, greeterMode: $greeterMode, nostrPubkey: $nostrPubkey, nostrRelays: $nostrRelays, hideBookmarks: $hideBookmarks, - noteForwardedSats: $noteForwardedSats, hideWalletBalance: $hideWalletBalance) { + noteForwardedSats: $noteForwardedSats, hideWalletBalance: $hideWalletBalance, hideIsContributor: $hideIsContributor) { ...SettingsFields } } @@ -150,6 +153,7 @@ export const USER_FIELDS = gql` stacked since photoId + isContributor meSubscriptionPosts meSubscriptionComments }` diff --git a/lib/validate.js b/lib/validate.js index 0062f2f2..db5e8c28 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -224,7 +224,8 @@ export const settingsSchema = object({ ).max(NOSTR_MAX_RELAY_NUM, ({ max, value }) => `${Math.abs(max - value.length)} too many`), hideBookmarks: boolean(), - hideWalletBalance: boolean() + hideWalletBalance: boolean(), + hideIsContributor: boolean() }) const warningMessage = 'If I logout, even accidentally, I will never be able to access my account again' diff --git a/pages/settings.js b/pages/settings.js index eb8d8de2..87206198 100644 --- a/pages/settings.js +++ b/pages/settings.js @@ -23,6 +23,7 @@ import { useShowModal } from '../components/modal' import { authErrorMessage } from '../components/login' import { NostrAuth } from '../components/nostr-auth' import { useToast } from '../components/toast' +import { useMe } from '../components/me' export const getServerSideProps = getGetServerSideProps({ query: SETTINGS, authRequired: true }) @@ -32,6 +33,7 @@ function bech32encode (hexString) { export default function Settings ({ ssrData }) { const toaster = useToast() + const me = useMe() const [setSettings] = useMutation(SET_SETTINGS, { update (cache, { data: { setSettings } }) { cache.modify({ @@ -77,7 +79,8 @@ export default function Settings ({ ssrData }) { nostrPubkey: settings?.nostrPubkey ? bech32encode(settings.nostrPubkey) : '', nostrRelays: settings?.nostrRelays?.length ? settings?.nostrRelays : [''], hideBookmarks: settings?.hideBookmarks, - hideWalletBalance: settings?.hideWalletBalance + hideWalletBalance: settings?.hideWalletBalance, + hideIsContributor: settings?.hideIsContributor }} schema={settingsSchema} onSubmit={async ({ tipDefault, nostrPubkey, nostrRelays, ...values }) => { @@ -241,6 +244,12 @@ export default function Settings ({ ssrData }) { name='clickToLoadImg' groupClassName='mb-0' /> + {me.isContributor && + hide that I'm a stacker.news contributor} + name='hideIsContributor' + groupClassName='mb-0' + />} hide my bookmarks from other stackers} name='hideBookmarks' diff --git a/prisma/migrations/20230906010648_verified_contributors/migration.sql b/prisma/migrations/20230906010648_verified_contributors/migration.sql new file mode 100644 index 00000000..3ce4fb00 --- /dev/null +++ b/prisma/migrations/20230906010648_verified_contributors/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "users" ADD COLUMN "hideIsContributor" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c3fceb8d..5f6f3280 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -90,6 +90,7 @@ model User { followers UserSubscription[] @relation("follower") followees UserSubscription[] @relation("followee") hideWelcomeBanner Boolean @default(false) + hideIsContributor Boolean @default(false) @@index([createdAt], map: "users.created_at_index") @@index([inviteId], map: "users.inviteId_index")