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:
parent
8ab58fff87
commit
bc2363dfab
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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!
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
k00b
|
||||||
|
kr
|
||||||
|
ekzyis
|
||||||
|
WeAreAllSatoshi
|
|
@ -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
|
||||||
}`
|
}`
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "hideIsContributor" BOOLEAN NOT NULL DEFAULT false;
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in New Issue