Compare commits
19 Commits
a669ec832b
...
eaa15b3b43
Author | SHA1 | Date | |
---|---|---|---|
|
eaa15b3b43 | ||
|
9b4f1643a7 | ||
|
411c8317b0 | ||
|
c39bfcecb6 | ||
|
f20ebad772 | ||
|
3ff03960eb | ||
|
9b08988402 | ||
|
b54268a88f | ||
|
e7eece744f | ||
|
54d3b11fbc | ||
|
06b877e3d3 | ||
|
5ff1334722 | ||
|
5e2185c18f | ||
|
bce4053b72 | ||
|
2d619438b1 | ||
|
5164258df8 | ||
|
f96b3bf19a | ||
|
a83783f008 | ||
|
dbbd9477fd |
@ -276,11 +276,15 @@ export default {
|
|||||||
// if nym, items must contain nym
|
// if nym, items must contain nym
|
||||||
if (nym) {
|
if (nym) {
|
||||||
filters.push({ wildcard: { 'user.name': `*${nym.slice(1).toLowerCase()}*` } })
|
filters.push({ wildcard: { 'user.name': `*${nym.slice(1).toLowerCase()}*` } })
|
||||||
|
// push same requirement to termQueries to avoid empty should clause
|
||||||
|
termQueries.push({ wildcard: { 'user.name': `*${nym.slice(1).toLowerCase()}*` } })
|
||||||
}
|
}
|
||||||
|
|
||||||
// if territory, item must be from territory
|
// if territory, item must be from territory
|
||||||
if (territory) {
|
if (territory) {
|
||||||
filters.push({ match: { 'sub.name': territory.slice(1) } })
|
filters.push({ match: { 'sub.name': territory.slice(1) } })
|
||||||
|
// push same requirement to termQueries to avoid empty should clause
|
||||||
|
termQueries.push({ match: { 'sub.name': territory.slice(1) } })
|
||||||
}
|
}
|
||||||
|
|
||||||
// if quoted phrases, items must contain entire phrase
|
// if quoted phrases, items must contain entire phrase
|
||||||
|
@ -430,6 +430,10 @@ const resolvers = {
|
|||||||
lte: to ? new Date(Number(to)) : undefined
|
lte: to ? new Date(Number(to)) : undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
invoice: true,
|
||||||
|
withdrawal: true
|
||||||
|
},
|
||||||
orderBy: [
|
orderBy: [
|
||||||
{ createdAt: 'desc' },
|
{ createdAt: 'desc' },
|
||||||
{ id: 'desc' }
|
{ id: 'desc' }
|
||||||
@ -445,6 +449,10 @@ const resolvers = {
|
|||||||
lte: decodedCursor.time
|
lte: decodedCursor.time
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
invoice: true,
|
||||||
|
withdrawal: true
|
||||||
|
},
|
||||||
orderBy: [
|
orderBy: [
|
||||||
{ createdAt: 'desc' },
|
{ createdAt: 'desc' },
|
||||||
{ id: 'desc' }
|
{ id: 'desc' }
|
||||||
@ -745,11 +753,42 @@ const resolvers = {
|
|||||||
return item
|
return item
|
||||||
},
|
},
|
||||||
sats: fact => msatsToSatsDecimal(fact.msats)
|
sats: fact => msatsToSatsDecimal(fact.msats)
|
||||||
|
},
|
||||||
|
|
||||||
|
WalletLogEntry: {
|
||||||
|
context: async ({ level, context, invoice, withdrawal }, args, { models }) => {
|
||||||
|
const isError = ['error', 'warn'].includes(level.toLowerCase())
|
||||||
|
|
||||||
|
if (withdrawal) {
|
||||||
|
return {
|
||||||
|
...await logContextFromBolt11(withdrawal.bolt11),
|
||||||
|
...(withdrawal.preimage ? { preimage: withdrawal.preimage } : {}),
|
||||||
|
...(isError ? { max_fee: formatMsats(withdrawal.msatsFeePaying) } : {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX never return invoice as context because it might leak sensitive sender details
|
||||||
|
// if (invoice) { ... }
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default injectResolvers(resolvers)
|
export default injectResolvers(resolvers)
|
||||||
|
|
||||||
|
const logContextFromBolt11 = async (bolt11) => {
|
||||||
|
const decoded = await parsePaymentRequest({ request: bolt11 })
|
||||||
|
return {
|
||||||
|
bolt11,
|
||||||
|
amount: formatMsats(decoded.mtokens),
|
||||||
|
payment_hash: decoded.id,
|
||||||
|
created_at: decoded.created_at,
|
||||||
|
expires_at: decoded.expires_at,
|
||||||
|
description: decoded.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const walletLogger = ({ wallet, models }) => {
|
export const walletLogger = ({ wallet, models }) => {
|
||||||
// no-op logger if wallet is not provided
|
// no-op logger if wallet is not provided
|
||||||
if (!wallet) {
|
if (!wallet) {
|
||||||
@ -762,23 +801,17 @@ export const walletLogger = ({ wallet, models }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// server implementation of wallet logger interface on client
|
// server implementation of wallet logger interface on client
|
||||||
const log = (level) => async (message, context = {}) => {
|
const log = (level) => async (message, ctx = {}) => {
|
||||||
try {
|
try {
|
||||||
if (context?.bolt11) {
|
let { invoiceId, withdrawalId, ...context } = ctx
|
||||||
|
|
||||||
|
if (context.bolt11) {
|
||||||
// automatically populate context from bolt11 to avoid duplicating this code
|
// automatically populate context from bolt11 to avoid duplicating this code
|
||||||
const decoded = await parsePaymentRequest({ request: context.bolt11 })
|
|
||||||
context = {
|
context = {
|
||||||
...context,
|
...context,
|
||||||
amount: formatMsats(decoded.mtokens),
|
...await logContextFromBolt11(context.bolt11)
|
||||||
payment_hash: decoded.id,
|
|
||||||
created_at: decoded.created_at,
|
|
||||||
expires_at: decoded.expires_at,
|
|
||||||
description: decoded.description,
|
|
||||||
// payments should affect wallet status
|
|
||||||
status: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.recv = true
|
|
||||||
|
|
||||||
await models.walletLog.create({
|
await models.walletLog.create({
|
||||||
data: {
|
data: {
|
||||||
@ -786,7 +819,9 @@ export const walletLogger = ({ wallet, models }) => {
|
|||||||
wallet: wallet.type,
|
wallet: wallet.type,
|
||||||
level,
|
level,
|
||||||
message,
|
message,
|
||||||
context
|
context,
|
||||||
|
invoiceId,
|
||||||
|
withdrawalId
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -15,6 +15,7 @@ import { getServerSession } from 'next-auth/next'
|
|||||||
import { getAuthOptions } from '@/pages/api/auth/[...nextauth]'
|
import { getAuthOptions } from '@/pages/api/auth/[...nextauth]'
|
||||||
import { NOFOLLOW_LIMIT } from '@/lib/constants'
|
import { NOFOLLOW_LIMIT } from '@/lib/constants'
|
||||||
import { satsToMsats } from '@/lib/format'
|
import { satsToMsats } from '@/lib/format'
|
||||||
|
import { MULTI_AUTH_ANON, MULTI_AUTH_LIST } from '@/lib/auth'
|
||||||
|
|
||||||
export default async function getSSRApolloClient ({ req, res, me = null }) {
|
export default async function getSSRApolloClient ({ req, res, me = null }) {
|
||||||
const session = req && await getServerSession(req, res, getAuthOptions(req))
|
const session = req && await getServerSession(req, res, getAuthOptions(req))
|
||||||
@ -155,7 +156,7 @@ export function getGetServerSideProps (
|
|||||||
|
|
||||||
// required to redirect to /signup on page reload
|
// required to redirect to /signup on page reload
|
||||||
// if we switched to anon and authentication is required
|
// if we switched to anon and authentication is required
|
||||||
if (req.cookies['multi_auth.user-id'] === 'anonymous') {
|
if (req.cookies[MULTI_AUTH_LIST] === MULTI_AUTH_ANON) {
|
||||||
me = null
|
me = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
14
awards.csv
14
awards.csv
@ -191,3 +191,17 @@ ed-kung,pr,#1952,#1951,easy,,,,100k,simplestacker@getalby.com,2025-03-10
|
|||||||
ed-kung,issue,#1952,#1951,easy,,,,10k,simplestacker@getalby.com,2025-03-10
|
ed-kung,issue,#1952,#1951,easy,,,,10k,simplestacker@getalby.com,2025-03-10
|
||||||
Scroogey-SN,pr,#1973,#1959,good-first-issue,,,,20k,Scroogey@coinos.io,???
|
Scroogey-SN,pr,#1973,#1959,good-first-issue,,,,20k,Scroogey@coinos.io,???
|
||||||
benthecarman,issue,#1953,#1950,good-first-issue,,,,2k,???,???
|
benthecarman,issue,#1953,#1950,good-first-issue,,,,2k,???,???
|
||||||
|
ed-kung,pr,#2012,#2004,easy,,,,100k,simplestacker@getalby.com,???
|
||||||
|
ed-kung,issue,#2012,#2004,easy,,,,10k,simplestacker@getalby.com,???
|
||||||
|
ed-kung,pr,#1993,#1982,good-first-issue,,,,20k,simplestacker@getalby.com,???
|
||||||
|
rideandslide,issue,#1993,#1982,good-first-issue,,,,2k,???,???
|
||||||
|
ed-kung,pr,#1972,#1254,good-first-issue,,,,20k,simplestacker@getalby.com,???
|
||||||
|
SatsAllDay,issue,#1972,#1254,good-first-issue,,,,2k,weareallsatoshi@getalby.com,???
|
||||||
|
ed-kung,pr,#1962,#1343,good-first-issue,,,,20k,simplestacker@getalby.com,???
|
||||||
|
ed-kung,pr,#1962,#1217,good-first-issue,,,,20k,simplestacker@getalby.com,???
|
||||||
|
ed-kung,pr,#1962,#866,easy,,,,100k,simplestacker@getalby.com,???
|
||||||
|
felipebueno,issue,#1962,#866,easy,,,,10k,felipebueno@blink.sv,???
|
||||||
|
cointastical,issue,#1962,#1217,good-first-issue,,,,2k,cointastical@stacker.news,???
|
||||||
|
Scroogey-SN,pr,#1975,#1964,good-first-issue,,,,20k,Scroogey@coinos.io,???
|
||||||
|
rideandslide,issue,#1986,#1985,good-first-issue,,,,2k,???,???
|
||||||
|
kristapsk,issue,#1976,#841,good-first-issue,,,,2k,???,???
|
||||||
|
|
@ -9,6 +9,7 @@ import { UserListRow } from '@/components/user-list'
|
|||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import AddIcon from '@/svgs/add-fill.svg'
|
import AddIcon from '@/svgs/add-fill.svg'
|
||||||
import { MultiAuthErrorBanner } from '@/components/banners'
|
import { MultiAuthErrorBanner } from '@/components/banners'
|
||||||
|
import { cookieOptions, MULTI_AUTH_ANON, MULTI_AUTH_LIST, MULTI_AUTH_POINTER } from '@/lib/auth'
|
||||||
|
|
||||||
const AccountContext = createContext()
|
const AccountContext = createContext()
|
||||||
|
|
||||||
@ -16,31 +17,19 @@ const CHECK_ERRORS_INTERVAL_MS = 5_000
|
|||||||
|
|
||||||
const b64Decode = str => Buffer.from(str, 'base64').toString('utf-8')
|
const b64Decode = str => Buffer.from(str, 'base64').toString('utf-8')
|
||||||
|
|
||||||
const maybeSecureCookie = cookie => {
|
|
||||||
return window.location.protocol === 'https:' ? cookie + '; Secure' : cookie
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AccountProvider = ({ children }) => {
|
export const AccountProvider = ({ children }) => {
|
||||||
const [accounts, setAccounts] = useState([])
|
const [accounts, setAccounts] = useState([])
|
||||||
const [meAnon, setMeAnon] = useState(true)
|
const [meAnon, setMeAnon] = useState(true)
|
||||||
const [errors, setErrors] = useState([])
|
const [errors, setErrors] = useState([])
|
||||||
|
|
||||||
const updateAccountsFromCookie = useCallback(() => {
|
const updateAccountsFromCookie = useCallback(() => {
|
||||||
const { multi_auth: multiAuthCookie } = cookie.parse(document.cookie)
|
const { [MULTI_AUTH_LIST]: listCookie } = cookie.parse(document.cookie)
|
||||||
const accounts = multiAuthCookie
|
const accounts = listCookie
|
||||||
? JSON.parse(b64Decode(multiAuthCookie))
|
? JSON.parse(b64Decode(listCookie))
|
||||||
: []
|
: []
|
||||||
setAccounts(accounts)
|
setAccounts(accounts)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const addAccount = useCallback(user => {
|
|
||||||
setAccounts(accounts => [...accounts, user])
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const removeAccount = useCallback(userId => {
|
|
||||||
setAccounts(accounts => accounts.filter(({ id }) => id !== userId))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const nextAccount = useCallback(async () => {
|
const nextAccount = useCallback(async () => {
|
||||||
const { status } = await fetch('/api/next-account', { credentials: 'include' })
|
const { status } = await fetch('/api/next-account', { credentials: 'include' })
|
||||||
// if status is 302, this means the server was able to switch us to the next available account
|
// if status is 302, this means the server was able to switch us to the next available account
|
||||||
@ -52,14 +41,14 @@ export const AccountProvider = ({ children }) => {
|
|||||||
|
|
||||||
const checkErrors = useCallback(() => {
|
const checkErrors = useCallback(() => {
|
||||||
const {
|
const {
|
||||||
multi_auth: multiAuthCookie,
|
[MULTI_AUTH_LIST]: listCookie,
|
||||||
'multi_auth.user-id': multiAuthUserIdCookie
|
[MULTI_AUTH_POINTER]: pointerCookie
|
||||||
} = cookie.parse(document.cookie)
|
} = cookie.parse(document.cookie)
|
||||||
|
|
||||||
const errors = []
|
const errors = []
|
||||||
|
|
||||||
if (!multiAuthCookie) errors.push('multi_auth cookie not found')
|
if (!listCookie) errors.push(`${MULTI_AUTH_LIST} cookie not found`)
|
||||||
if (!multiAuthUserIdCookie) errors.push('multi_auth.user-id cookie not found')
|
if (!pointerCookie) errors.push(`${MULTI_AUTH_POINTER} cookie not found`)
|
||||||
|
|
||||||
setErrors(errors)
|
setErrors(errors)
|
||||||
}, [])
|
}, [])
|
||||||
@ -69,8 +58,8 @@ export const AccountProvider = ({ children }) => {
|
|||||||
|
|
||||||
updateAccountsFromCookie()
|
updateAccountsFromCookie()
|
||||||
|
|
||||||
const { 'multi_auth.user-id': multiAuthUserIdCookie } = cookie.parse(document.cookie)
|
const { [MULTI_AUTH_POINTER]: pointerCookie } = cookie.parse(document.cookie)
|
||||||
setMeAnon(multiAuthUserIdCookie === 'anonymous')
|
setMeAnon(pointerCookie === 'anonymous')
|
||||||
|
|
||||||
const interval = setInterval(checkErrors, CHECK_ERRORS_INTERVAL_MS)
|
const interval = setInterval(checkErrors, CHECK_ERRORS_INTERVAL_MS)
|
||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
@ -79,14 +68,12 @@ export const AccountProvider = ({ children }) => {
|
|||||||
const value = useMemo(
|
const value = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
accounts,
|
accounts,
|
||||||
addAccount,
|
|
||||||
removeAccount,
|
|
||||||
meAnon,
|
meAnon,
|
||||||
setMeAnon,
|
setMeAnon,
|
||||||
nextAccount,
|
nextAccount,
|
||||||
multiAuthErrors: errors
|
multiAuthErrors: errors
|
||||||
}),
|
}),
|
||||||
[accounts, addAccount, removeAccount, meAnon, setMeAnon, nextAccount, errors])
|
[accounts, meAnon, setMeAnon, nextAccount])
|
||||||
return <AccountContext.Provider value={value}>{children}</AccountContext.Provider>
|
return <AccountContext.Provider value={value}>{children}</AccountContext.Provider>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,24 +87,23 @@ const AccountListRow = ({ account, ...props }) => {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
// fetch updated names and photo ids since they might have changed since we were issued the JWTs
|
// fetch updated names and photo ids since they might have changed since we were issued the JWTs
|
||||||
const [name, setName] = useState(account.name)
|
const { data, error } = useQuery(USER,
|
||||||
const [photoId, setPhotoId] = useState(account.photoId)
|
|
||||||
useQuery(USER,
|
|
||||||
{
|
{
|
||||||
variables: { id: account.id },
|
variables: { id: account.id }
|
||||||
onCompleted ({ user: { name, photoId } }) {
|
|
||||||
if (photoId) setPhotoId(photoId)
|
|
||||||
if (name) setName(name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
if (error) console.error(`query for user ${account.id} failed:`, error)
|
||||||
|
|
||||||
|
const name = data?.user?.name || account.name
|
||||||
|
const photoId = data?.user?.photoId || account.photoId
|
||||||
|
|
||||||
const onClick = async (e) => {
|
const onClick = async (e) => {
|
||||||
// prevent navigation
|
// prevent navigation
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
// update pointer cookie
|
// update pointer cookie
|
||||||
document.cookie = maybeSecureCookie(`multi_auth.user-id=${anonRow ? 'anonymous' : account.id}; Path=/`)
|
const options = cookieOptions({ httpOnly: false })
|
||||||
|
document.cookie = cookie.serialize(MULTI_AUTH_POINTER, anonRow ? MULTI_AUTH_ANON : account.id, options)
|
||||||
|
|
||||||
// update state
|
// update state
|
||||||
if (anonRow) {
|
if (anonRow) {
|
||||||
|
@ -37,16 +37,11 @@ export function BoostHelp () {
|
|||||||
<li>The highest boost in a territory over the last 30 days is pinned to the top of the territory</li>
|
<li>The highest boost in a territory over the last 30 days is pinned to the top of the territory</li>
|
||||||
<li>The highest boost across all territories over the last 30 days is pinned to the top of the homepage</li>
|
<li>The highest boost across all territories over the last 30 days is pinned to the top of the homepage</li>
|
||||||
<li>The minimum boost is {numWithUnits(BOOST_MIN, { abbreviate: false })}</li>
|
<li>The minimum boost is {numWithUnits(BOOST_MIN, { abbreviate: false })}</li>
|
||||||
<li>Each {numWithUnits(BOOST_MULT, { abbreviate: false })} of boost is equivalent to a zap-vote from a maximally trusted stacker
|
<li>Each {numWithUnits(BOOST_MULT, { abbreviate: false })} of boost is equivalent to a zap-vote from a maximally trusted stacker (very rare)
|
||||||
<ul>
|
<ul>
|
||||||
<li>e.g. {numWithUnits(BOOST_MULT * 5, { abbreviate: false })} is like five zap-votes from a maximally trusted stacker</li>
|
<li>e.g. {numWithUnits(BOOST_MULT * 5, { abbreviate: false })} is like five zap-votes from a maximally trusted stacker</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li>The decay of boost "votes" increases at 1.25x the rate of organic votes
|
|
||||||
<ul>
|
|
||||||
<li>i.e. boost votes fall out of ranking faster</li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li>boost can take a few minutes to show higher ranking in feed</li>
|
<li>boost can take a few minutes to show higher ranking in feed</li>
|
||||||
<li>100% of boost goes to the territory founder and top stackers as rewards</li>
|
<li>100% of boost goes to the territory founder and top stackers as rewards</li>
|
||||||
</ol>
|
</ol>
|
||||||
|
@ -9,6 +9,9 @@ import { NostrAuthWithExplainer } from './nostr-auth'
|
|||||||
import LoginButton from './login-button'
|
import LoginButton from './login-button'
|
||||||
import { emailSchema } from '@/lib/validate'
|
import { emailSchema } from '@/lib/validate'
|
||||||
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
|
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
|
||||||
|
import { datePivot } from '@/lib/time'
|
||||||
|
import * as cookie from 'cookie'
|
||||||
|
import { cookieOptions } from '@/lib/auth'
|
||||||
|
|
||||||
export function EmailLoginForm ({ text, callbackUrl, multiAuth }) {
|
export function EmailLoginForm ({ text, callbackUrl, multiAuth }) {
|
||||||
const disabled = multiAuth
|
const disabled = multiAuth
|
||||||
@ -59,15 +62,14 @@ export default function Login ({ providers, callbackUrl, multiAuth, error, text,
|
|||||||
|
|
||||||
// signup/signin awareness cookie
|
// signup/signin awareness cookie
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const cookieOptions = [
|
// expire cookie if we're on /signup instead of /login
|
||||||
`signin=${!!signin}`,
|
// since the server will only check if the cookie is set, not its value
|
||||||
'path=/',
|
const options = cookieOptions({
|
||||||
'max-age=' + (signin ? 60 * 60 * 24 : 0), // 24 hours if signin is true, expire the cookie otherwise
|
expires: signin ? datePivot(new Date(), { hours: 24 }) : 0,
|
||||||
'SameSite=Lax',
|
maxAge: signin ? 86400 : 0,
|
||||||
process.env.NODE_ENV === 'production' ? 'Secure' : ''
|
httpOnly: false
|
||||||
].filter(Boolean).join(';')
|
})
|
||||||
|
document.cookie = cookie.serialize('signin', signin, options)
|
||||||
document.cookie = cookieOptions
|
|
||||||
}, [signin])
|
}, [signin])
|
||||||
|
|
||||||
if (router.query.type === 'lightning') {
|
if (router.query.type === 'lightning') {
|
||||||
|
@ -239,7 +239,13 @@ export function useWalletLogs (wallet, initialPage = 1, logsPerPage = 10) {
|
|||||||
const newLogs = data.walletLogs.entries.map(({ createdAt, wallet: walletType, ...log }) => ({
|
const newLogs = data.walletLogs.entries.map(({ createdAt, wallet: walletType, ...log }) => ({
|
||||||
ts: +new Date(createdAt),
|
ts: +new Date(createdAt),
|
||||||
wallet: walletTag(getWalletByType(walletType)),
|
wallet: walletTag(getWalletByType(walletType)),
|
||||||
...log
|
...log,
|
||||||
|
// required to resolve recv status
|
||||||
|
context: {
|
||||||
|
recv: true,
|
||||||
|
status: !!log.context?.bolt11 && ['warn', 'error', 'success'].includes(log.level.toLowerCase()),
|
||||||
|
...log.context
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
const combinedLogs = uniqueSort([...result.data, ...newLogs])
|
const combinedLogs = uniqueSort([...result.data, ...newLogs])
|
||||||
|
|
||||||
|
@ -89,16 +89,15 @@ Click [here](/wallets) or click on your name and select 'wallets'. You should th
|
|||||||
We currently support the following wallets:
|
We currently support the following wallets:
|
||||||
|
|
||||||
- [WebLN](https://www.webln.guide/ressources/webln-providers)
|
- [WebLN](https://www.webln.guide/ressources/webln-providers)
|
||||||
- [Blink](https://www.blink.sv/)
|
- [Blink](https://www.blink.sv/). Read the [guide](https://stacker.news/items/705629/r/supratic)
|
||||||
- [Core Lightning](https://docs.corelightning.org/) via [CLNRest](https://docs.corelightning.org/docs/rest)
|
- [Core Lightning](https://docs.corelightning.org/) via [CLNRest](https://docs.corelightning.org/docs/rest). Read the [guide](https://stacker.news/items/545926/r/supratic)
|
||||||
- [Lightning Node Connect](https://docs.lightning.engineering/lightning-network-tools/lightning-terminal/lightning-node-connect) (LNC)
|
- [Lightning Node Connect](https://docs.lightning.engineering/lightning-network-tools/lightning-terminal/lightning-node-connect) (LNC)
|
||||||
- [Lightning Network Daemon](https://github.com/lightningnetwork/lnd) (LND) via [gRPC](https://lightning.engineering/api-docs/api/lnd/)
|
- [Lightning Network Daemon](https://github.com/lightningnetwork/lnd) (LND) via [gRPC](https://lightning.engineering/api-docs/api/lnd/). Read the [guide](https://stacker.news/items/704693/r/supratic)
|
||||||
- [LNbits](https://lnbits.com/)
|
- [LNbits](https://lnbits.com/). Read the [guide](https://stacker.news/items/697132/r/supratic)
|
||||||
- [Nostr Wallet Connect](https://nwc.dev/) (NWC)
|
- [Nostr Wallet Connect](https://nwc.dev/) (NWC). Read the [guide](https://stacker.news/items/698497/r/supratic)
|
||||||
- [lightning address](https://strike.me/learn/what-is-a-lightning-address/)
|
- [lightning address](https://strike.me/learn/what-is-a-lightning-address/). Read the [guide](https://stacker.news/items/694593/r/supratic)
|
||||||
- [phoenixd](https://phoenix.acinq.co/server)
|
- [phoenixd](https://phoenix.acinq.co/server). Read the [guide](https://stacker.news/items/695912/r/supratic)
|
||||||
|
|
||||||
Click on the wallet you want to attach and complete the form.
|
|
||||||
|
|
||||||
### I can't find my wallet. Can I not attach one?
|
### I can't find my wallet. Can I not attach one?
|
||||||
|
|
||||||
|
78
lib/auth.js
78
lib/auth.js
@ -6,19 +6,32 @@ import { encode as encodeJWT, decode as decodeJWT } from 'next-auth/jwt'
|
|||||||
const b64Encode = obj => Buffer.from(JSON.stringify(obj)).toString('base64')
|
const b64Encode = obj => Buffer.from(JSON.stringify(obj)).toString('base64')
|
||||||
const b64Decode = s => JSON.parse(Buffer.from(s, 'base64'))
|
const b64Decode = s => JSON.parse(Buffer.from(s, 'base64'))
|
||||||
|
|
||||||
const userJwtRegexp = /^multi_auth\.\d+$/
|
export const HTTPS = process.env.NODE_ENV === 'production'
|
||||||
|
|
||||||
const HTTPS = process.env.NODE_ENV === 'production'
|
const secureCookie = (name) =>
|
||||||
const SESSION_COOKIE_NAME = HTTPS ? '__Secure-next-auth.session-token' : 'next-auth.session-token'
|
HTTPS
|
||||||
|
? `__Secure-${name}`
|
||||||
|
: name
|
||||||
|
|
||||||
const cookieOptions = (args) => ({
|
export const SESSION_COOKIE = secureCookie('next-auth.session-token')
|
||||||
|
export const MULTI_AUTH_LIST = secureCookie('multi_auth')
|
||||||
|
export const MULTI_AUTH_POINTER = secureCookie('multi_auth.user-id')
|
||||||
|
export const MULTI_AUTH_ANON = 'anonymous'
|
||||||
|
|
||||||
|
export const MULTI_AUTH_JWT = id => secureCookie(`multi_auth.${id}`)
|
||||||
|
|
||||||
|
const MULTI_AUTH_REGEXP = /^(__Secure-)?multi_auth/
|
||||||
|
const MULTI_AUTH_JWT_REGEXP = /^(__Secure-)?multi_auth\.\d+$/
|
||||||
|
|
||||||
|
export const cookieOptions = (args) => ({
|
||||||
path: '/',
|
path: '/',
|
||||||
secure: process.env.NODE_ENV === 'production',
|
secure: HTTPS,
|
||||||
// httpOnly cookies by default
|
// httpOnly cookies by default
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
// default expiration for next-auth JWTs is in 30 days
|
// default expiration for next-auth JWTs is in 30 days
|
||||||
expires: datePivot(new Date(), { days: 30 }),
|
expires: datePivot(new Date(), { days: 30 }),
|
||||||
|
maxAge: 2592000, // 30 days in seconds
|
||||||
...args
|
...args
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -27,44 +40,43 @@ export function setMultiAuthCookies (req, res, { id, jwt, name, photoId }) {
|
|||||||
const jsOptions = { ...httpOnlyOptions, httpOnly: false }
|
const jsOptions = { ...httpOnlyOptions, httpOnly: false }
|
||||||
|
|
||||||
// add JWT to **httpOnly** cookie
|
// add JWT to **httpOnly** cookie
|
||||||
res.appendHeader('Set-Cookie', cookie.serialize(`multi_auth.${id}`, jwt, httpOnlyOptions))
|
res.appendHeader('Set-Cookie', cookie.serialize(MULTI_AUTH_JWT(id), jwt, httpOnlyOptions))
|
||||||
|
|
||||||
// switch to user we just added
|
// switch to user we just added
|
||||||
res.appendHeader('Set-Cookie', cookie.serialize('multi_auth.user-id', id, jsOptions))
|
res.appendHeader('Set-Cookie', cookie.serialize(MULTI_AUTH_POINTER, id, jsOptions))
|
||||||
|
|
||||||
let newMultiAuth = [{ id, name, photoId }]
|
let newMultiAuth = [{ id, name, photoId }]
|
||||||
if (req.cookies.multi_auth) {
|
if (req.cookies[MULTI_AUTH_LIST]) {
|
||||||
const oldMultiAuth = b64Decode(req.cookies.multi_auth)
|
const oldMultiAuth = b64Decode(req.cookies[MULTI_AUTH_LIST])
|
||||||
// make sure we don't add duplicates
|
// make sure we don't add duplicates
|
||||||
if (oldMultiAuth.some(({ id: id_ }) => id_ === id)) return
|
if (oldMultiAuth.some(({ id: id_ }) => id_ === id)) return
|
||||||
newMultiAuth = [...oldMultiAuth, ...newMultiAuth]
|
newMultiAuth = [...oldMultiAuth, ...newMultiAuth]
|
||||||
}
|
}
|
||||||
res.appendHeader('Set-Cookie', cookie.serialize('multi_auth', b64Encode(newMultiAuth), jsOptions))
|
res.appendHeader('Set-Cookie', cookie.serialize(MULTI_AUTH_LIST, b64Encode(newMultiAuth), jsOptions))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function switchSessionCookie (request) {
|
function switchSessionCookie (request) {
|
||||||
// switch next-auth session cookie with multi_auth cookie if cookie pointer present
|
// switch next-auth session cookie with multi_auth cookie if cookie pointer present
|
||||||
|
|
||||||
// is there a cookie pointer?
|
// is there a cookie pointer?
|
||||||
const cookiePointerName = 'multi_auth.user-id'
|
const hasCookiePointer = !!request.cookies[MULTI_AUTH_POINTER]
|
||||||
const hasCookiePointer = !!request.cookies[cookiePointerName]
|
|
||||||
|
|
||||||
// is there a session?
|
// is there a session?
|
||||||
const hasSession = !!request.cookies[SESSION_COOKIE_NAME]
|
const hasSession = !!request.cookies[SESSION_COOKIE]
|
||||||
|
|
||||||
if (!hasCookiePointer || !hasSession) {
|
if (!hasCookiePointer || !hasSession) {
|
||||||
// no session or no cookie pointer. do nothing.
|
// no session or no cookie pointer. do nothing.
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
const userId = request.cookies[cookiePointerName]
|
const userId = request.cookies[MULTI_AUTH_POINTER]
|
||||||
if (userId === 'anonymous') {
|
if (userId === MULTI_AUTH_ANON) {
|
||||||
// user switched to anon. only delete session cookie.
|
// user switched to anon. only delete session cookie.
|
||||||
delete request.cookies[SESSION_COOKIE_NAME]
|
delete request.cookies[SESSION_COOKIE]
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
const userJWT = request.cookies[`multi_auth.${userId}`]
|
const userJWT = request.cookies[MULTI_AUTH_JWT(userId)]
|
||||||
if (!userJWT) {
|
if (!userJWT) {
|
||||||
// no JWT for account switching found
|
// no JWT for account switching found
|
||||||
return request
|
return request
|
||||||
@ -72,7 +84,7 @@ export function switchSessionCookie (request) {
|
|||||||
|
|
||||||
if (userJWT) {
|
if (userJWT) {
|
||||||
// use JWT found in cookie pointed to by cookie pointer
|
// use JWT found in cookie pointed to by cookie pointer
|
||||||
request.cookies[SESSION_COOKIE_NAME] = userJWT
|
request.cookies[SESSION_COOKIE] = userJWT
|
||||||
return request
|
return request
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,13 +92,13 @@ export function switchSessionCookie (request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function checkMultiAuthCookies (req, res) {
|
export function checkMultiAuthCookies (req, res) {
|
||||||
if (!req.cookies.multi_auth || !req.cookies['multi_auth.user-id']) {
|
if (!req.cookies[MULTI_AUTH_LIST] || !req.cookies[MULTI_AUTH_POINTER]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const accounts = b64Decode(req.cookies.multi_auth)
|
const accounts = b64Decode(req.cookies[MULTI_AUTH_LIST])
|
||||||
for (const account of accounts) {
|
for (const account of accounts) {
|
||||||
if (!req.cookies[`multi_auth.${account.id}`]) {
|
if (!req.cookies[MULTI_AUTH_JWT(account.id)]) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,22 +106,18 @@ export function checkMultiAuthCookies (req, res) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetMultiAuthCookies (req, res) {
|
function resetMultiAuthCookies (req, res) {
|
||||||
const httpOnlyOptions = cookieOptions({ expires: 0, maxAge: 0 })
|
const httpOnlyOptions = cookieOptions({ expires: 0, maxAge: 0 })
|
||||||
const jsOptions = { ...httpOnlyOptions, httpOnly: false }
|
const jsOptions = { ...httpOnlyOptions, httpOnly: false }
|
||||||
|
|
||||||
if ('multi_auth' in req.cookies) res.appendHeader('Set-Cookie', cookie.serialize('multi_auth', '', jsOptions))
|
|
||||||
if ('multi_auth.user-id' in req.cookies) res.appendHeader('Set-Cookie', cookie.serialize('multi_auth.user-id', '', jsOptions))
|
|
||||||
|
|
||||||
for (const key of Object.keys(req.cookies)) {
|
for (const key of Object.keys(req.cookies)) {
|
||||||
// reset all user JWTs
|
if (!MULTI_AUTH_REGEXP.test(key)) continue
|
||||||
if (userJwtRegexp.test(key)) {
|
const options = MULTI_AUTH_JWT_REGEXP.test(key) ? httpOnlyOptions : jsOptions
|
||||||
res.appendHeader('Set-Cookie', cookie.serialize(key, '', httpOnlyOptions))
|
res.appendHeader('Set-Cookie', cookie.serialize(key, '', options))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function refreshMultiAuthCookies (req, res) {
|
async function refreshMultiAuthCookies (req, res) {
|
||||||
const httpOnlyOptions = cookieOptions()
|
const httpOnlyOptions = cookieOptions()
|
||||||
const jsOptions = { ...httpOnlyOptions, httpOnly: false }
|
const jsOptions = { ...httpOnlyOptions, httpOnly: false }
|
||||||
|
|
||||||
@ -125,15 +133,15 @@ export async function refreshMultiAuthCookies (req, res) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAnon = req.cookies['multi_auth.user-id'] === 'anonymous'
|
const isAnon = req.cookies[MULTI_AUTH_POINTER] === MULTI_AUTH_ANON
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(req.cookies)) {
|
for (const [key, value] of Object.entries(req.cookies)) {
|
||||||
// only refresh session cookie manually if we switched to anon since else it's already handled by next-auth
|
// only refresh session cookie manually if we switched to anon since else it's already handled by next-auth
|
||||||
if (key === SESSION_COOKIE_NAME && !isAnon) continue
|
if (key === SESSION_COOKIE && !isAnon) continue
|
||||||
|
|
||||||
if (!key.startsWith('multi_auth') && key !== SESSION_COOKIE_NAME) continue
|
if (!key.startsWith(MULTI_AUTH_LIST) && key !== SESSION_COOKIE) continue
|
||||||
|
|
||||||
if (userJwtRegexp.test(key) || key === SESSION_COOKIE_NAME) {
|
if (MULTI_AUTH_JWT_REGEXP.test(key) || key === SESSION_COOKIE) {
|
||||||
const oldToken = value
|
const oldToken = value
|
||||||
const newToken = await refreshToken(oldToken)
|
const newToken = await refreshToken(oldToken)
|
||||||
res.appendHeader('Set-Cookie', cookie.serialize(key, newToken, httpOnlyOptions))
|
res.appendHeader('Set-Cookie', cookie.serialize(key, newToken, httpOnlyOptions))
|
||||||
|
88
package-lock.json
generated
88
package-lock.json
generated
@ -52,7 +52,7 @@
|
|||||||
"mdast-util-gfm": "^3.0.0",
|
"mdast-util-gfm": "^3.0.0",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"micromark-extension-gfm": "^3.0.0",
|
"micromark-extension-gfm": "^3.0.0",
|
||||||
"next": "^14.2.16",
|
"next": "^14.2.25",
|
||||||
"next-auth": "^4.24.8",
|
"next-auth": "^4.24.8",
|
||||||
"next-plausible": "^3.12.2",
|
"next-plausible": "^3.12.2",
|
||||||
"next-seo": "^6.6.0",
|
"next-seo": "^6.6.0",
|
||||||
@ -4126,9 +4126,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "14.2.16",
|
"version": "14.2.25",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.25.tgz",
|
||||||
"integrity": "sha512-fLrX5TfJzHCbnZ9YUSnGW63tMV3L4nSfhgOQ0iCcX21Pt+VSTDuaLsSuL8J/2XAiVA5AnzvXDpf6pMs60QxOag=="
|
"integrity": "sha512-JnzQ2cExDeG7FxJwqAksZ3aqVJrHjFwZQAEJ9gQZSoEhIow7SNoKZzju/AwQ+PLIR4NY8V0rhcVozx/2izDO0w=="
|
||||||
},
|
},
|
||||||
"node_modules/@next/eslint-plugin-next": {
|
"node_modules/@next/eslint-plugin-next": {
|
||||||
"version": "14.2.15",
|
"version": "14.2.15",
|
||||||
@ -4186,9 +4186,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "14.2.16",
|
"version": "14.2.25",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.25.tgz",
|
||||||
"integrity": "sha512-uFT34QojYkf0+nn6MEZ4gIWQ5aqGF11uIZ1HSxG+cSbj+Mg3+tYm8qXYd3dKN5jqKUm5rBVvf1PBRO/MeQ6rxw==",
|
"integrity": "sha512-09clWInF1YRd6le00vt750s3m7SEYNehz9C4PUcSu3bAdCTpjIV4aTYQZ25Ehrr83VR1rZeqtKUPWSI7GfuKZQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -4201,9 +4201,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "14.2.16",
|
"version": "14.2.25",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.25.tgz",
|
||||||
"integrity": "sha512-mCecsFkYezem0QiZlg2bau3Xul77VxUD38b/auAjohMA22G9KTJneUYMv78vWoCCFkleFAhY1NIvbyjj1ncG9g==",
|
"integrity": "sha512-V+iYM/QR+aYeJl3/FWWU/7Ix4b07ovsQ5IbkwgUK29pTHmq+5UxeDr7/dphvtXEq5pLB/PucfcBNh9KZ8vWbug==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -4216,9 +4216,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "14.2.16",
|
"version": "14.2.25",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.25.tgz",
|
||||||
"integrity": "sha512-yhkNA36+ECTC91KSyZcgWgKrYIyDnXZj8PqtJ+c2pMvj45xf7y/HrgI17hLdrcYamLfVt7pBaJUMxADtPaczHA==",
|
"integrity": "sha512-LFnV2899PJZAIEHQ4IMmZIgL0FBieh5keMnriMY1cK7ompR+JUd24xeTtKkcaw8QmxmEdhoE5Mu9dPSuDBgtTg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -4231,9 +4231,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "14.2.16",
|
"version": "14.2.25",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.25.tgz",
|
||||||
"integrity": "sha512-X2YSyu5RMys8R2lA0yLMCOCtqFOoLxrq2YbazFvcPOE4i/isubYjkh+JCpRmqYfEuCVltvlo+oGfj/b5T2pKUA==",
|
"integrity": "sha512-QC5y5PPTmtqFExcKWKYgUNkHeHE/z3lUsu83di488nyP0ZzQ3Yse2G6TCxz6nNsQwgAx1BehAJTZez+UQxzLfw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -4246,9 +4246,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "14.2.16",
|
"version": "14.2.25",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.25.tgz",
|
||||||
"integrity": "sha512-9AGcX7VAkGbc5zTSa+bjQ757tkjr6C/pKS7OK8cX7QEiK6MHIIezBLcQ7gQqbDW2k5yaqba2aDtaBeyyZh1i6Q==",
|
"integrity": "sha512-y6/ML4b9eQ2D/56wqatTJN5/JR8/xdObU2Fb1RBidnrr450HLCKr6IJZbPqbv7NXmje61UyxjF5kvSajvjye5w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -4261,9 +4261,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "14.2.16",
|
"version": "14.2.25",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.25.tgz",
|
||||||
"integrity": "sha512-Klgeagrdun4WWDaOizdbtIIm8khUDQJ/5cRzdpXHfkbY91LxBXeejL4kbZBrpR/nmgRrQvmz4l3OtttNVkz2Sg==",
|
"integrity": "sha512-sPX0TSXHGUOZFvv96GoBXpB3w4emMqKeMgemrSxI7A6l55VBJp/RKYLwZIB9JxSqYPApqiREaIIap+wWq0RU8w==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -4276,9 +4276,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "14.2.16",
|
"version": "14.2.25",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.25.tgz",
|
||||||
"integrity": "sha512-PwW8A1UC1Y0xIm83G3yFGPiOBftJK4zukTmk7DI1CebyMOoaVpd8aSy7K6GhobzhkjYvqS/QmzcfsWG2Dwizdg==",
|
"integrity": "sha512-ReO9S5hkA1DU2cFCsGoOEp7WJkhFzNbU/3VUF6XxNGUCQChyug6hZdYL/istQgfT/GWE6PNIg9cm784OI4ddxQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@ -4291,9 +4291,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-ia32-msvc": {
|
"node_modules/@next/swc-win32-ia32-msvc": {
|
||||||
"version": "14.2.16",
|
"version": "14.2.25",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.25.tgz",
|
||||||
"integrity": "sha512-jhPl3nN0oKEshJBNDAo0etGMzv0j3q3VYorTSFqH1o3rwv1MQRdor27u1zhkgsHPNeY1jxcgyx1ZsCkDD1IHgg==",
|
"integrity": "sha512-DZ/gc0o9neuCDyD5IumyTGHVun2dCox5TfPQI/BJTYwpSNYM3CZDI4i6TOdjeq1JMo+Ug4kPSMuZdwsycwFbAw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"ia32"
|
"ia32"
|
||||||
],
|
],
|
||||||
@ -4306,9 +4306,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "14.2.16",
|
"version": "14.2.25",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.25.tgz",
|
||||||
"integrity": "sha512-OA7NtfxgirCjfqt+02BqxC3MIgM/JaGjw9tOe4fyZgPsqfseNiMPnCRP44Pfs+Gpo9zPN+SXaFsgP6vk8d571A==",
|
"integrity": "sha512-KSznmS6eFjQ9RJ1nEc66kJvtGIL1iZMYmGEXsZPh2YtnLtqrgdVvKXJY2ScjjoFnG6nGLyPFR0UiEvDwVah4Tw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@ -15657,11 +15657,11 @@
|
|||||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "14.2.16",
|
"version": "14.2.25",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-14.2.16.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-14.2.25.tgz",
|
||||||
"integrity": "sha512-LcO7WnFu6lYSvCzZoo1dB+IO0xXz5uEv52HF1IUN0IqVTUIZGHuuR10I5efiLadGt+4oZqTcNZyVVEem/TM5nA==",
|
"integrity": "sha512-N5M7xMc4wSb4IkPvEV5X2BRRXUmhVHNyaXwEM86+voXthSZz8ZiRyQW4p9mwAoAPIm6OzuVZtn7idgEJeAJN3Q==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "14.2.16",
|
"@next/env": "14.2.25",
|
||||||
"@swc/helpers": "0.5.5",
|
"@swc/helpers": "0.5.5",
|
||||||
"busboy": "1.6.0",
|
"busboy": "1.6.0",
|
||||||
"caniuse-lite": "^1.0.30001579",
|
"caniuse-lite": "^1.0.30001579",
|
||||||
@ -15676,15 +15676,15 @@
|
|||||||
"node": ">=18.17.0"
|
"node": ">=18.17.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "14.2.16",
|
"@next/swc-darwin-arm64": "14.2.25",
|
||||||
"@next/swc-darwin-x64": "14.2.16",
|
"@next/swc-darwin-x64": "14.2.25",
|
||||||
"@next/swc-linux-arm64-gnu": "14.2.16",
|
"@next/swc-linux-arm64-gnu": "14.2.25",
|
||||||
"@next/swc-linux-arm64-musl": "14.2.16",
|
"@next/swc-linux-arm64-musl": "14.2.25",
|
||||||
"@next/swc-linux-x64-gnu": "14.2.16",
|
"@next/swc-linux-x64-gnu": "14.2.25",
|
||||||
"@next/swc-linux-x64-musl": "14.2.16",
|
"@next/swc-linux-x64-musl": "14.2.25",
|
||||||
"@next/swc-win32-arm64-msvc": "14.2.16",
|
"@next/swc-win32-arm64-msvc": "14.2.25",
|
||||||
"@next/swc-win32-ia32-msvc": "14.2.16",
|
"@next/swc-win32-ia32-msvc": "14.2.25",
|
||||||
"@next/swc-win32-x64-msvc": "14.2.16"
|
"@next/swc-win32-x64-msvc": "14.2.25"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@opentelemetry/api": "^1.1.0",
|
"@opentelemetry/api": "^1.1.0",
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
"mdast-util-gfm": "^3.0.0",
|
"mdast-util-gfm": "^3.0.0",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"micromark-extension-gfm": "^3.0.0",
|
"micromark-extension-gfm": "^3.0.0",
|
||||||
"next": "^14.2.16",
|
"next": "^14.2.25",
|
||||||
"next-auth": "^4.24.8",
|
"next-auth": "^4.24.8",
|
||||||
"next-plausible": "^3.12.2",
|
"next-plausible": "^3.12.2",
|
||||||
"next-seo": "^6.6.0",
|
"next-seo": "^6.6.0",
|
||||||
|
@ -223,7 +223,7 @@ async function nostrEventAuth (event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @type {import('next-auth/providers').Provider[]} */
|
/** @type {import('next-auth/providers').Provider[]} */
|
||||||
const getProviders = res => [
|
const getProviders = (req, res) => [
|
||||||
CredentialsProvider({
|
CredentialsProvider({
|
||||||
id: 'lightning',
|
id: 'lightning',
|
||||||
name: 'Lightning',
|
name: 'Lightning',
|
||||||
@ -275,14 +275,14 @@ const getProviders = res => [
|
|||||||
from: process.env.LOGIN_EMAIL_FROM,
|
from: process.env.LOGIN_EMAIL_FROM,
|
||||||
maxAge: 5 * 60, // expires in 5 minutes
|
maxAge: 5 * 60, // expires in 5 minutes
|
||||||
generateVerificationToken: generateRandomString,
|
generateVerificationToken: generateRandomString,
|
||||||
sendVerificationRequest
|
sendVerificationRequest: (...args) => sendVerificationRequest(...args, req)
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
|
|
||||||
/** @returns {import('next-auth').AuthOptions} */
|
/** @returns {import('next-auth').AuthOptions} */
|
||||||
export const getAuthOptions = (req, res) => ({
|
export const getAuthOptions = (req, res) => ({
|
||||||
callbacks: getCallbacks(req, res),
|
callbacks: getCallbacks(req, res),
|
||||||
providers: getProviders(res),
|
providers: getProviders(req, res),
|
||||||
adapter: {
|
adapter: {
|
||||||
...PrismaAdapter(prisma),
|
...PrismaAdapter(prisma),
|
||||||
createUser: data => {
|
createUser: data => {
|
||||||
@ -421,7 +421,7 @@ async function sendVerificationRequest ({
|
|||||||
url,
|
url,
|
||||||
token,
|
token,
|
||||||
provider
|
provider
|
||||||
}) {
|
}, req) {
|
||||||
let user = await prisma.user.findUnique({
|
let user = await prisma.user.findUnique({
|
||||||
where: {
|
where: {
|
||||||
// Look for the user by hashed email
|
// Look for the user by hashed email
|
||||||
@ -443,6 +443,11 @@ async function sendVerificationRequest ({
|
|||||||
|
|
||||||
const site = new URL(url).host
|
const site = new URL(url).host
|
||||||
|
|
||||||
|
// if we're trying to sign in but no user was found, resolve the promise
|
||||||
|
if (req.cookies.signin && !user) {
|
||||||
|
return resolve()
|
||||||
|
}
|
||||||
|
|
||||||
nodemailer.createTransport(server).sendMail(
|
nodemailer.createTransport(server).sendMail(
|
||||||
{
|
{
|
||||||
to: email,
|
to: email,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import * as cookie from 'cookie'
|
import * as cookie from 'cookie'
|
||||||
import { datePivot } from '@/lib/time'
|
import { datePivot } from '@/lib/time'
|
||||||
|
import { HTTPS, MULTI_AUTH_JWT, MULTI_AUTH_LIST, MULTI_AUTH_POINTER, SESSION_COOKIE } from '@/lib/auth'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {NextApiRequest} req
|
* @param {NextApiRequest} req
|
||||||
@ -8,14 +9,10 @@ import { datePivot } from '@/lib/time'
|
|||||||
*/
|
*/
|
||||||
export default (req, res) => {
|
export default (req, res) => {
|
||||||
// is there a cookie pointer?
|
// is there a cookie pointer?
|
||||||
const cookiePointerName = 'multi_auth.user-id'
|
const userId = req.cookies[MULTI_AUTH_POINTER]
|
||||||
const userId = req.cookies[cookiePointerName]
|
|
||||||
|
|
||||||
const secure = process.env.NODE_ENV === 'production'
|
|
||||||
|
|
||||||
// is there a session?
|
// is there a session?
|
||||||
const sessionCookieName = secure ? '__Secure-next-auth.session-token' : 'next-auth.session-token'
|
const sessionJWT = req.cookies[SESSION_COOKIE]
|
||||||
const sessionJWT = req.cookies[sessionCookieName]
|
|
||||||
|
|
||||||
if (!userId && !sessionJWT) {
|
if (!userId && !sessionJWT) {
|
||||||
// no cookie pointer and no session cookie present. nothing to do.
|
// no cookie pointer and no session cookie present. nothing to do.
|
||||||
@ -27,33 +24,33 @@ export default (req, res) => {
|
|||||||
|
|
||||||
const cookieOptions = {
|
const cookieOptions = {
|
||||||
path: '/',
|
path: '/',
|
||||||
secure,
|
secure: HTTPS,
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
expires: datePivot(new Date(), { months: 1 })
|
expires: datePivot(new Date(), { months: 1 })
|
||||||
}
|
}
|
||||||
// remove JWT pointed to by cookie pointer
|
// remove JWT pointed to by cookie pointer
|
||||||
cookies.push(cookie.serialize(`multi_auth.${userId}`, '', { ...cookieOptions, expires: 0, maxAge: 0 }))
|
cookies.push(cookie.serialize(MULTI_AUTH_JWT(userId), '', { ...cookieOptions, expires: 0, maxAge: 0 }))
|
||||||
|
|
||||||
// update multi_auth cookie and check if there are more accounts available
|
// update multi_auth cookie and check if there are more accounts available
|
||||||
const oldMultiAuth = req.cookies.multi_auth ? b64Decode(req.cookies.multi_auth) : undefined
|
const oldMultiAuth = req.cookies[MULTI_AUTH_LIST] ? b64Decode(req.cookies[MULTI_AUTH_LIST]) : undefined
|
||||||
const newMultiAuth = oldMultiAuth?.filter(({ id }) => id !== Number(userId))
|
const newMultiAuth = oldMultiAuth?.filter(({ id }) => id !== Number(userId))
|
||||||
if (!oldMultiAuth || newMultiAuth?.length === 0) {
|
if (!oldMultiAuth || newMultiAuth?.length === 0) {
|
||||||
// no next account available. cleanup: remove multi_auth + pointer cookie
|
// no next account available. cleanup: remove multi_auth + pointer cookie
|
||||||
cookies.push(cookie.serialize('multi_auth', '', { ...cookieOptions, httpOnly: false, expires: 0, maxAge: 0 }))
|
cookies.push(cookie.serialize(MULTI_AUTH_LIST, '', { ...cookieOptions, httpOnly: false, expires: 0, maxAge: 0 }))
|
||||||
cookies.push(cookie.serialize('multi_auth.user-id', '', { ...cookieOptions, httpOnly: false, expires: 0, maxAge: 0 }))
|
cookies.push(cookie.serialize(MULTI_AUTH_POINTER, '', { ...cookieOptions, httpOnly: false, expires: 0, maxAge: 0 }))
|
||||||
res.setHeader('Set-Cookie', cookies)
|
res.setHeader('Set-Cookie', cookies)
|
||||||
res.status(204).end()
|
res.status(204).end()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cookies.push(cookie.serialize('multi_auth', b64Encode(newMultiAuth), { ...cookieOptions, httpOnly: false }))
|
cookies.push(cookie.serialize(MULTI_AUTH_LIST, b64Encode(newMultiAuth), { ...cookieOptions, httpOnly: false }))
|
||||||
|
|
||||||
const newUserId = newMultiAuth[0].id
|
const newUserId = newMultiAuth[0].id
|
||||||
const newUserJWT = req.cookies[`multi_auth.${newUserId}`]
|
const newUserJWT = req.cookies[MULTI_AUTH_JWT(newUserId)]
|
||||||
res.setHeader('Set-Cookie', [
|
res.setHeader('Set-Cookie', [
|
||||||
...cookies,
|
...cookies,
|
||||||
cookie.serialize(cookiePointerName, newUserId, { ...cookieOptions, httpOnly: false }),
|
cookie.serialize(MULTI_AUTH_POINTER, newUserId, { ...cookieOptions, httpOnly: false }),
|
||||||
cookie.serialize(sessionCookieName, newUserJWT, cookieOptions)
|
cookie.serialize(SESSION_COOKIE, newUserJWT, cookieOptions)
|
||||||
])
|
])
|
||||||
|
|
||||||
res.status(302).end()
|
res.status(302).end()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import Image from 'react-bootstrap/Image'
|
import Image from 'react-bootstrap/Image'
|
||||||
|
import * as cookie from 'cookie'
|
||||||
import { StaticLayout } from '@/components/layout'
|
import { StaticLayout } from '@/components/layout'
|
||||||
import { getGetServerSideProps } from '@/api/ssrApollo'
|
import { getGetServerSideProps } from '@/api/ssrApollo'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
@ -12,8 +13,10 @@ export const getServerSideProps = getGetServerSideProps({ query: null })
|
|||||||
export default function Email () {
|
export default function Email () {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [callback, setCallback] = useState(null) // callback.email, callback.callbackUrl
|
const [callback, setCallback] = useState(null) // callback.email, callback.callbackUrl
|
||||||
|
const [signin, setSignin] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setSignin(!!cookie.parse(document.cookie).signin)
|
||||||
setCallback(JSON.parse(window.sessionStorage.getItem('callback')))
|
setCallback(JSON.parse(window.sessionStorage.getItem('callback')))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
@ -27,6 +30,13 @@ export default function Email () {
|
|||||||
router.push(url)
|
router.push(url)
|
||||||
}, [callback, router])
|
}, [callback, router])
|
||||||
|
|
||||||
|
const buildMessage = () => {
|
||||||
|
const email = callback?.email || 'your email address'
|
||||||
|
return signin
|
||||||
|
? `if there's a match, a magic code will be sent to ${email}`
|
||||||
|
: `a magic code has been sent to ${email}`
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StaticLayout>
|
<StaticLayout>
|
||||||
<div className='p-4 text-center'>
|
<div className='p-4 text-center'>
|
||||||
@ -35,14 +45,14 @@ export default function Email () {
|
|||||||
<Image className='rounded-1 shadow-sm' width='640' height='302' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/cowboy-saloon.gif`} fluid />
|
<Image className='rounded-1 shadow-sm' width='640' height='302' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/cowboy-saloon.gif`} fluid />
|
||||||
</video>
|
</video>
|
||||||
<h2 className='pt-4'>Check your email</h2>
|
<h2 className='pt-4'>Check your email</h2>
|
||||||
<h4 className='text-muted pt-2 pb-4'>a magic code has been sent to {callback ? callback.email : 'your email address'}</h4>
|
<h4 className='text-muted pt-2 pb-4'>{buildMessage()}</h4>
|
||||||
<MagicCodeForm onSubmit={(token) => pushCallback(token)} disabled={!callback} />
|
<MagicCodeForm onSubmit={(token) => pushCallback(token)} disabled={!callback} signin={signin} />
|
||||||
</div>
|
</div>
|
||||||
</StaticLayout>
|
</StaticLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MagicCodeForm = ({ onSubmit, disabled }) => {
|
export const MagicCodeForm = ({ onSubmit, disabled, signin }) => {
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
initial={{
|
initial={{
|
||||||
@ -64,7 +74,7 @@ export const MagicCodeForm = ({ onSubmit, disabled }) => {
|
|||||||
hideError // hide error message on every input, allow custom error message
|
hideError // hide error message on every input, allow custom error message
|
||||||
disabled={disabled} // disable the form if no callback is provided
|
disabled={disabled} // disable the form if no callback is provided
|
||||||
/>
|
/>
|
||||||
<SubmitButton variant='primary' className='px-4' disabled={disabled}>login</SubmitButton>
|
<SubmitButton variant='primary' className='px-4' disabled={disabled}>{signin ? 'login' : 'signup'}</SubmitButton>
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import Link from 'next/link'
|
|||||||
import { StaticLayout } from '@/components/layout'
|
import { StaticLayout } from '@/components/layout'
|
||||||
import Login from '@/components/login'
|
import Login from '@/components/login'
|
||||||
import { isExternal } from '@/lib/url'
|
import { isExternal } from '@/lib/url'
|
||||||
|
import { MULTI_AUTH_ANON, MULTI_AUTH_POINTER } from '@/lib/auth'
|
||||||
|
|
||||||
export async function getServerSideProps ({ req, res, query: { callbackUrl, multiAuth = false, error = null } }) {
|
export async function getServerSideProps ({ req, res, query: { callbackUrl, multiAuth = false, error = null } }) {
|
||||||
let session = await getServerSession(req, res, getAuthOptions(req))
|
let session = await getServerSession(req, res, getAuthOptions(req))
|
||||||
@ -12,7 +13,7 @@ export async function getServerSideProps ({ req, res, query: { callbackUrl, mult
|
|||||||
// required to prevent infinite redirect loops if we switch to anon
|
// required to prevent infinite redirect loops if we switch to anon
|
||||||
// but are on a page that would redirect us to /signup.
|
// but are on a page that would redirect us to /signup.
|
||||||
// without this code, /signup would redirect us back to the callbackUrl.
|
// without this code, /signup would redirect us back to the callbackUrl.
|
||||||
if (req.cookies['multi_auth.user-id'] === 'anonymous') {
|
if (req.cookies[MULTI_AUTH_POINTER] === MULTI_AUTH_ANON) {
|
||||||
session = null
|
session = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
ALTER TABLE "WalletLog"
|
||||||
|
ADD COLUMN "invoiceId" INTEGER,
|
||||||
|
ADD COLUMN "withdrawalId" INTEGER;
|
||||||
|
|
||||||
|
ALTER TABLE "WalletLog" ADD CONSTRAINT "WalletLog_invoiceId_fkey" FOREIGN KEY ("invoiceId") REFERENCES "Invoice"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
ALTER TABLE "WalletLog" ADD CONSTRAINT "WalletLog_withdrawalId_fkey" FOREIGN KEY ("withdrawalId") REFERENCES "Withdrawl"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
TRUNCATE "WalletLog" RESTRICT;
|
@ -0,0 +1,35 @@
|
|||||||
|
CREATE OR REPLACE VIEW hot_score_constants AS
|
||||||
|
SELECT 10000.0 AS boost_per_vote,
|
||||||
|
1.0 AS vote_power,
|
||||||
|
1.1 AS vote_decay,
|
||||||
|
3 AS age_wait_hours,
|
||||||
|
0.25 AS comment_vote_scaler;
|
||||||
|
|
||||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS hot_score_view_temp AS
|
||||||
|
SELECT id,
|
||||||
|
CASE WHEN "Item"."weightedVotes" - "Item"."weightedDownVotes" > 0
|
||||||
|
THEN (POWER("Item"."weightedVotes" - "Item"."weightedDownVotes", hot_score_constants.vote_power)
|
||||||
|
+ "Item"."weightedComments"*hot_score_constants.comment_vote_scaler
|
||||||
|
+ "Item".boost / hot_score_constants.boost_per_vote)
|
||||||
|
/ POWER(GREATEST(hot_score_constants.age_wait_hours, EXTRACT(EPOCH FROM (now() - "Item".created_at))/3600), hot_score_constants.vote_decay)
|
||||||
|
ELSE "Item"."weightedVotes" - "Item"."weightedDownVotes" END AS hot_score,
|
||||||
|
CASE WHEN "Item"."subWeightedVotes" - "Item"."subWeightedDownVotes" > 0
|
||||||
|
THEN (POWER("Item"."subWeightedVotes" - "Item"."subWeightedDownVotes", hot_score_constants.vote_power)
|
||||||
|
+ "Item"."weightedComments"*hot_score_constants.comment_vote_scaler
|
||||||
|
+ "Item".boost / hot_score_constants.boost_per_vote)
|
||||||
|
/ POWER(GREATEST(hot_score_constants.age_wait_hours, EXTRACT(EPOCH FROM (now() - "Item".created_at))/3600), hot_score_constants.vote_decay)
|
||||||
|
ELSE "Item"."subWeightedVotes" - "Item"."subWeightedDownVotes" END AS sub_hot_score
|
||||||
|
FROM "Item", hot_score_constants
|
||||||
|
WHERE "Item"."weightedVotes" > 0 OR "Item"."weightedDownVotes" > 0 OR "Item"."subWeightedVotes" > 0
|
||||||
|
OR "Item"."subWeightedDownVotes" > 0 OR "Item"."weightedComments" > 0 OR "Item".boost > 0;
|
||||||
|
|
||||||
|
DROP MATERIALIZED VIEW IF EXISTS hot_score_view;
|
||||||
|
ALTER MATERIALIZED VIEW hot_score_view_temp RENAME TO hot_score_view;
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS hot_score_view_id_idx ON hot_score_view(id);
|
||||||
|
CREATE INDEX IF NOT EXISTS hot_score_view_hot_score_idx ON hot_score_view(hot_score DESC NULLS LAST);
|
||||||
|
CREATE INDEX IF NOT EXISTS hot_score_view_sub_hot_score_idx ON hot_score_view(sub_hot_score DESC NULLS LAST);
|
||||||
|
CREATE INDEX IF NOT EXISTS hot_score_view_hot_score_no_nulls_idx ON hot_score_view(hot_score DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS hot_score_view_sub_hot_score_no_nulls_idx ON hot_score_view(sub_hot_score DESC);
|
||||||
|
|
||||||
|
|
@ -271,6 +271,10 @@ model WalletLog {
|
|||||||
wallet WalletType
|
wallet WalletType
|
||||||
level LogLevel
|
level LogLevel
|
||||||
message String
|
message String
|
||||||
|
invoiceId Int?
|
||||||
|
invoice Invoice? @relation(fields: [invoiceId], references: [id])
|
||||||
|
withdrawalId Int?
|
||||||
|
withdrawal Withdrawl? @relation(fields: [withdrawalId], references: [id])
|
||||||
context Json? @db.JsonB
|
context Json? @db.JsonB
|
||||||
|
|
||||||
@@index([userId, createdAt])
|
@@index([userId, createdAt])
|
||||||
@ -971,6 +975,7 @@ model Invoice {
|
|||||||
Upload Upload[]
|
Upload Upload[]
|
||||||
PollVote PollVote[]
|
PollVote PollVote[]
|
||||||
PollBlindVote PollBlindVote[]
|
PollBlindVote PollBlindVote[]
|
||||||
|
WalletLog WalletLog[]
|
||||||
|
|
||||||
@@index([createdAt], map: "Invoice.created_at_index")
|
@@index([createdAt], map: "Invoice.created_at_index")
|
||||||
@@index([userId], map: "Invoice.userId_index")
|
@@index([userId], map: "Invoice.userId_index")
|
||||||
@ -1048,6 +1053,7 @@ model Withdrawl {
|
|||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
wallet Wallet? @relation(fields: [walletId], references: [id], onDelete: SetNull)
|
wallet Wallet? @relation(fields: [walletId], references: [id], onDelete: SetNull)
|
||||||
invoiceForward InvoiceForward?
|
invoiceForward InvoiceForward?
|
||||||
|
WalletLog WalletLog[]
|
||||||
|
|
||||||
@@index([createdAt], map: "Withdrawl.created_at_index")
|
@@index([createdAt], map: "Withdrawl.created_at_index")
|
||||||
@@index([userId], map: "Withdrawl.userId_index")
|
@@index([userId], map: "Withdrawl.userId_index")
|
||||||
|
@ -39,8 +39,7 @@ export async function * createUserInvoice (userId, { msats, description, descrip
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info(
|
logger.info(
|
||||||
`↙ incoming payment: ${formatSats(msatsToSats(msats))}`,
|
`↙ incoming payment: ${formatSats(msatsToSats(msats))}`, {
|
||||||
{
|
|
||||||
amount: formatMsats(msats)
|
amount: formatMsats(msats)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { getPaymentFailureStatus, hodlInvoiceCltvDetails, getPaymentOrNotSent }
|
|||||||
import { paidActions } from '@/api/paidAction'
|
import { paidActions } from '@/api/paidAction'
|
||||||
import { walletLogger } from '@/api/resolvers/wallet'
|
import { walletLogger } from '@/api/resolvers/wallet'
|
||||||
import { LND_PATHFINDING_TIME_PREF_PPM, LND_PATHFINDING_TIMEOUT_MS, PAID_ACTION_TERMINAL_STATES } from '@/lib/constants'
|
import { LND_PATHFINDING_TIME_PREF_PPM, LND_PATHFINDING_TIMEOUT_MS, PAID_ACTION_TERMINAL_STATES } from '@/lib/constants'
|
||||||
import { formatMsats, formatSats, msatsToSats, toPositiveNumber } from '@/lib/format'
|
import { formatSats, msatsToSats, toPositiveNumber } from '@/lib/format'
|
||||||
import { datePivot } from '@/lib/time'
|
import { datePivot } from '@/lib/time'
|
||||||
import { Prisma } from '@prisma/client'
|
import { Prisma } from '@prisma/client'
|
||||||
import {
|
import {
|
||||||
@ -317,17 +317,13 @@ export async function paidActionForwarded ({ data: { invoiceId, withdrawal, ...a
|
|||||||
}, { models, lnd, boss })
|
}, { models, lnd, boss })
|
||||||
|
|
||||||
if (transitionedInvoice) {
|
if (transitionedInvoice) {
|
||||||
const { bolt11, msatsPaid } = transitionedInvoice.invoiceForward.withdrawl
|
const withdrawal = transitionedInvoice.invoiceForward.withdrawl
|
||||||
|
|
||||||
const logger = walletLogger({ wallet: transitionedInvoice.invoiceForward.wallet, models })
|
const logger = walletLogger({ wallet: transitionedInvoice.invoiceForward.wallet, models })
|
||||||
logger.ok(
|
logger.ok(
|
||||||
`↙ payment received: ${formatSats(msatsToSats(Number(msatsPaid)))}`,
|
`↙ payment received: ${formatSats(msatsToSats(Number(withdrawal.msatsPaid)))}`, {
|
||||||
{
|
invoiceId: transitionedInvoice.id,
|
||||||
bolt11,
|
withdrawalId: withdrawal.id
|
||||||
preimage: transitionedInvoice.preimage
|
|
||||||
// we could show the outgoing fee that we paid from the incoming amount to the receiver
|
|
||||||
// but we don't since it might look like the receiver paid the fee but that's not the case.
|
|
||||||
// fee: formatMsats(msatsFeePaid)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,12 +372,11 @@ export async function paidActionFailedForward ({ data: { invoiceId, withdrawal:
|
|||||||
}, { models, lnd, boss })
|
}, { models, lnd, boss })
|
||||||
|
|
||||||
if (transitionedInvoice) {
|
if (transitionedInvoice) {
|
||||||
const { bolt11, msatsFeePaying } = transitionedInvoice.invoiceForward.withdrawl
|
const fwd = transitionedInvoice.invoiceForward
|
||||||
const logger = walletLogger({ wallet: transitionedInvoice.invoiceForward.wallet, models })
|
const logger = walletLogger({ wallet: fwd.wallet, models })
|
||||||
logger.warn(
|
logger.warn(
|
||||||
`incoming payment failed: ${message}`, {
|
`incoming payment failed: ${message}`, {
|
||||||
bolt11,
|
withdrawalId: fwd.withdrawl.id
|
||||||
max_fee: formatMsats(msatsFeePaying)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -446,7 +441,11 @@ export async function paidActionCanceling ({ data: { invoiceId, ...args }, model
|
|||||||
const { wallet, bolt11 } = transitionedInvoice.invoiceForward
|
const { wallet, bolt11 } = transitionedInvoice.invoiceForward
|
||||||
const logger = walletLogger({ wallet, models })
|
const logger = walletLogger({ wallet, models })
|
||||||
const decoded = await parsePaymentRequest({ request: bolt11 })
|
const decoded = await parsePaymentRequest({ request: bolt11 })
|
||||||
logger.info(`invoice for ${formatSats(msatsToSats(decoded.mtokens))} canceled by payer`, { bolt11 })
|
logger.info(
|
||||||
|
`invoice for ${formatSats(msatsToSats(decoded.mtokens))} canceled by payer`, {
|
||||||
|
bolt11,
|
||||||
|
invoiceId: transitionedInvoice.id
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,11 +125,8 @@ export async function payingActionConfirmed ({ data: args, models, lnd, boss })
|
|||||||
|
|
||||||
const logger = walletLogger({ models, wallet: transitionedWithdrawal.wallet })
|
const logger = walletLogger({ models, wallet: transitionedWithdrawal.wallet })
|
||||||
logger?.ok(
|
logger?.ok(
|
||||||
`↙ payment received: ${formatSats(msatsToSats(transitionedWithdrawal.msatsPaid))}`,
|
`↙ payment received: ${formatSats(msatsToSats(transitionedWithdrawal.msatsPaid))}`, {
|
||||||
{
|
withdrawalId: transitionedWithdrawal.id
|
||||||
bolt11: transitionedWithdrawal.bolt11,
|
|
||||||
preimage: transitionedWithdrawal.preimage,
|
|
||||||
fee: formatMsats(transitionedWithdrawal.msatsFeePaid)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,6 @@ const Z_CONFIDENCE = 6.109410204869 // 99.9999999% confidence
|
|||||||
const SEED_WEIGHT = 0.83
|
const SEED_WEIGHT = 0.83
|
||||||
const AGAINST_MSAT_MIN = 1000
|
const AGAINST_MSAT_MIN = 1000
|
||||||
const MSAT_MIN = 1001 // 20001 is the minimum for a tip to be counted in trust
|
const MSAT_MIN = 1001 // 20001 is the minimum for a tip to be counted in trust
|
||||||
const INDEPENDENCE_THRESHOLD = 50 // how many zappers are needed to consider a sub independent
|
|
||||||
const IRRELEVANT_CUMULATIVE_TRUST = 0.001 // if a user has less than this amount of cumulative trust, they are irrelevant
|
const IRRELEVANT_CUMULATIVE_TRUST = 0.001 // if a user has less than this amount of cumulative trust, they are irrelevant
|
||||||
|
|
||||||
// for each subName, we'll need to get two graphs
|
// for each subName, we'll need to get two graphs
|
||||||
@ -37,9 +36,9 @@ export async function trust ({ boss, models }) {
|
|||||||
console.timeLog('trust', `computing global comment trust for ${territory.name}`)
|
console.timeLog('trust', `computing global comment trust for ${territory.name}`)
|
||||||
const vGlobalComment = await trustGivenGraph(commentGraph)
|
const vGlobalComment = await trustGivenGraph(commentGraph)
|
||||||
console.timeLog('trust', `computing sub post trust for ${territory.name}`)
|
console.timeLog('trust', `computing sub post trust for ${territory.name}`)
|
||||||
const vSubPost = await trustGivenGraph(postGraph, postGraph.length > INDEPENDENCE_THRESHOLD ? [territory.userId] : seeds)
|
const vSubPost = await trustGivenGraph(postGraph, [territory.userId])
|
||||||
console.timeLog('trust', `computing sub comment trust for ${territory.name}`)
|
console.timeLog('trust', `computing sub comment trust for ${territory.name}`)
|
||||||
const vSubComment = await trustGivenGraph(commentGraph, commentGraph.length > INDEPENDENCE_THRESHOLD ? [territory.userId] : seeds)
|
const vSubComment = await trustGivenGraph(commentGraph, [territory.userId])
|
||||||
console.timeLog('trust', `storing trust for ${territory.name}`)
|
console.timeLog('trust', `storing trust for ${territory.name}`)
|
||||||
let results = reduceVectors(territory.name, {
|
let results = reduceVectors(territory.name, {
|
||||||
zapPostTrust: {
|
zapPostTrust: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user