Verified contributors (#474)

* `isContributor`, `hideIsContributor` user fields and basic UI decoration on profile page

* Update verified contributor decoration on profile page

* Add contributors instructions

* update setting label

* Remove `isContributor` from DB, load contributors from file into memory

* fix merge error

---------

Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
SatsAllDay 2023-09-18 14:57:02 -04:00 committed by GitHub
parent 8ab58fff87
commit bc2363dfab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 55 additions and 5 deletions

View File

@ -1,3 +1,5 @@
import { readFile } from 'fs/promises'
import { join, resolve } from 'path'
import { GraphQLError } from 'graphql' import { GraphQLError } from 'graphql'
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor' import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
import { msatsToSats } from '../../lib/format' import { msatsToSats } from '../../lib/format'
@ -5,6 +7,20 @@ import { bioSchema, emailSchema, settingsSchema, ssValidate, userSchema } from '
import { getItem, updateItem, filterClause, createItem } from './item' import { getItem, updateItem, filterClause, createItem } from './item'
import { datePivot } from '../../lib/time' 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) { export function within (table, within) {
let interval = ' AND "' + table + '".created_at >= $1 - INTERVAL ' let interval = ' AND "' + table + '".created_at >= $1 - INTERVAL '
switch (within) { switch (within) {
@ -817,6 +833,16 @@ export default {
}) })
return !!subscription?.commentsSubscribedAt 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)
} }
} }
} }

View File

@ -25,7 +25,7 @@ export default gql`
noteInvites: Boolean!, noteJobIndicator: Boolean!, noteCowboyHat: Boolean!, hideInvoiceDesc: Boolean!, noteInvites: Boolean!, noteJobIndicator: Boolean!, noteCowboyHat: Boolean!, hideInvoiceDesc: Boolean!,
hideFromTopUsers: Boolean!, hideCowboyHat: Boolean!, clickToLoadImg: Boolean!, hideFromTopUsers: Boolean!, hideCowboyHat: Boolean!, clickToLoadImg: Boolean!,
wildWestMode: Boolean!, greeterMode: Boolean!, nostrPubkey: String, nostrRelays: [String!], hideBookmarks: 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! setPhoto(photoId: ID!): Int!
upsertBio(bio: String!): User! upsertBio(bio: String!): User!
setWalkthrough(tipPopover: Boolean, upvotePopover: Boolean): Boolean setWalkthrough(tipPopover: Boolean, upvotePopover: Boolean): Boolean
@ -92,6 +92,8 @@ export default gql`
greeterMode: Boolean! greeterMode: Boolean!
lastCheckedJobs: String lastCheckedJobs: String
authMethods: AuthMethods! authMethods: AuthMethods!
isContributor: Boolean!
hideIsContributor: Boolean!
meSubscriptionPosts: Boolean! meSubscriptionPosts: Boolean!
meSubscriptionComments: Boolean! meSubscriptionComments: Boolean!
} }

View File

@ -212,6 +212,7 @@ function HeaderHeader ({ user }) {
: <span>never</span>} : <span>never</span>}
</small> </small>
<small className='text-muted d-flex-inline'>longest cowboy streak: {user.maxStreak !== null ? user.maxStreak : 'none'}</small> <small className='text-muted d-flex-inline'>longest cowboy streak: {user.maxStreak !== null ? user.maxStreak : 'none'}</small>
{user.isContributor && <small className='text-muted'>🧑💻 verified stacker.news contributor</small>}
</div> </div>
</div> </div>
</div> </div>

4
contributors.txt Normal file
View File

@ -0,0 +1,4 @@
k00b
kr
ekzyis
WeAreAllSatoshi

View File

@ -36,6 +36,8 @@ export const ME = gql`
lastCheckedJobs lastCheckedJobs
hideWelcomeBanner hideWelcomeBanner
hideWalletBalance hideWalletBalance
isContributor
hideIsContributor
} }
}` }`
@ -57,6 +59,7 @@ export const SETTINGS_FIELDS = gql`
hideFromTopUsers hideFromTopUsers
hideCowboyHat hideCowboyHat
hideBookmarks hideBookmarks
hideIsContributor
clickToLoadImg clickToLoadImg
hideWalletBalance hideWalletBalance
nostrPubkey nostrPubkey
@ -88,14 +91,14 @@ mutation setSettings($tipDefault: Int!, $turboTipping: Boolean!, $fiatCurrency:
$noteInvites: Boolean!, $noteJobIndicator: Boolean!, $noteCowboyHat: Boolean!, $hideInvoiceDesc: Boolean!, $noteInvites: Boolean!, $noteJobIndicator: Boolean!, $noteCowboyHat: Boolean!, $hideInvoiceDesc: Boolean!,
$hideFromTopUsers: Boolean!, $hideCowboyHat: Boolean!, $clickToLoadImg: Boolean!, $hideFromTopUsers: Boolean!, $hideCowboyHat: Boolean!, $clickToLoadImg: Boolean!,
$wildWestMode: Boolean!, $greeterMode: Boolean!, $nostrPubkey: String, $nostrRelays: [String!], $hideBookmarks: 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, setSettings(tipDefault: $tipDefault, turboTipping: $turboTipping, fiatCurrency: $fiatCurrency,
noteItemSats: $noteItemSats, noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants, noteItemSats: $noteItemSats, noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants,
noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites, noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites,
noteJobIndicator: $noteJobIndicator, noteCowboyHat: $noteCowboyHat, hideInvoiceDesc: $hideInvoiceDesc, noteJobIndicator: $noteJobIndicator, noteCowboyHat: $noteCowboyHat, hideInvoiceDesc: $hideInvoiceDesc,
hideFromTopUsers: $hideFromTopUsers, hideCowboyHat: $hideCowboyHat, clickToLoadImg: $clickToLoadImg, hideFromTopUsers: $hideFromTopUsers, hideCowboyHat: $hideCowboyHat, clickToLoadImg: $clickToLoadImg,
wildWestMode: $wildWestMode, greeterMode: $greeterMode, nostrPubkey: $nostrPubkey, nostrRelays: $nostrRelays, hideBookmarks: $hideBookmarks, wildWestMode: $wildWestMode, greeterMode: $greeterMode, nostrPubkey: $nostrPubkey, nostrRelays: $nostrRelays, hideBookmarks: $hideBookmarks,
noteForwardedSats: $noteForwardedSats, hideWalletBalance: $hideWalletBalance) { noteForwardedSats: $noteForwardedSats, hideWalletBalance: $hideWalletBalance, hideIsContributor: $hideIsContributor) {
...SettingsFields ...SettingsFields
} }
} }
@ -150,6 +153,7 @@ export const USER_FIELDS = gql`
stacked stacked
since since
photoId photoId
isContributor
meSubscriptionPosts meSubscriptionPosts
meSubscriptionComments meSubscriptionComments
}` }`

View File

@ -224,7 +224,8 @@ export const settingsSchema = object({
).max(NOSTR_MAX_RELAY_NUM, ).max(NOSTR_MAX_RELAY_NUM,
({ max, value }) => `${Math.abs(max - value.length)} too many`), ({ max, value }) => `${Math.abs(max - value.length)} too many`),
hideBookmarks: boolean(), 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' const warningMessage = 'If I logout, even accidentally, I will never be able to access my account again'

View File

@ -23,6 +23,7 @@ import { useShowModal } from '../components/modal'
import { authErrorMessage } from '../components/login' import { authErrorMessage } from '../components/login'
import { NostrAuth } from '../components/nostr-auth' import { NostrAuth } from '../components/nostr-auth'
import { useToast } from '../components/toast' import { useToast } from '../components/toast'
import { useMe } from '../components/me'
export const getServerSideProps = getGetServerSideProps({ query: SETTINGS, authRequired: true }) export const getServerSideProps = getGetServerSideProps({ query: SETTINGS, authRequired: true })
@ -32,6 +33,7 @@ function bech32encode (hexString) {
export default function Settings ({ ssrData }) { export default function Settings ({ ssrData }) {
const toaster = useToast() const toaster = useToast()
const me = useMe()
const [setSettings] = useMutation(SET_SETTINGS, { const [setSettings] = useMutation(SET_SETTINGS, {
update (cache, { data: { setSettings } }) { update (cache, { data: { setSettings } }) {
cache.modify({ cache.modify({
@ -77,7 +79,8 @@ export default function Settings ({ ssrData }) {
nostrPubkey: settings?.nostrPubkey ? bech32encode(settings.nostrPubkey) : '', nostrPubkey: settings?.nostrPubkey ? bech32encode(settings.nostrPubkey) : '',
nostrRelays: settings?.nostrRelays?.length ? settings?.nostrRelays : [''], nostrRelays: settings?.nostrRelays?.length ? settings?.nostrRelays : [''],
hideBookmarks: settings?.hideBookmarks, hideBookmarks: settings?.hideBookmarks,
hideWalletBalance: settings?.hideWalletBalance hideWalletBalance: settings?.hideWalletBalance,
hideIsContributor: settings?.hideIsContributor
}} }}
schema={settingsSchema} schema={settingsSchema}
onSubmit={async ({ tipDefault, nostrPubkey, nostrRelays, ...values }) => { onSubmit={async ({ tipDefault, nostrPubkey, nostrRelays, ...values }) => {
@ -241,6 +244,12 @@ export default function Settings ({ ssrData }) {
name='clickToLoadImg' name='clickToLoadImg'
groupClassName='mb-0' groupClassName='mb-0'
/> />
{me.isContributor &&
<Checkbox
label={<>hide that I'm a stacker.news contributor</>}
name='hideIsContributor'
groupClassName='mb-0'
/>}
<Checkbox <Checkbox
label={<>hide my bookmarks from other stackers</>} label={<>hide my bookmarks from other stackers</>}
name='hideBookmarks' name='hideBookmarks'

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "hideIsContributor" BOOLEAN NOT NULL DEFAULT false;

View File

@ -90,6 +90,7 @@ model User {
followers UserSubscription[] @relation("follower") followers UserSubscription[] @relation("follower")
followees UserSubscription[] @relation("followee") followees UserSubscription[] @relation("followee")
hideWelcomeBanner Boolean @default(false) hideWelcomeBanner Boolean @default(false)
hideIsContributor Boolean @default(false)
@@index([createdAt], map: "users.created_at_index") @@index([createdAt], map: "users.created_at_index")
@@index([inviteId], map: "users.inviteId_index") @@index([inviteId], map: "users.inviteId_index")