Skip to content

Commit 74d79f9

Browse files
authored
Merge branch 'master' into normalized-wallet-logs
2 parents 863282f + dbbd947 commit 74d79f9

File tree

6 files changed

+168
-13
lines changed

6 files changed

+168
-13
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
</p>
66

77

8-
- Stacker News makes internet communities that pay you Bitcoin
8+
- Stacker News is trying to fix online communities with economics
99
- What You See is What We Ship (look ma, I invented an initialism)
1010
- 100% FOSS
1111
- We pay bitcoin for PRs, issues, documentation, code reviews and more

components/nav/common.js

+7-3
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ export function MeDropdown ({ me, dropNavKey }) {
223223
)
224224
}
225225

226+
// this is the width of the 'switch account' button if no width is given
227+
const SWITCH_ACCOUNT_BUTTON_WIDTH = '162px'
228+
226229
export function SignUpButton ({ className = 'py-0', width }) {
227230
const router = useRouter()
228231
const handleLogin = useCallback(async pathname => await router.push({
@@ -233,7 +236,8 @@ export function SignUpButton ({ className = 'py-0', width }) {
233236
return (
234237
<Button
235238
className={classNames('align-items-center ps-2 pe-3', className)}
236-
style={{ borderWidth: '2px', width: width || '150px' }}
239+
// 161px is the width of the 'switch account' button
240+
style={{ borderWidth: '2px', width: width || SWITCH_ACCOUNT_BUTTON_WIDTH }}
237241
id='signup'
238242
onClick={() => handleLogin('/signup')}
239243
>
@@ -257,7 +261,7 @@ export default function LoginButton () {
257261
<Button
258262
className='align-items-center px-3 py-1'
259263
id='login'
260-
style={{ borderWidth: '2px', width: '150px' }}
264+
style={{ borderWidth: '2px', width: SWITCH_ACCOUNT_BUTTON_WIDTH }}
261265
variant='outline-grey-darkmode'
262266
onClick={() => handleLogin('/login')}
263267
>
@@ -344,7 +348,7 @@ function SwitchAccountButton ({ handleClose }) {
344348
<Button
345349
className='align-items-center px-3 py-1'
346350
variant='outline-grey-darkmode'
347-
style={{ borderWidth: '2px', width: '150px' }}
351+
style={{ borderWidth: '2px', width: SWITCH_ACCOUNT_BUTTON_WIDTH }}
348352
onClick={() => {
349353
// login buttons rendered in offcanvas aren't wrapped inside <Dropdown>
350354
// so we manually close the offcanvas in that case by passing down handleClose here

components/wallet-logger.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,11 @@ function DeleteWalletLogsObstacle ({ wallet, setLogs, onClose }) {
6262
const { deleteLogs } = useWalletLogManager(setLogs)
6363
const toaster = useToast()
6464

65-
const prompt = `Do you really want to delete all ${wallet ? '' : 'wallet'} logs ${wallet ? 'of this wallet' : ''}?`
65+
let prompt = 'Do you really want to delete all wallet logs?'
66+
if (wallet) {
67+
prompt = 'Do you really want to delete all logs of this wallet?'
68+
}
69+
6670
return (
6771
<div className='text-center'>
6872
{prompt}

pages/api/auth/[...nextauth].js

+9-4
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ async function nostrEventAuth (event) {
223223
}
224224

225225
/** @type {import('next-auth/providers').Provider[]} */
226-
const getProviders = res => [
226+
const getProviders = (req, res) => [
227227
CredentialsProvider({
228228
id: 'lightning',
229229
name: 'Lightning',
@@ -275,14 +275,14 @@ const getProviders = res => [
275275
from: process.env.LOGIN_EMAIL_FROM,
276276
maxAge: 5 * 60, // expires in 5 minutes
277277
generateVerificationToken: generateRandomString,
278-
sendVerificationRequest
278+
sendVerificationRequest: (...args) => sendVerificationRequest(...args, req)
279279
})
280280
]
281281

282282
/** @returns {import('next-auth').AuthOptions} */
283283
export const getAuthOptions = (req, res) => ({
284284
callbacks: getCallbacks(req, res),
285-
providers: getProviders(res),
285+
providers: getProviders(req, res),
286286
adapter: {
287287
...PrismaAdapter(prisma),
288288
createUser: data => {
@@ -421,7 +421,7 @@ async function sendVerificationRequest ({
421421
url,
422422
token,
423423
provider
424-
}) {
424+
}, req) {
425425
let user = await prisma.user.findUnique({
426426
where: {
427427
// Look for the user by hashed email
@@ -443,6 +443,11 @@ async function sendVerificationRequest ({
443443

444444
const site = new URL(url).host
445445

446+
// if we're trying to sign in but no user was found, resolve the promise
447+
if (req.cookies.signin && !user) {
448+
return resolve()
449+
}
450+
446451
nodemailer.createTransport(server).sendMail(
447452
{
448453
to: email,

pages/email.js

+14-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Image from 'react-bootstrap/Image'
2+
import * as cookie from 'cookie'
23
import { StaticLayout } from '@/components/layout'
34
import { getGetServerSideProps } from '@/api/ssrApollo'
45
import { useRouter } from 'next/router'
@@ -12,8 +13,10 @@ export const getServerSideProps = getGetServerSideProps({ query: null })
1213
export default function Email () {
1314
const router = useRouter()
1415
const [callback, setCallback] = useState(null) // callback.email, callback.callbackUrl
16+
const [signin, setSignin] = useState(false)
1517

1618
useEffect(() => {
19+
setSignin(!!cookie.parse(document.cookie).signin)
1720
setCallback(JSON.parse(window.sessionStorage.getItem('callback')))
1821
}, [])
1922

@@ -27,6 +30,13 @@ export default function Email () {
2730
router.push(url)
2831
}, [callback, router])
2932

33+
const buildMessage = () => {
34+
const email = callback?.email || 'your email address'
35+
return signin
36+
? `if there's a match, a magic code will be sent to ${email}`
37+
: `a magic code has been sent to ${email}`
38+
}
39+
3040
return (
3141
<StaticLayout>
3242
<div className='p-4 text-center'>
@@ -35,14 +45,14 @@ export default function Email () {
3545
<Image className='rounded-1 shadow-sm' width='640' height='302' src={`${process.env.NEXT_PUBLIC_ASSET_PREFIX}/cowboy-saloon.gif`} fluid />
3646
</video>
3747
<h2 className='pt-4'>Check your email</h2>
38-
<h4 className='text-muted pt-2 pb-4'>a magic code has been sent to {callback ? callback.email : 'your email address'}</h4>
39-
<MagicCodeForm onSubmit={(token) => pushCallback(token)} disabled={!callback} />
48+
<h4 className='text-muted pt-2 pb-4'>{buildMessage()}</h4>
49+
<MagicCodeForm onSubmit={(token) => pushCallback(token)} disabled={!callback} signin={signin} />
4050
</div>
4151
</StaticLayout>
4252
)
4353
}
4454

45-
export const MagicCodeForm = ({ onSubmit, disabled }) => {
55+
export const MagicCodeForm = ({ onSubmit, disabled, signin }) => {
4656
return (
4757
<Form
4858
initial={{
@@ -64,7 +74,7 @@ export const MagicCodeForm = ({ onSubmit, disabled }) => {
6474
hideError // hide error message on every input, allow custom error message
6575
disabled={disabled} // disable the form if no callback is provided
6676
/>
67-
<SubmitButton variant='primary' className='px-4' disabled={disabled}>login</SubmitButton>
77+
<SubmitButton variant='primary' className='px-4' disabled={disabled}>{signin ? 'login' : 'signup'}</SubmitButton>
6878
</Form>
6979
)
7080
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
-- add limit and offset
2+
CREATE OR REPLACE FUNCTION item_comments_zaprank_with_me_limited(
3+
_item_id int, _global_seed int, _me_id int, _limit int, _offset int, _grandchild_limit int,
4+
_level int, _where text, _order_by text)
5+
RETURNS jsonb
6+
LANGUAGE plpgsql VOLATILE PARALLEL SAFE AS
7+
$$
8+
DECLARE
9+
result jsonb;
10+
BEGIN
11+
IF _level < 1 THEN
12+
RETURN '[]'::jsonb;
13+
END IF;
14+
15+
EXECUTE 'CREATE TEMP TABLE IF NOT EXISTS t_item ON COMMIT DROP AS '
16+
|| 'WITH RECURSIVE base AS ( '
17+
|| ' (SELECT "Item".*, 1 as level, ROW_NUMBER() OVER () as rn '
18+
|| ' FROM "Item" '
19+
|| ' LEFT JOIN hot_score_view g(id, "hotScore", "subHotScore") ON g.id = "Item".id '
20+
|| ' WHERE "Item"."parentId" = $1 '
21+
|| _order_by || ' '
22+
|| ' LIMIT $4 '
23+
|| ' OFFSET $5) '
24+
|| ' UNION ALL '
25+
|| ' (SELECT "Item".*, b.level + 1, ROW_NUMBER() OVER (PARTITION BY "Item"."parentId" ' || _order_by || ') as rn '
26+
|| ' FROM "Item" '
27+
|| ' JOIN base b ON "Item"."parentId" = b.id '
28+
|| ' LEFT JOIN hot_score_view g(id, "hotScore", "subHotScore") ON g.id = "Item".id '
29+
|| ' WHERE b.level < $7 AND (b.level = 1 OR b.rn <= $6)) '
30+
|| ') '
31+
|| 'SELECT "Item".*, '
32+
|| ' "Item".created_at at time zone ''UTC'' AS "createdAt", '
33+
|| ' "Item".updated_at at time zone ''UTC'' AS "updatedAt", '
34+
|| ' "Item"."invoicePaidAt" at time zone ''UTC'' AS "invoicePaidAtUTC", '
35+
|| ' to_jsonb(users.*) || jsonb_build_object(''meMute'', "Mute"."mutedId" IS NOT NULL) AS user, '
36+
|| ' COALESCE("ItemAct"."meMsats", 0) AS "meMsats", '
37+
|| ' COALESCE("ItemAct"."mePendingMsats", 0) as "mePendingMsats", '
38+
|| ' COALESCE("ItemAct"."meDontLikeMsats", 0) AS "meDontLikeMsats", '
39+
|| ' COALESCE("ItemAct"."meMcredits", 0) AS "meMcredits", '
40+
|| ' COALESCE("ItemAct"."mePendingMcredits", 0) as "mePendingMcredits", '
41+
|| ' "Bookmark"."itemId" IS NOT NULL AS "meBookmark", '
42+
|| ' "ThreadSubscription"."itemId" IS NOT NULL AS "meSubscription", '
43+
|| ' g.hot_score AS "hotScore", g.sub_hot_score AS "subHotScore" '
44+
|| 'FROM base "Item" '
45+
|| 'JOIN users ON users.id = "Item"."userId" '
46+
|| ' LEFT JOIN "Mute" ON "Mute"."muterId" = $3 AND "Mute"."mutedId" = "Item"."userId" '
47+
|| ' LEFT JOIN "Bookmark" ON "Bookmark"."userId" = $3 AND "Bookmark"."itemId" = "Item".id '
48+
|| ' LEFT JOIN "ThreadSubscription" ON "ThreadSubscription"."userId" = $3 AND "ThreadSubscription"."itemId" = "Item".id '
49+
|| ' LEFT JOIN hot_score_view g ON g.id = "Item".id '
50+
|| 'LEFT JOIN LATERAL ( '
51+
|| ' SELECT "itemId", '
52+
|| ' sum("ItemAct".msats) FILTER (WHERE "invoiceActionState" IS DISTINCT FROM ''FAILED'' AND "InvoiceForward".id IS NOT NULL AND (act = ''FEE'' OR act = ''TIP'')) AS "meMsats", '
53+
|| ' sum("ItemAct".msats) FILTER (WHERE "invoiceActionState" IS DISTINCT FROM ''FAILED'' AND "InvoiceForward".id IS NULL AND (act = ''FEE'' OR act = ''TIP'')) AS "meMcredits", '
54+
|| ' sum("ItemAct".msats) FILTER (WHERE "invoiceActionState" IS NOT DISTINCT FROM ''PENDING'' AND "InvoiceForward".id IS NOT NULL AND (act = ''FEE'' OR act = ''TIP'')) AS "mePendingMsats", '
55+
|| ' sum("ItemAct".msats) FILTER (WHERE "invoiceActionState" IS NOT DISTINCT FROM ''PENDING'' AND "InvoiceForward".id IS NULL AND (act = ''FEE'' OR act = ''TIP'')) AS "mePendingMcredits", '
56+
|| ' sum("ItemAct".msats) FILTER (WHERE "invoiceActionState" IS DISTINCT FROM ''FAILED'' AND act = ''DONT_LIKE_THIS'') AS "meDontLikeMsats" '
57+
|| ' FROM "ItemAct" '
58+
|| ' LEFT JOIN "Invoice" ON "Invoice".id = "ItemAct"."invoiceId" '
59+
|| ' LEFT JOIN "InvoiceForward" ON "InvoiceForward"."invoiceId" = "Invoice"."id" '
60+
|| ' WHERE "ItemAct"."userId" = $3 '
61+
|| ' AND "ItemAct"."itemId" = "Item".id '
62+
|| ' GROUP BY "ItemAct"."itemId" '
63+
|| ') "ItemAct" ON true '
64+
|| 'WHERE ("Item".level = 1 OR "Item".rn <= $6 - "Item".level + 2) ' || _where || ' '
65+
USING _item_id, _global_seed, _me_id, _limit, _offset, _grandchild_limit, _level, _where, _order_by;
66+
67+
EXECUTE ''
68+
|| 'SELECT COALESCE(jsonb_agg(sub), ''[]''::jsonb) AS comments '
69+
|| 'FROM ( '
70+
|| ' SELECT "Item".*, item_comments_zaprank_with_me_limited("Item".id, $2, $3, $4, $5, $6, $7 - 1, $8, $9) AS comments '
71+
|| ' FROM t_item "Item" '
72+
|| ' WHERE "Item"."parentId" = $1 '
73+
|| _order_by
74+
|| ' ) sub'
75+
INTO result USING _item_id, _global_seed, _me_id, _limit, _offset, _grandchild_limit, _level, _where, _order_by;
76+
77+
RETURN result;
78+
END
79+
$$;
80+
81+
CREATE OR REPLACE FUNCTION item_comments_limited(
82+
_item_id int, _limit int, _offset int, _grandchild_limit int,
83+
_level int, _where text, _order_by text)
84+
RETURNS jsonb
85+
LANGUAGE plpgsql VOLATILE PARALLEL SAFE AS
86+
$$
87+
DECLARE
88+
result jsonb;
89+
BEGIN
90+
IF _level < 1 THEN
91+
RETURN '[]'::jsonb;
92+
END IF;
93+
94+
EXECUTE 'CREATE TEMP TABLE IF NOT EXISTS t_item ON COMMIT DROP AS '
95+
|| 'WITH RECURSIVE base AS ( '
96+
|| ' (SELECT "Item".*, 1 as level, ROW_NUMBER() OVER () as rn '
97+
|| ' FROM "Item" '
98+
|| ' LEFT JOIN hot_score_view g(id, "hotScore", "subHotScore") ON g.id = "Item".id '
99+
|| ' WHERE "Item"."parentId" = $1 '
100+
|| _order_by || ' '
101+
|| ' LIMIT $2 '
102+
|| ' OFFSET $3) '
103+
|| ' UNION ALL '
104+
|| ' (SELECT "Item".*, b.level + 1, ROW_NUMBER() OVER (PARTITION BY "Item"."parentId" ' || _order_by || ') '
105+
|| ' FROM "Item" '
106+
|| ' JOIN base b ON "Item"."parentId" = b.id '
107+
|| ' LEFT JOIN hot_score_view g(id, "hotScore", "subHotScore") ON g.id = "Item".id '
108+
|| ' WHERE b.level < $5 AND (b.level = 1 OR b.rn <= $4)) '
109+
|| ') '
110+
|| 'SELECT "Item".*, "Item".created_at at time zone ''UTC'' AS "createdAt", "Item".updated_at at time zone ''UTC'' AS "updatedAt", '
111+
|| ' "Item"."invoicePaidAt" at time zone ''UTC'' AS "invoicePaidAtUTC", '
112+
|| ' to_jsonb(users.*) as user, '
113+
|| ' g.hot_score AS "hotScore", g.sub_hot_score AS "subHotScore" '
114+
|| 'FROM base "Item" '
115+
|| 'JOIN users ON users.id = "Item"."userId" '
116+
|| 'LEFT JOIN hot_score_view g ON g.id = "Item".id '
117+
|| 'WHERE ("Item".level = 1 OR "Item".rn <= $4 - "Item".level + 2) ' || _where
118+
USING _item_id, _limit, _offset, _grandchild_limit, _level, _where, _order_by;
119+
120+
121+
EXECUTE ''
122+
|| 'SELECT COALESCE(jsonb_agg(sub), ''[]''::jsonb) AS comments '
123+
|| 'FROM ( '
124+
|| ' SELECT "Item".*, item_comments_limited("Item".id, $2, $3, $4, $5 - 1, $6, $7) AS comments '
125+
|| ' FROM t_item "Item" '
126+
|| ' WHERE "Item"."parentId" = $1 '
127+
|| _order_by
128+
|| ' ) sub'
129+
INTO result USING _item_id, _limit, _offset, _grandchild_limit, _level, _where, _order_by;
130+
RETURN result;
131+
END
132+
$$;

0 commit comments

Comments
 (0)