Add Opt-in to Display Linked Accounts in Profile (#826)
* Add display linked accounts to settings * Apply suggestions from code review Co-authored-by: ekzyis <ek@stacker.news> * small styling enhancements --------- Co-authored-by: ekzyis <ek@stacker.news> Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com> Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
This commit is contained in:
parent
5c3c7fb185
commit
b3498fe277
@ -503,10 +503,15 @@ export default {
|
|||||||
throw new GraphQLError('no such account', { extensions: { code: 'BAD_INPUT' } })
|
throw new GraphQLError('no such account', { extensions: { code: 'BAD_INPUT' } })
|
||||||
}
|
}
|
||||||
await models.account.delete({ where: { id: account.id } })
|
await models.account.delete({ where: { id: account.id } })
|
||||||
|
if (authType === 'twitter') {
|
||||||
|
await models.user.update({ where: { id: me.id }, data: { hideTwitter: true, twitterId: null } })
|
||||||
|
} else {
|
||||||
|
await models.user.update({ where: { id: me.id }, data: { hideGithub: true, githubId: null } })
|
||||||
|
}
|
||||||
} else if (authType === 'lightning') {
|
} else if (authType === 'lightning') {
|
||||||
user = await models.user.update({ where: { id: me.id }, data: { pubkey: null } })
|
user = await models.user.update({ where: { id: me.id }, data: { pubkey: null } })
|
||||||
} else if (authType === 'nostr') {
|
} else if (authType === 'nostr') {
|
||||||
user = await models.user.update({ where: { id: me.id }, data: { nostrAuthPubkey: null } })
|
user = await models.user.update({ where: { id: me.id }, data: { hideNostr: true, nostrAuthPubkey: null } })
|
||||||
} else if (authType === 'email') {
|
} else if (authType === 'email') {
|
||||||
user = await models.user.update({ where: { id: me.id }, data: { email: null, emailVerified: null } })
|
user = await models.user.update({ where: { id: me.id }, data: { email: null, emailVerified: null } })
|
||||||
} else {
|
} else {
|
||||||
@ -811,6 +816,24 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
githubId: async (user, args, { me }) => {
|
||||||
|
if ((!me || me.id !== user.id) && user.hideGithub) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return user.githubId
|
||||||
|
},
|
||||||
|
twitterId: async (user, args, { models, me }) => {
|
||||||
|
if ((!me || me.id !== user.id) && user.hideTwitter) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return user.twitterId
|
||||||
|
},
|
||||||
|
nostrAuthPubkey: async (user, args, { models, me }) => {
|
||||||
|
if ((!me || me.id !== user.id) && user.hideNostr) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return user.nostrAuthPubkey
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,6 +60,9 @@ export default gql`
|
|||||||
greeterMode: Boolean!
|
greeterMode: Boolean!
|
||||||
hideBookmarks: Boolean!
|
hideBookmarks: Boolean!
|
||||||
hideCowboyHat: Boolean!
|
hideCowboyHat: Boolean!
|
||||||
|
hideGithub: Boolean!
|
||||||
|
hideNostr: Boolean!
|
||||||
|
hideTwitter: Boolean!
|
||||||
hideFromTopUsers: Boolean!
|
hideFromTopUsers: Boolean!
|
||||||
hideInvoiceDesc: Boolean!
|
hideInvoiceDesc: Boolean!
|
||||||
hideIsContributor: Boolean!
|
hideIsContributor: Boolean!
|
||||||
@ -119,6 +122,9 @@ export default gql`
|
|||||||
greeterMode: Boolean!
|
greeterMode: Boolean!
|
||||||
hideBookmarks: Boolean!
|
hideBookmarks: Boolean!
|
||||||
hideCowboyHat: Boolean!
|
hideCowboyHat: Boolean!
|
||||||
|
hideGithub: Boolean!
|
||||||
|
hideNostr: Boolean!
|
||||||
|
hideTwitter: Boolean!
|
||||||
hideFromTopUsers: Boolean!
|
hideFromTopUsers: Boolean!
|
||||||
hideInvoiceDesc: Boolean!
|
hideInvoiceDesc: Boolean!
|
||||||
hideIsContributor: Boolean!
|
hideIsContributor: Boolean!
|
||||||
@ -156,5 +162,8 @@ export default gql`
|
|||||||
streak: Int
|
streak: Int
|
||||||
maxStreak: Int
|
maxStreak: Int
|
||||||
isContributor: Boolean
|
isContributor: Boolean
|
||||||
|
githubId: String
|
||||||
|
twitterId: String
|
||||||
|
nostrAuthPubkey: String
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
@ -24,6 +24,10 @@ import CodeIcon from '../svgs/terminal-box-fill.svg'
|
|||||||
import MuteDropdownItem from './mute'
|
import MuteDropdownItem from './mute'
|
||||||
import copy from 'clipboard-copy'
|
import copy from 'clipboard-copy'
|
||||||
import { useToast } from './toast'
|
import { useToast } from './toast'
|
||||||
|
import { hexToBech32 } from '../lib/nostr'
|
||||||
|
import NostrIcon from '../svgs/nostr.svg'
|
||||||
|
import GithubIcon from '../svgs/github-fill.svg'
|
||||||
|
import TwitterIcon from '../svgs/twitter-fill.svg'
|
||||||
|
|
||||||
export default function UserHeader ({ user }) {
|
export default function UserHeader ({ user }) {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -180,8 +184,36 @@ function HeaderNym ({ user, isMe }) {
|
|||||||
: <NymView user={user} isMe={isMe} setEditting={setEditting} />
|
: <NymView user={user} isMe={isMe} setEditting={setEditting} />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SocialLink ({ name, id }) {
|
||||||
|
const className = `${styles.social} text-reset`
|
||||||
|
if (name === 'Nostr') {
|
||||||
|
const npub = hexToBech32(id)
|
||||||
|
return (
|
||||||
|
<Link className={className} target='_blank' href={`https://nostr.com/${npub}`} rel='noreferrer'>
|
||||||
|
<NostrIcon width={20} height={20} className='me-1' />
|
||||||
|
{npub.slice(0, 10)}...{npub.slice(-10)}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
} else if (name === 'Github') {
|
||||||
|
return (
|
||||||
|
<Link className={className} target='_blank' href={`https://github.com/${id}`} rel='noreferrer'>
|
||||||
|
<GithubIcon width={20} height={20} className='me-1' />
|
||||||
|
{id}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
} else if (name === 'Twitter') {
|
||||||
|
return (
|
||||||
|
<Link className={className} target='_blank' href={`https://twitter.com/${id}`} rel='noreferrer'>
|
||||||
|
<TwitterIcon width={20} height={20} className='me-1' />
|
||||||
|
@{id}
|
||||||
|
</Link>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function HeaderHeader ({ user }) {
|
function HeaderHeader ({ user }) {
|
||||||
const me = useMe()
|
const me = useMe()
|
||||||
|
|
||||||
const showModal = useShowModal()
|
const showModal = useShowModal()
|
||||||
const toaster = useToast()
|
const toaster = useToast()
|
||||||
|
|
||||||
@ -229,8 +261,24 @@ function HeaderHeader ({ user }) {
|
|||||||
? <Link href={`/items/${user.since}`} className='ms-1'>#{user.since}</Link>
|
? <Link href={`/items/${user.since}`} className='ms-1'>#{user.since}</Link>
|
||||||
: <span>never</span>}
|
: <span>never</span>}
|
||||||
</small>
|
</small>
|
||||||
{user.optional.maxStreak !== null && <small className='text-muted d-flex-inline'>longest cowboy streak: {user.optional.maxStreak}</small>}
|
{user.optional.maxStreak !== null &&
|
||||||
{user.optional.isContributor && <small className='text-muted d-flex align-items-center'><CodeIcon className='me-1' height={16} width={16} /> verified stacker.news contributor</small>}
|
<small className='text-muted d-flex-inline'>longest cowboy streak: {user.optional.maxStreak}</small>}
|
||||||
|
{user.optional.isContributor &&
|
||||||
|
<small className='text-muted d-flex align-items-center'>
|
||||||
|
<CodeIcon className='me-1' height={16} width={16} /> verified stacker.news contributor
|
||||||
|
</small>}
|
||||||
|
{user.optional.nostrAuthPubkey !== null && !me.privates?.hideNostr &&
|
||||||
|
<small className='text-muted d-flex-inline'>
|
||||||
|
<SocialLink name='Nostr' id={user.optional.nostrAuthPubkey} />
|
||||||
|
</small>}
|
||||||
|
{user.optional.githubId !== null && !me?.privates?.hideGithub &&
|
||||||
|
<small className='text-muted d-flex-inline'>
|
||||||
|
<SocialLink name='Github' id={user.optional.githubId} />
|
||||||
|
</small>}
|
||||||
|
{user.optional.twitterId !== null && !me?.privates?.hideTwitter &&
|
||||||
|
<small className='text-muted d-flex-inline'>
|
||||||
|
<SocialLink name='Twitter' id={user.optional.twitterId} />
|
||||||
|
</small>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,3 +31,9 @@
|
|||||||
font-size: 150%;
|
font-size: 150%;
|
||||||
line-height: 1.25rem;
|
line-height: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.social {
|
||||||
|
font-weight: bold;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
@ -15,6 +15,9 @@ export const ME = gql`
|
|||||||
greeterMode
|
greeterMode
|
||||||
hideCowboyHat
|
hideCowboyHat
|
||||||
hideFromTopUsers
|
hideFromTopUsers
|
||||||
|
hideGithub
|
||||||
|
hideNostr
|
||||||
|
hideTwitter
|
||||||
hideInvoiceDesc
|
hideInvoiceDesc
|
||||||
hideIsContributor
|
hideIsContributor
|
||||||
hideWalletBalance
|
hideWalletBalance
|
||||||
@ -47,6 +50,9 @@ export const ME = gql`
|
|||||||
isContributor
|
isContributor
|
||||||
stacked
|
stacked
|
||||||
streak
|
streak
|
||||||
|
githubId
|
||||||
|
nostrAuthPubkey
|
||||||
|
twitterId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
@ -73,6 +79,9 @@ export const SETTINGS_FIELDS = gql`
|
|||||||
hideFromTopUsers
|
hideFromTopUsers
|
||||||
hideCowboyHat
|
hideCowboyHat
|
||||||
hideBookmarks
|
hideBookmarks
|
||||||
|
hideGithub
|
||||||
|
hideNostr
|
||||||
|
hideTwitter
|
||||||
hideIsContributor
|
hideIsContributor
|
||||||
imgproxyOnly
|
imgproxyOnly
|
||||||
hideWalletBalance
|
hideWalletBalance
|
||||||
@ -182,6 +191,9 @@ export const USER_FIELDS = gql`
|
|||||||
streak
|
streak
|
||||||
maxStreak
|
maxStreak
|
||||||
isContributor
|
isContributor
|
||||||
|
githubId
|
||||||
|
nostrAuthPubkey
|
||||||
|
twitterId
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
@ -476,6 +476,9 @@ 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(),
|
||||||
|
hideGithub: boolean(),
|
||||||
|
hideNostr: boolean(),
|
||||||
|
hideTwitter: boolean(),
|
||||||
hideWalletBalance: boolean(),
|
hideWalletBalance: boolean(),
|
||||||
diagnostics: boolean(),
|
diagnostics: boolean(),
|
||||||
hideIsContributor: boolean()
|
hideIsContributor: boolean()
|
||||||
|
@ -12,6 +12,30 @@ import { NodeNextRequest } from 'next/dist/server/base-http/node'
|
|||||||
import { schnorr } from '@noble/curves/secp256k1'
|
import { schnorr } from '@noble/curves/secp256k1'
|
||||||
import { sendUserNotification } from '../../../api/webPush'
|
import { sendUserNotification } from '../../../api/webPush'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores userIds in user table
|
||||||
|
* @returns {Partial<import('next-auth').EventCallbacks>}
|
||||||
|
* */
|
||||||
|
function getEventCallbacks () {
|
||||||
|
return {
|
||||||
|
async linkAccount ({ user, profile, account }) {
|
||||||
|
if (account.provider === 'github') {
|
||||||
|
await prisma.user.update({ where: { id: user.id }, data: { githubId: profile.name } })
|
||||||
|
} else if (account.provider === 'twitter') {
|
||||||
|
await prisma.user.update({ where: { id: user.id }, data: { twitterId: profile.name } })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async signIn ({ user, profile, account, isNewUser }) {
|
||||||
|
if (account.provider === 'github') {
|
||||||
|
await prisma.user.update({ where: { id: user.id }, data: { githubId: profile.name } })
|
||||||
|
} else if (account.provider === 'twitter') {
|
||||||
|
await prisma.user.update({ where: { id: user.id }, data: { twitterId: profile.name } })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns {Partial<import('next-auth').CallbacksOptions>} */
|
||||||
function getCallbacks (req) {
|
function getCallbacks (req) {
|
||||||
return {
|
return {
|
||||||
/**
|
/**
|
||||||
@ -136,6 +160,7 @@ async function nostrEventAuth (event) {
|
|||||||
return { k1, pubkey }
|
return { k1, pubkey }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @type {import('next-auth/providers').Provider[]} */
|
||||||
const providers = [
|
const providers = [
|
||||||
CredentialsProvider({
|
CredentialsProvider({
|
||||||
id: 'lightning',
|
id: 'lightning',
|
||||||
@ -164,7 +189,7 @@ const providers = [
|
|||||||
url: 'https://github.com/login/oauth/authorize',
|
url: 'https://github.com/login/oauth/authorize',
|
||||||
params: { scope: '' }
|
params: { scope: '' }
|
||||||
},
|
},
|
||||||
profile: profile => {
|
profile (profile) {
|
||||||
return {
|
return {
|
||||||
id: profile.id,
|
id: profile.id,
|
||||||
name: profile.login
|
name: profile.login
|
||||||
@ -174,7 +199,7 @@ const providers = [
|
|||||||
TwitterProvider({
|
TwitterProvider({
|
||||||
clientId: process.env.TWITTER_ID,
|
clientId: process.env.TWITTER_ID,
|
||||||
clientSecret: process.env.TWITTER_SECRET,
|
clientSecret: process.env.TWITTER_SECRET,
|
||||||
profile: profile => {
|
profile (profile) {
|
||||||
return {
|
return {
|
||||||
id: profile.id,
|
id: profile.id,
|
||||||
name: profile.screen_name
|
name: profile.screen_name
|
||||||
@ -188,6 +213,7 @@ const providers = [
|
|||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/** @returns {import('next-auth').AuthOptions} */
|
||||||
export const getAuthOptions = req => ({
|
export const getAuthOptions = req => ({
|
||||||
callbacks: getCallbacks(req),
|
callbacks: getCallbacks(req),
|
||||||
providers,
|
providers,
|
||||||
@ -199,7 +225,8 @@ export const getAuthOptions = req => ({
|
|||||||
signIn: '/login',
|
signIn: '/login',
|
||||||
verifyRequest: '/email',
|
verifyRequest: '/email',
|
||||||
error: '/auth/error'
|
error: '/auth/error'
|
||||||
}
|
},
|
||||||
|
events: getEventCallbacks()
|
||||||
})
|
})
|
||||||
|
|
||||||
export default async (req, res) => {
|
export default async (req, res) => {
|
||||||
|
@ -3,7 +3,7 @@ import Alert from 'react-bootstrap/Alert'
|
|||||||
import Button from 'react-bootstrap/Button'
|
import Button from 'react-bootstrap/Button'
|
||||||
import InputGroup from 'react-bootstrap/InputGroup'
|
import InputGroup from 'react-bootstrap/InputGroup'
|
||||||
import { CenterLayout } from '../../components/layout'
|
import { CenterLayout } from '../../components/layout'
|
||||||
import { useState } from 'react'
|
import { useState, useMemo } from 'react'
|
||||||
import { gql, useMutation, useQuery } from '@apollo/client'
|
import { gql, useMutation, useQuery } from '@apollo/client'
|
||||||
import { getGetServerSideProps } from '../../api/ssrApollo'
|
import { getGetServerSideProps } from '../../api/ssrApollo'
|
||||||
import LoginButton from '../../components/login-button'
|
import LoginButton from '../../components/login-button'
|
||||||
@ -52,7 +52,7 @@ export default function Settings ({ ssrData }) {
|
|||||||
const logger = useLogger()
|
const logger = useLogger()
|
||||||
|
|
||||||
const { data } = useQuery(SETTINGS)
|
const { data } = useQuery(SETTINGS)
|
||||||
const { settings: { privates: settings } } = data || ssrData
|
const { settings: { privates: settings } } = useMemo(() => data ?? ssrData, [data, ssrData])
|
||||||
if (!data && !ssrData) return <PageLoading />
|
if (!data && !ssrData) return <PageLoading />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -79,6 +79,9 @@ export default function Settings ({ ssrData }) {
|
|||||||
autoDropBolt11s: settings?.autoDropBolt11s,
|
autoDropBolt11s: settings?.autoDropBolt11s,
|
||||||
hideFromTopUsers: settings?.hideFromTopUsers,
|
hideFromTopUsers: settings?.hideFromTopUsers,
|
||||||
hideCowboyHat: settings?.hideCowboyHat,
|
hideCowboyHat: settings?.hideCowboyHat,
|
||||||
|
hideGithub: settings?.hideGithub,
|
||||||
|
hideNostr: settings?.hideNostr,
|
||||||
|
hideTwitter: settings?.hideTwitter,
|
||||||
imgproxyOnly: settings?.imgproxyOnly,
|
imgproxyOnly: settings?.imgproxyOnly,
|
||||||
wildWestMode: settings?.wildWestMode,
|
wildWestMode: settings?.wildWestMode,
|
||||||
greeterMode: settings?.greeterMode,
|
greeterMode: settings?.greeterMode,
|
||||||
@ -284,6 +287,66 @@ export default function Settings ({ ssrData }) {
|
|||||||
name='hideBookmarks'
|
name='hideBookmarks'
|
||||||
groupClassName='mb-0'
|
groupClassName='mb-0'
|
||||||
/>
|
/>
|
||||||
|
<Checkbox
|
||||||
|
disabled={me.optional.githubId === null}
|
||||||
|
label={
|
||||||
|
<div className='d-flex align-items-center'>hide my linked github profile
|
||||||
|
<Info>
|
||||||
|
<ul className='fw-bold'>
|
||||||
|
<li>Linked accounts are hidden from your profile by default</li>
|
||||||
|
<li>uncheck this to display your github on your profile</li>
|
||||||
|
{me.optional.githubId === null &&
|
||||||
|
<div className='my-2'>
|
||||||
|
<li><i>You don't seem to have a linked github account</i></li>
|
||||||
|
<ul><li>If this is wrong, try unlinking/relinking</li></ul>
|
||||||
|
</div>}
|
||||||
|
</ul>
|
||||||
|
</Info>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
name='hideGithub'
|
||||||
|
groupClassName='mb-0'
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
disabled={me.optional.nostrAuthPubkey === null}
|
||||||
|
label={
|
||||||
|
<div className='d-flex align-items-center'>hide my linked nostr profile
|
||||||
|
<Info>
|
||||||
|
<ul className='fw-bold'>
|
||||||
|
<li>Linked accounts are hidden from your profile by default</li>
|
||||||
|
<li>Uncheck this to display your npub on your profile</li>
|
||||||
|
{me.optional.nostrAuthPubkey === null &&
|
||||||
|
<div className='my-2'>
|
||||||
|
<li>You don't seem to have a linked nostr account</li>
|
||||||
|
<ul><li>If this is wrong, try unlinking/relinking</li></ul>
|
||||||
|
</div>}
|
||||||
|
</ul>
|
||||||
|
</Info>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
name='hideNostr'
|
||||||
|
groupClassName='mb-0'
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
disabled={me.optional.twitterId === null}
|
||||||
|
label={
|
||||||
|
<div className='d-flex align-items-center'>hide my linked twitter profile
|
||||||
|
<Info>
|
||||||
|
<ul className='fw-bold'>
|
||||||
|
<li>Linked accounts are hidden from your profile by default</li>
|
||||||
|
<li>Uncheck this to display your twitter on your profile</li>
|
||||||
|
{me.optional.twitterId === null &&
|
||||||
|
<div className='my-2'>
|
||||||
|
<i>You don't seem to have a linked twitter account</i>
|
||||||
|
<ul><li>If this is wrong, try unlinking/relinking</li></ul>
|
||||||
|
</div>}
|
||||||
|
</ul>
|
||||||
|
</Info>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
name='hideTwitter'
|
||||||
|
groupClassName='mb-0'
|
||||||
|
/>
|
||||||
{me.optional?.isContributor &&
|
{me.optional?.isContributor &&
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={<>hide that I'm a stacker.news contributor</>}
|
label={<>hide that I'm a stacker.news contributor</>}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "hideGithub" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
ADD COLUMN "hideNostr" BOOLEAN NOT NULL DEFAULT true,
|
||||||
|
ADD COLUMN "hideTwitter" BOOLEAN NOT NULL DEFAULT true;
|
@ -0,0 +1,3 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "githubId" TEXT,
|
||||||
|
ADD COLUMN "twitterId" TEXT;
|
@ -92,6 +92,11 @@ model User {
|
|||||||
Session Session[]
|
Session Session[]
|
||||||
itemForwards ItemForward[]
|
itemForwards ItemForward[]
|
||||||
hideBookmarks Boolean @default(false)
|
hideBookmarks Boolean @default(false)
|
||||||
|
hideGithub Boolean @default(true)
|
||||||
|
hideNostr Boolean @default(true)
|
||||||
|
hideTwitter Boolean @default(true)
|
||||||
|
githubId String?
|
||||||
|
twitterId String?
|
||||||
followers UserSubscription[] @relation("follower")
|
followers UserSubscription[] @relation("follower")
|
||||||
followees UserSubscription[] @relation("followee")
|
followees UserSubscription[] @relation("followee")
|
||||||
hideWelcomeBanner Boolean @default(false)
|
hideWelcomeBanner Boolean @default(false)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user