This day on ... automated post (#1273)
* add this day posting job * put in proper timezone * make sure we're in central timezone * schedule thisDay job
This commit is contained in:
parent
7fd4f58e81
commit
c20a954cfc
|
@ -1328,7 +1328,7 @@ export const createItem = async (parent, { forward, ...item }, { me, models, lnd
|
|||
return resultItem
|
||||
}
|
||||
|
||||
const getForwardUsers = async (models, forward) => {
|
||||
export const getForwardUsers = async (models, forward) => {
|
||||
const fwdUsers = []
|
||||
if (forward) {
|
||||
// find all users in one db query
|
||||
|
|
|
@ -1,215 +0,0 @@
|
|||
import { getGetServerSideProps } from '@/api/ssrApollo'
|
||||
import Layout from '@/components/layout'
|
||||
import { datePivot, dayMonthYearToDate } from '@/lib/time'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { numWithUnits, suffix, abbrNum } from '@/lib/format'
|
||||
import PageLoading from '@/components/page-loading'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
// force SSR to include CSP nonces
|
||||
export const getServerSideProps = getGetServerSideProps({ query: null })
|
||||
|
||||
const THIS_DAY = gql`
|
||||
query thisDay($to: String, $from: String) {
|
||||
posts: items (sort: "top", when: "custom", from: $from, to: $to, limit: 1) {
|
||||
items {
|
||||
id
|
||||
title
|
||||
text
|
||||
url
|
||||
ncomments
|
||||
sats
|
||||
boost
|
||||
subName
|
||||
user {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
comments: items (sort: "top", type: "comments", when: "custom", from: $from, to: $to, limit: 1) {
|
||||
items {
|
||||
id
|
||||
parentId
|
||||
text
|
||||
ncomments
|
||||
sats
|
||||
boost
|
||||
user {
|
||||
name
|
||||
}
|
||||
root {
|
||||
title
|
||||
id
|
||||
subName
|
||||
user {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
users: topUsers(when: "custom", from: $from, to: $to) {
|
||||
users {
|
||||
name
|
||||
nposts(when: "custom", from: $from, to: $to)
|
||||
ncomments(when: "custom", from: $from, to: $to)
|
||||
optional {
|
||||
stacked(when: "custom", from: $from, to: $to)
|
||||
spent(when: "custom", from: $from, to: $to)
|
||||
referrals(when: "custom", from: $from, to: $to)
|
||||
}
|
||||
}
|
||||
}
|
||||
territories: topSubs(when: "custom", from: $from, to: $to, limit: 1) {
|
||||
subs {
|
||||
name
|
||||
createdAt
|
||||
desc
|
||||
user {
|
||||
name
|
||||
id
|
||||
optional {
|
||||
streak
|
||||
}
|
||||
}
|
||||
ncomments(when: "custom", from: $from, to: $to)
|
||||
nposts(when: "custom", from: $from, to: $to)
|
||||
|
||||
optional {
|
||||
stacked(when: "custom", from: $from, to: $to)
|
||||
spent(when: "custom", from: $from, to: $to)
|
||||
revenue(when: "custom", from: $from, to: $to)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Index () {
|
||||
const router = useRouter()
|
||||
const days = []
|
||||
let day = router.query.day
|
||||
? datePivot(dayMonthYearToDate(router.query.day), { years: -1 })
|
||||
: datePivot(new Date(), { years: -1 })
|
||||
while (day > new Date('2021-06-10')) {
|
||||
days.push(day)
|
||||
day = datePivot(day, { years: -1 })
|
||||
}
|
||||
|
||||
const sep = `
|
||||
https://imgprxy.stacker.news/fsFoWlgwKYsk5mxx2ijgqU8fg04I_2zA_D28t_grR74/rs:fit:960:540/aHR0cHM6Ly9tLnN0YWNrZXIubmV3cy8yMzc5Ng
|
||||
`
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<code style={{ whiteSpace: 'pre-line' }}>
|
||||
* * -
|
||||
{days
|
||||
.map(day => <ThisDay key={day} day={day} />)
|
||||
.reduce((acc, x) => acc === null
|
||||
? [x]
|
||||
: [acc, sep, x], null)}
|
||||
</code>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
function ThisDay ({ day }) {
|
||||
const [from, to] = [
|
||||
String(new Date(new Date(day).setHours(0, 0, 0, 0)).getTime()),
|
||||
String(new Date(new Date(day).setHours(23, 59, 59, 999)).getTime())]
|
||||
|
||||
const { data } = useQuery(THIS_DAY, { variables: { from, to } })
|
||||
|
||||
if (!data) return <PageLoading />
|
||||
|
||||
return `
|
||||
### ${day.toLocaleString('default', { month: 'long', day: 'numeric', year: 'numeric' })} 📅
|
||||
|
||||
----
|
||||
|
||||
### 📝 \`TOP POST\`
|
||||
|
||||
${topPost(data.posts.items)}
|
||||
|
||||
----
|
||||
|
||||
### 💬 \`TOP COMMENT\`
|
||||
|
||||
${topComment(data.comments.items)}
|
||||
|
||||
----
|
||||
|
||||
### 🏆 \`TOP STACKER\`
|
||||
|
||||
${topStacker(data.users.users)}
|
||||
|
||||
----
|
||||
|
||||
### 🗺️ \`TOP TERRITORY\`
|
||||
|
||||
${topTerritory(data.territories.subs)}
|
||||
`
|
||||
}
|
||||
|
||||
const truncateString = (string = '', maxLength = 140) =>
|
||||
string.length > maxLength
|
||||
? `${string.substring(0, 250)} […]`
|
||||
: string
|
||||
|
||||
function topPost (posts) {
|
||||
const post = posts?.[0]
|
||||
|
||||
if (!post) return 'No top post'
|
||||
|
||||
return `**[${post.title}](https://stacker.news/items/${post.id}/r/Undisciplined)**
|
||||
${post.text
|
||||
? `
|
||||
#### Excerpt
|
||||
> ${truncateString(post.text)}`
|
||||
: ''}
|
||||
*${numWithUnits(post.sats)} \\ ${numWithUnits(post.ncomments, { unitSingular: 'comment', unitPlural: 'comments' })} \\ @${post.user.name} \\ ~${post.subName}*`
|
||||
}
|
||||
|
||||
function topComment (comments) {
|
||||
const comment = comments?.[0]
|
||||
|
||||
if (!comment) return 'No top comment'
|
||||
|
||||
return `**https://stacker.news/items/${comment.root.id}/r/Undisciplined?commentId=${comment.id}**
|
||||
|
||||
${comment.text
|
||||
? `
|
||||
#### Excerpt
|
||||
> ${truncateString(comment.text)}`
|
||||
: ''}
|
||||
|
||||
*${numWithUnits(comment.sats)} \\ ${numWithUnits(comment.ncomments, { unitSingular: 'reply', unitPlural: 'replies' })} \\ @${comment.user.name}*
|
||||
|
||||
From **[${comment.root.title}](https://stacker.news/items/${comment.root.id}/r/Undisciplined)** by @${comment.root.user.name} in ~${comment.root.subName}`
|
||||
}
|
||||
|
||||
function topStacker (users) {
|
||||
const userIdx = users.findIndex(u => !!u)
|
||||
|
||||
if (userIdx === -1) return 'No top stacker'
|
||||
const user = users[userIdx]
|
||||
|
||||
return `${suffix(userIdx + 1)} place **@${user.name}** ${userIdx > 0 ? `(1st${userIdx > 1 ? `-${suffix(userIdx - 1)}` : ''} hiding)` : ''}
|
||||
|
||||
*${abbrNum(user.optional?.stacked)} stacked \\ ${abbrNum(user.optional?.spent)} spent \\ ${numWithUnits(user.nposts, { unitSingular: 'post', unitPlural: 'posts' })} \\ ${numWithUnits(user.ncomments, { unitSingular: 'comment', unitPlural: 'comments' })} \\ ${numWithUnits(user.optional.referrals, { unitSingular: 'referral', unitPlural: 'referrals' })}*`
|
||||
}
|
||||
|
||||
function topTerritory (subs) {
|
||||
const sub = subs?.[0]
|
||||
|
||||
if (!sub) return 'No top territory'
|
||||
|
||||
return `**~${sub.name}**
|
||||
${sub.desc
|
||||
? `> ${truncateString(sub.desc)}`
|
||||
: ''}
|
||||
|
||||
founded by @${sub.user.name} on ${new Date(sub.createdAt).toDateString()}
|
||||
|
||||
*${abbrNum(sub.optional?.stacked)} stacked \\ ${abbrNum(sub.optional?.revenue)} revenue \\ ${abbrNum(sub.optional?.spent)} spent \\ ${numWithUnits(sub.nposts, { unitSingular: 'post', unitPlural: 'posts' })} \\ ${numWithUnits(sub.ncomments, { unitSingular: 'comment', unitPlural: 'comments' })}*`
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
export { default, getServerSideProps } from './[day].js'
|
|
@ -0,0 +1,16 @@
|
|||
CREATE OR REPLACE FUNCTION schedule_this_day_job()
|
||||
RETURNS INTEGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
BEGIN
|
||||
INSERT INTO pgboss.schedule (name, cron, timezone)
|
||||
VALUES ('thisDay', '0 5 * * *', 'America/Chicago') ON CONFLICT DO NOTHING;
|
||||
return 0;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
return 0;
|
||||
END;
|
||||
$$;
|
||||
|
||||
SELECT schedule_this_day_job();
|
||||
DROP FUNCTION IF EXISTS schedule_this_day_job;
|
|
@ -26,6 +26,7 @@ import { autoWithdraw } from './autowithdraw.js'
|
|||
import { saltAndHashEmails } from './saltAndHashEmails.js'
|
||||
import { remindUser } from './reminder.js'
|
||||
import { holdAction, settleAction, settleActionError } from './paidAction.js'
|
||||
import { thisDay } from './thisDay.js'
|
||||
|
||||
const { loadEnvConfig } = nextEnv
|
||||
const { ApolloClient, HttpLink, InMemoryCache } = apolloClient
|
||||
|
@ -111,6 +112,7 @@ async function work () {
|
|||
await boss.work('settleAction', jobWrapper(settleAction))
|
||||
await boss.work('holdAction', jobWrapper(holdAction))
|
||||
await boss.work('checkInvoice', jobWrapper(checkInvoice))
|
||||
await boss.work('thisDay', jobWrapper(thisDay))
|
||||
|
||||
console.log('working jobs')
|
||||
}
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
import { datePivot } from '@/lib/time'
|
||||
import gql from 'graphql-tag'
|
||||
import { numWithUnits, abbrNum } from '@/lib/format'
|
||||
import { paidActions } from '@/api/paidAction'
|
||||
import { USER_ID } from '@/lib/constants'
|
||||
import { getForwardUsers } from '@/api/resolvers/item'
|
||||
|
||||
export async function thisDay ({ models, apollo }) {
|
||||
const days = []
|
||||
let yearsAgo = 1
|
||||
while (datePivot(new Date(), { years: -yearsAgo }) > new Date('2021-06-10')) {
|
||||
const [{ from, to }] = await models.$queryRaw`
|
||||
SELECT (date_trunc('day', (now() AT TIME ZONE 'America/Chicago')) AT TIME ZONE 'America/Chicago') - ${`${yearsAgo} year`}::interval as from,
|
||||
(date_trunc('day', (now() AT TIME ZONE 'America/Chicago')) AT TIME ZONE 'America/Chicago') - ${`${yearsAgo} year`}::interval + interval '1 day - 1 second' as to`
|
||||
|
||||
const { data } = await apollo.query({
|
||||
query: THIS_DAY,
|
||||
variables: { from: new Date(from).getTime().toString(), to: new Date(to).getTime().toString() }
|
||||
})
|
||||
|
||||
days.push({
|
||||
data,
|
||||
day: new Date(from).toLocaleString('default', { timeZone: 'America/Chicago', month: 'long', day: 'numeric', year: 'numeric' })
|
||||
})
|
||||
|
||||
yearsAgo++
|
||||
}
|
||||
|
||||
const date = new Date().toLocaleString('default', { timeZone: 'America/Chicago', month: 'long', day: 'numeric' })
|
||||
|
||||
const text = `${topPosts(days)}
|
||||
${topStackers(days)}
|
||||
${topComments(days)}
|
||||
${topSubs(days)}`
|
||||
|
||||
const user = await models.user.findUnique({ where: { id: USER_ID.sn } })
|
||||
const forward = days.map(({ data }) => data.users.users?.[0]?.name).filter(Boolean).map(name => ({ nym: name, pct: 10 }))
|
||||
forward.push({ nym: 'Undisciplined', pct: 50 })
|
||||
const forwardUsers = await getForwardUsers(models, forward)
|
||||
await models.$transaction(async tx => {
|
||||
const context = { tx, cost: BigInt(1), user, models }
|
||||
const result = await paidActions.ITEM_CREATE.perform({
|
||||
text, title: `This Day on SN: ${date}`, subName: 'meta', userId: USER_ID.sn, forwardUsers
|
||||
}, context)
|
||||
await paidActions.ITEM_CREATE.onPaid(result, context)
|
||||
})
|
||||
}
|
||||
|
||||
function topPosts (days) {
|
||||
let text = '#### Top Posts'
|
||||
for (const { day, data } of days) {
|
||||
const post = data.posts.items?.[0]
|
||||
if (post) {
|
||||
text += `
|
||||
- [${post.title}](${process.env.NEXT_PUBLIC_URL}/items/${post.id})
|
||||
- ${numWithUnits(post.sats)} \\ ${numWithUnits(post.ncomments, { unitSingular: 'comment', unitPlural: 'comments' })} \\ @${post.user.name} \\ ~${post.subName} \\ \`${day}\``
|
||||
} else {
|
||||
text += `
|
||||
- no top post for \`${day}\``
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
function topStackers (days) {
|
||||
let text = '#### Top Stackers'
|
||||
for (const { day, data } of days) {
|
||||
const user = data.users.users?.[0]
|
||||
if (user) {
|
||||
text += `
|
||||
- @${user.name}
|
||||
- ${abbrNum(user.optional?.stacked)} stacked \\ ${abbrNum(user.optional?.spent)} spent \\ ${numWithUnits(user.nposts, { unitSingular: 'post', unitPlural: 'posts' })} \\ ${numWithUnits(user.ncomments, { unitSingular: 'comment', unitPlural: 'comments' })} \\ \`${day}\``
|
||||
} else {
|
||||
text += `
|
||||
- stacker is in hiding for \`${day}\``
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
function topComments (days) {
|
||||
let text = '#### Top Comments'
|
||||
for (const { day, data } of days) {
|
||||
const comment = data.comments.items?.[0]
|
||||
if (comment) {
|
||||
text += `
|
||||
- ${process.env.NEXT_PUBLIC_URL}/items/${comment.root.id}?commentId=${comment.id} on [${comment.root.title}](${process.env.NEXT_PUBLIC_URL}/items/${comment.root.id})
|
||||
- ${numWithUnits(comment.sats)} \\ ${numWithUnits(comment.ncomments, { unitSingular: 'reply', unitPlural: 'replies' })} \\ @${comment.user.name} \\ \`${day}\`
|
||||
> ${comment.text.trim().split('\n')[0]} [...]`
|
||||
} else {
|
||||
text += `
|
||||
- no top comment for \`${day}\``
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
function topSubs (days) {
|
||||
let text = '#### Top Territories'
|
||||
for (const { day, data } of days) {
|
||||
const sub = data.territories.subs?.[0]
|
||||
if (sub) {
|
||||
text += `
|
||||
- ~${sub.name}
|
||||
- ${abbrNum(sub.optional?.stacked)} stacked \\ ${abbrNum(sub.optional?.revenue)} revenue \\ ${abbrNum(sub.optional?.spent)} spent \\ ${numWithUnits(sub.nposts, { unitSingular: 'post', unitPlural: 'posts' })} \\ ${numWithUnits(sub.ncomments, { unitSingular: 'comment', unitPlural: 'comments' })} \\ \`${day}\``
|
||||
} else {
|
||||
text += `
|
||||
- no top territory for \`${day}\``
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
const THIS_DAY = gql`
|
||||
query thisDay($to: String, $from: String) {
|
||||
posts: items (sort: "top", when: "custom", from: $from, to: $to, limit: 1) {
|
||||
items {
|
||||
id
|
||||
title
|
||||
text
|
||||
url
|
||||
ncomments
|
||||
sats
|
||||
boost
|
||||
subName
|
||||
user {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
comments: items (sort: "top", type: "comments", when: "custom", from: $from, to: $to, limit: 1) {
|
||||
items {
|
||||
id
|
||||
parentId
|
||||
text
|
||||
ncomments
|
||||
sats
|
||||
boost
|
||||
user {
|
||||
name
|
||||
}
|
||||
root {
|
||||
title
|
||||
id
|
||||
subName
|
||||
user {
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
users: topUsers(when: "custom", from: $from, to: $to, limit: 1) {
|
||||
users {
|
||||
name
|
||||
nposts(when: "custom", from: $from, to: $to)
|
||||
ncomments(when: "custom", from: $from, to: $to)
|
||||
optional {
|
||||
stacked(when: "custom", from: $from, to: $to)
|
||||
spent(when: "custom", from: $from, to: $to)
|
||||
referrals(when: "custom", from: $from, to: $to)
|
||||
}
|
||||
}
|
||||
}
|
||||
territories: topSubs(when: "custom", from: $from, to: $to, limit: 1) {
|
||||
subs {
|
||||
name
|
||||
createdAt
|
||||
desc
|
||||
user {
|
||||
name
|
||||
id
|
||||
optional {
|
||||
streak
|
||||
}
|
||||
}
|
||||
ncomments(when: "custom", from: $from, to: $to)
|
||||
nposts(when: "custom", from: $from, to: $to)
|
||||
|
||||
optional {
|
||||
stacked(when: "custom", from: $from, to: $to)
|
||||
spent(when: "custom", from: $from, to: $to)
|
||||
revenue(when: "custom", from: $from, to: $to)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
Loading…
Reference in New Issue