Compare commits

..

No commits in common. "d41b2e14f13b771a75cdc1a162ef95e3ced30b05" and "9e6675b8d29098d0fb4c8381cdaabe000523d362" have entirely different histories.

13 changed files with 47 additions and 150 deletions

View File

@ -1,37 +1,58 @@
## Description
<!--
A clear and concise description of what you changed and why.
Bullet points can be enough.
You can use the following PRs as inspiration:
- https://github.com/stackernews/stacker.news/pull/227 (feature)
- https://github.com/stackernews/stacker.news/pull/915 (feature)
- https://github.com/stackernews/stacker.news/pull/871 (fix)
- <your PR could be here>
Don't forget to mention which tickets this closes (if any).
Use following syntax to close them automatically on merge: closes #<number>
Use following syntax to close them automatically on merge: closes #<NUMBER>
-->
## Screenshots
<!--
If your changes are user facing, please add screenshots of the new UI.
You can also create a video to showcase your changes (useful to show UX).
-->
## Additional Context
<!--
You can mention here anything that you think is relevant for this PR. Some examples:
* You encountered something that you didn't understand while working on this PR
* You were not sure about something you did but did not find a better way
* You initially had a different approach but went with a different approach for some reason
-->
## Checklist
<!-- Examples for backwards incompatible changes:
- dropping database columns
- changing GraphQL type definitions to make a field mandatory -->
- [ ] Are your changes backwards compatible?
<!--
If your PR is not ready for review yet, please mark your PR as a draft.
If changes were requested, request a new review when you incorporated the feedback.
-->
<!-- If your PR is not ready for review yet, please mark your PR as a draft.
If changes were requested, request a new review when you incorporated the feedback. -->
- [ ] Did you QA this? Could we deploy this straight to production?
<!-- You should be able to use the mobile browser emulator in your browser to test this. -->
- [ ] For frontend changes: Tested on mobile?
<!-- New env vars need to be called out
so they can be properly configured for prod. -->
- [ ] Did you introduce any new environment variables? If so, call them out explicitly in the PR description.

View File

@ -176,10 +176,7 @@ export default {
let sitems = null
let termQueries = []
// short circuit: return empty result if either:
// 1. no query provided, or
// 2. searching bookmarks without being authed
if (!q || (what === 'bookmarks' && !me)) {
if (!q) {
return {
items: [],
cursor: null
@ -194,11 +191,6 @@ export default {
case 'comments':
whatArr.push({ bool: { must: { exists: { field: 'parentId' } } } })
break
case 'bookmarks':
if (me?.id) {
whatArr.push({ match: { bookmarkedBy: me?.id } })
}
break
default:
break
}

View File

@ -55,10 +55,3 @@ benalleng,pr,#1068,#1067,good-first-issue,,,,20k,???,???
abhiShandy,helpfulness,#1068,#1067,good-first-issue,,,,2k,abhishandy@stacker.news,2024-04-14
bumi,pr,#1076,,,,,,20k,bumi@getalby.com,2024-04-16
benalleng,pr,#1079,#977,easy,,,,100k,???,???
felipebueno,pr,#1024,,,,,,20k,felipe@stacker.news,2024-04-21
SatsAllDay,pr,#1075,#1064,medium-hard,,1,,450k,weareallsatoshi@getalby.com,2024-04-21
aChrisYouKnow,issue,#1075,#1064,medium-hard,,1,,45k,ACYK@stacker.news,2024-04-22
SatsAllDay,pr,#1098,,,,,,20k,weareallsatoshi@getalby.com,2024-04-21
SatsAllDay,pr,#1095,#728,medium,,,,250k,weareallsatoshi@getalby.com,2024-04-21
benalleng,pr,#1090,#1077,good-first-issue,,,,20k,???,???
benalleng,helpfulness,#1087,,,,,informed fix,20k,???,???

1 name type pr id issue ids difficulty priority changes requested notes amount receive method date paid
55 abhiShandy helpfulness #1068 #1067 good-first-issue 2k abhishandy@stacker.news 2024-04-14
56 bumi pr #1076 20k bumi@getalby.com 2024-04-16
57 benalleng pr #1079 #977 easy 100k ??? ???
felipebueno pr #1024 20k felipe@stacker.news 2024-04-21
SatsAllDay pr #1075 #1064 medium-hard 1 450k weareallsatoshi@getalby.com 2024-04-21
aChrisYouKnow issue #1075 #1064 medium-hard 1 45k ACYK@stacker.news 2024-04-22
SatsAllDay pr #1098 20k weareallsatoshi@getalby.com 2024-04-21
SatsAllDay pr #1095 #728 medium 250k weareallsatoshi@getalby.com 2024-04-21
benalleng pr #1090 #1077 good-first-issue 20k ??? ???
benalleng helpfulness #1087 informed fix 20k ??? ???

View File

@ -29,8 +29,6 @@ import { AWS_S3_URL_REGEXP } from '@/lib/constants'
import { whenRange } from '@/lib/time'
import { useFeeButton } from './fee-button'
import Thumb from '@/svgs/thumb-up-fill.svg'
import Eye from '@/svgs/eye-fill.svg'
import EyeClose from '@/svgs/eye-close-line.svg'
import Info from './info'
export function SubmitButton ({
@ -1054,35 +1052,5 @@ function Client (Component) {
}
}
function PasswordHider ({ onClick, showPass }) {
return (
<InputGroup.Text
style={{ cursor: 'pointer' }}
onClick={onClick}
>
{!showPass
? <EyeClose
fill='var(--bs-body-color)' height={20} width={20}
/>
: <Eye
fill='var(--bs-body-color)' height={20} width={20}
/>}
</InputGroup.Text>
)
}
export function PasswordInput ({ newPass, ...props }) {
const [showPass, setShowPass] = useState(false)
return (
<ClientInput
{...props}
type={showPass ? 'text' : 'password'}
autoComplete={newPass ? 'new-password' : 'current-password'}
append={<PasswordHider showPass={showPass} onClick={() => setShowPass(!showPass)} />}
/>
)
}
export const ClientInput = Client(Input)
export const ClientCheckbox = Client(Checkbox)

View File

@ -1,17 +1,15 @@
import Container from 'react-bootstrap/Container'
import styles from './search.module.css'
import SearchIcon from '@/svgs/search-line.svg'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { Form, Input, Select, DatePicker, SubmitButton } from './form'
import { useRouter } from 'next/router'
import { whenToFrom } from '@/lib/time'
import { useMe } from './me'
export default function Search ({ sub }) {
const router = useRouter()
const [q, setQ] = useState(router.query.q || '')
const inputRef = useRef(null)
const me = useMe()
useEffect(() => {
inputRef.current?.focus()
@ -52,7 +50,6 @@ export default function Search ({ sub }) {
const what = router.pathname.startsWith('/stackers') ? 'stackers' : router.query.what || 'all'
const sort = router.query.sort || 'zaprank'
const when = router.query.when || 'forever'
const whatItemOptions = useMemo(() => (['all', 'posts', 'comments', me ? 'bookmarks' : undefined, 'stackers'].filter(item => !!item)), [me])
return (
<>
@ -89,7 +86,7 @@ export default function Search ({ sub }) {
name='what'
size='sm'
overrideValue={what}
items={whatItemOptions}
items={['all', 'posts', 'comments', 'stackers']}
/>
{what !== 'stackers' &&
<>

View File

@ -110,7 +110,7 @@ export const ToastProvider = ({ children }) => {
// Only clear toasts with no cancel function on page navigation
// since navigation should not interfere with being able to cancel an action.
useEffect(() => {
const handleRouteChangeStart = () => setToasts(toasts => toasts.length > 0 ? toasts.filter(({ onCancel, onUndo, persistOnNavigate }) => onCancel || onUndo || persistOnNavigate) : toasts)
const handleRouteChangeStart = () => setToasts(toasts => toasts.length > 0 ? toasts.filter(({ onCancel, onUndo }) => onCancel || onUndo) : toasts)
router.events.on('routeChangeStart', handleRouteChangeStart)
return () => {

View File

@ -40,7 +40,6 @@ export const toastDeleteScheduled = (toaster, upsertResponseData, dataKey, isEdi
}[dataKey] ?? 'item'
const message = `${itemType === 'comment' ? 'your comment' : isEdit ? `this ${itemType}` : `your new ${itemType}`} will be deleted at ${deleteScheduledAt.toLocaleString()}`
// only persist this on navigation for posts, not comments
toaster.success(message, { persistOnNavigate: itemType !== 'comment' })
toaster.success(message)
}
}

View File

@ -1,5 +1,5 @@
import { getGetServerSideProps } from '@/api/ssrApollo'
import { Form, ClientInput, ClientCheckbox, PasswordInput } from '@/components/form'
import { Form, ClientInput, ClientCheckbox } from '@/components/form'
import { CenterLayout } from '@/components/layout'
import { WalletButtonBar, WalletCard } from '@/components/wallet-card'
import { lnbitsSchema } from '@/lib/validate'
@ -51,12 +51,12 @@ export default function LNbits () {
required
autoFocus
/>
<PasswordInput
<ClientInput
initialValue={adminKey}
type='password'
autoComplete='false'
label='admin key'
name='adminKey'
newPass
required
/>
<ClientCheckbox
disabled={!enabled || isDefault || enabledProviders.length === 1}

View File

@ -1,5 +1,5 @@
import { getGetServerSideProps } from '@/api/ssrApollo'
import { Form, ClientCheckbox, PasswordInput } from '@/components/form'
import { Form, ClientInput, ClientCheckbox } from '@/components/form'
import { CenterLayout } from '@/components/layout'
import { WalletButtonBar, WalletCard } from '@/components/wallet-card'
import { nwcSchema } from '@/lib/validate'
@ -43,11 +43,12 @@ export default function NWC () {
}
}}
>
<PasswordInput
<ClientInput
initialValue={nwcUrl}
label='connection'
name='nwcUrl'
newPass
type='password'
autoComplete='new-password'
required
autoFocus
/>

View File

@ -1,46 +0,0 @@
CREATE OR REPLACE FUNCTION index_bookmarked_item() RETURNS TRIGGER AS $$
BEGIN
-- if a bookmark was created or updated, `NEW` will be used
IF NEW IS NOT NULL THEN
INSERT INTO pgboss.job (name, data) VALUES ('indexItem', jsonb_build_object('id', NEW."itemId"));
RETURN NEW;
END IF;
-- if a bookmark was deleted, `OLD` will be used
IF OLD IS NOT NULL THEN
-- include `updatedAt` in the `indexItem` job as `now()` to indicate when the indexed item should think it was updated
-- this is to facilitate the fact that deleted bookmarks do not show up when re-indexing the item, and therefore
-- we don't have a reliable way to calculate a more recent index version, to displace the prior version
INSERT INTO pgboss.job (name, data) VALUES ('indexItem', jsonb_build_object('id', OLD."itemId", 'updatedAt', now()));
RETURN OLD;
END IF;
-- This should never be reached
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- Re-index the bookmarked item when a bookmark changes, so new bookmarks are searchable
DROP TRIGGER IF EXISTS index_bookmarked_item ON "Bookmark";
CREATE TRIGGER index_bookmarked_item
AFTER INSERT OR UPDATE OR DELETE ON "Bookmark"
FOR EACH ROW
EXECUTE PROCEDURE index_bookmarked_item();
-- hack ... prisma doesn't know about our other schemas (e.g. pgboss)
-- and this is only really a problem on their "shadow database"
-- so we catch the exception it throws and ignore it
CREATE OR REPLACE FUNCTION reindex_all_current_bookmarked_items() RETURNS void AS $$
BEGIN
-- Re-index all existing bookmarked items so these bookmarks are searchable
INSERT INTO pgboss.job (name, data, priority, startafter, expirein)
SELECT 'indexItem', jsonb_build_object('id', "itemId"), -100, now() + interval '10 minutes', interval '1 day'
FROM "Bookmark"
GROUP BY "itemId";
EXCEPTION WHEN OTHERS THEN
-- catch the exception for prisma dev execution, but do nothing with it
END;
$$ LANGUAGE plpgsql;
-- execute the function once
SELECT reindex_all_current_bookmarked_items();
-- then drop it since we don't need it anymore
DROP FUNCTION reindex_all_current_bookmarked_items();

View File

@ -171,8 +171,9 @@ Have a great weekend!
##### Top Posts
${top.data.items.items.slice(0, 10).map((item, i) =>
`${i + 1}. [${item.title}](https://stacker.news/items/${item.id})
- ${abbrNum(item.sats)} sats${item.boost ? ` \\ ${abbrNum(item.boost)} boost` : ''} \\ ${item.ncomments} comments \\ [@${item.user.name}](https://stacker.news/${item.user.name})\n`).join('')}
`${i + 1}. [@${item.user.name}](https://stacker.news/${item.user.name}) [${item.title}](https://stacker.news/items/${item.id})
- [${item.title}](https://stacker.news/items/${item.id})
- ${abbrNum(item.sats)} sats${item.boost ? ` \\ ${abbrNum(item.boost)} boost` : ''} \\ ${item.ncomments} comments \\ [@${item.user.name}](https://stacker.news/${item.user.name})\n`).join('')}
##### Don't miss
${top.data.items.items.map((item, i) =>

7
sndev
View File

@ -13,12 +13,7 @@ docker__compose() {
COMPOSE_PROFILES="images,search,payments,email,capture"
fi
ENV_LOCAL=
if [ -f .env.local ]; then
ENV_LOCAL='--env-file .env.local'
fi
CURRENT_UID=$(id -u) CURRENT_GID=$(id -g) COMPOSE_PROFILES=$COMPOSE_PROFILES command docker compose --env-file .env.development $ENV_LOCAL "$@"
CURRENT_UID=$(id -u) CURRENT_GID=$(id -g) COMPOSE_PROFILES=$COMPOSE_PROFILES command docker compose --env-file .env.development --env-file .env.local "$@"
}
docker__exec() {

View File

@ -35,7 +35,7 @@ const ITEM_SEARCH_FIELDS = gql`
ncomments
}`
async function _indexItem (item, { models, updatedAt }) {
async function _indexItem (item, { models }) {
console.log('indexing item', item.id)
// HACK: modify the title for jobs so that company/location are searchable
// and highlighted without further modification
@ -60,33 +60,11 @@ async function _indexItem (item, { models, updatedAt }) {
itemcp.wvotes = itemdb.weightedVotes - itemdb.weightedDownVotes
const bookmarkedBy = await models.bookmark.findMany({
where: { itemId: Number(item.id) },
select: { userId: true, createdAt: true },
orderBy: [
{
createdAt: 'desc'
}
]
})
itemcp.bookmarkedBy = bookmarkedBy.map(bookmark => bookmark.userId)
// use the latest of:
// 1. an explicitly-supplied updatedAt value, used when a bookmark to this item was removed
// 2. when the item itself was updated
// 3. or when it was last bookmarked
// to determine the latest version of the indexed version
const latestUpdatedAt = Math.max(
updatedAt ? new Date(updatedAt).getTime() : 0,
new Date(item.updatedAt).getTime(),
bookmarkedBy[0] ? new Date(bookmarkedBy[0].createdAt).getTime() : 0
)
try {
await search.index({
id: item.id,
index: process.env.OPENSEARCH_INDEX,
version: new Date(latestUpdatedAt).getTime(),
version: new Date(item.updatedAt).getTime(),
versionType: 'external_gte',
body: itemcp
})
@ -101,9 +79,7 @@ async function _indexItem (item, { models, updatedAt }) {
}
}
// `data.updatedAt` is an explicit updatedAt value for the use case of a bookmark being removed
// this is consulted to generate the index version
export async function indexItem ({ data: { id, updatedAt }, apollo, models }) {
export async function indexItem ({ data: { id }, apollo, models }) {
// 1. grab item from database
// could use apollo to avoid duping logic
// when grabbing sats and user name, etc
@ -118,7 +94,7 @@ export async function indexItem ({ data: { id, updatedAt }, apollo, models }) {
})
// 2. index it with external version based on updatedAt
await _indexItem(item, { models, updatedAt })
await _indexItem(item, { models })
}
export async function indexAllItems ({ apollo, models }) {