wild west mode
This commit is contained in:
parent
14ce36c1cb
commit
7faae425b3
@ -4,20 +4,23 @@ import serialize from './serial'
|
|||||||
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
||||||
import { getMetadata, metadataRuleSets } from 'page-metadata-parser'
|
import { getMetadata, metadataRuleSets } from 'page-metadata-parser'
|
||||||
import domino from 'domino'
|
import domino from 'domino'
|
||||||
import { BOOST_MIN, ITEM_SPAM_INTERVAL, MAX_POLL_NUM_CHOICES, MAX_TITLE_LENGTH } from '../../lib/constants'
|
import {
|
||||||
|
BOOST_MIN, ITEM_SPAM_INTERVAL, MAX_POLL_NUM_CHOICES,
|
||||||
|
MAX_TITLE_LENGTH, ITEM_FILTER_THRESHOLD, DONT_LIKE_THIS_COST
|
||||||
|
} from '../../lib/constants'
|
||||||
import { mdHas } from '../../lib/md'
|
import { mdHas } from '../../lib/md'
|
||||||
|
|
||||||
async function comments (models, id, sort) {
|
async function comments (me, models, id, sort) {
|
||||||
let orderBy
|
let orderBy
|
||||||
switch (sort) {
|
switch (sort) {
|
||||||
case 'top':
|
case 'top':
|
||||||
orderBy = 'ORDER BY "Item"."weightedVotes" DESC, "Item".id DESC'
|
orderBy = `ORDER BY ${await orderByNumerator(me, models)} DESC, "Item".id DESC`
|
||||||
break
|
break
|
||||||
case 'recent':
|
case 'recent':
|
||||||
orderBy = 'ORDER BY "Item".created_at DESC, "Item".id DESC'
|
orderBy = 'ORDER BY "Item".created_at DESC, "Item".id DESC'
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
orderBy = COMMENTS_ORDER_BY_SATS
|
orderBy = `ORDER BY ${await orderByNumerator(me, models)}/POWER(EXTRACT(EPOCH FROM ((NOW() AT TIME ZONE 'UTC') - "Item".created_at))/3600+2, 1.3) DESC NULLS LAST, "Item".id DESC`
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,18 +29,18 @@ async function comments (models, id, sort) {
|
|||||||
${SELECT}, ARRAY[row_number() OVER (${orderBy}, "Item".path)] AS sort_path
|
${SELECT}, ARRAY[row_number() OVER (${orderBy}, "Item".path)] AS sort_path
|
||||||
FROM "Item"
|
FROM "Item"
|
||||||
WHERE "parentId" = $1
|
WHERE "parentId" = $1
|
||||||
|
${await filterClause(me, models)}
|
||||||
UNION ALL
|
UNION ALL
|
||||||
${SELECT}, p.sort_path || row_number() OVER (${orderBy}, "Item".path)
|
${SELECT}, p.sort_path || row_number() OVER (${orderBy}, "Item".path)
|
||||||
FROM base p
|
FROM base p
|
||||||
JOIN "Item" ON "Item"."parentId" = p.id)
|
JOIN "Item" ON "Item"."parentId" = p.id
|
||||||
|
WHERE true
|
||||||
|
${await filterClause(me, models)})
|
||||||
SELECT * FROM base ORDER BY sort_path`, Number(id))
|
SELECT * FROM base ORDER BY sort_path`, Number(id))
|
||||||
return nestComments(flat, id)[0]
|
return nestComments(flat, id)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
const COMMENTS_ORDER_BY_SATS =
|
export async function getItem (parent, { id }, { me, models }) {
|
||||||
'ORDER BY POWER("Item"."weightedVotes", 1.2)/POWER(EXTRACT(EPOCH FROM ((NOW() AT TIME ZONE \'UTC\') - "Item".created_at))/3600+2, 1.3) DESC NULLS LAST, "Item".id DESC'
|
|
||||||
|
|
||||||
export async function getItem (parent, { id }, { models }) {
|
|
||||||
const [item] = await models.$queryRaw(`
|
const [item] = await models.$queryRaw(`
|
||||||
${SELECT}
|
${SELECT}
|
||||||
FROM "Item"
|
FROM "Item"
|
||||||
@ -67,6 +70,38 @@ function topClause (within) {
|
|||||||
return interval
|
return interval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function orderByNumerator (me, models) {
|
||||||
|
if (me) {
|
||||||
|
const user = await models.user.findUnique({ where: { id: me.id } })
|
||||||
|
if (user.wildWestMode) {
|
||||||
|
return 'GREATEST("Item"."weightedVotes", POWER("Item"."weightedVotes", 1.2))'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `(CASE WHEN "Item"."weightedVotes" > "Item"."weightedDownVotes"
|
||||||
|
THEN 1
|
||||||
|
ELSE -1 END
|
||||||
|
* GREATEST(ABS("Item"."weightedVotes" - "Item"."weightedDownVotes"), POWER(ABS("Item"."weightedVotes" - "Item"."weightedDownVotes"), 1.2)))`
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function filterClause (me, models) {
|
||||||
|
if (me) {
|
||||||
|
const user = await models.user.findUnique({ where: { id: me.id } })
|
||||||
|
if (user.wildWestMode) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the item is above the threshold or is mine
|
||||||
|
let clause = ` AND ("Item"."weightedVotes" - "Item"."weightedDownVotes" > -${ITEM_FILTER_THRESHOLD}`
|
||||||
|
if (me) {
|
||||||
|
clause += ` OR "Item"."userId" = ${me.id}`
|
||||||
|
}
|
||||||
|
clause += ')'
|
||||||
|
|
||||||
|
return clause
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Query: {
|
Query: {
|
||||||
itemRepetition: async (parent, { parentId }, { me, models }) => {
|
itemRepetition: async (parent, { parentId }, { me, models }) => {
|
||||||
@ -106,6 +141,7 @@ export default {
|
|||||||
WHERE "userId" = $1 AND "parentId" IS NULL AND created_at <= $2
|
WHERE "userId" = $1 AND "parentId" IS NULL AND created_at <= $2
|
||||||
AND "pinId" IS NULL
|
AND "pinId" IS NULL
|
||||||
${activeOrMine()}
|
${activeOrMine()}
|
||||||
|
${await filterClause(me, models)}
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
OFFSET $3
|
OFFSET $3
|
||||||
LIMIT ${LIMIT}`, user.id, decodedCursor.time, decodedCursor.offset)
|
LIMIT ${LIMIT}`, user.id, decodedCursor.time, decodedCursor.offset)
|
||||||
@ -117,6 +153,7 @@ export default {
|
|||||||
WHERE "parentId" IS NULL AND created_at <= $1
|
WHERE "parentId" IS NULL AND created_at <= $1
|
||||||
${subClause(3)}
|
${subClause(3)}
|
||||||
${activeOrMine()}
|
${activeOrMine()}
|
||||||
|
${await filterClause(me, models)}
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
OFFSET $2
|
OFFSET $2
|
||||||
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, sub || 'NULL')
|
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, sub || 'NULL')
|
||||||
@ -128,7 +165,8 @@ export default {
|
|||||||
WHERE "parentId" IS NULL AND "Item".created_at <= $1
|
WHERE "parentId" IS NULL AND "Item".created_at <= $1
|
||||||
AND "pinId" IS NULL
|
AND "pinId" IS NULL
|
||||||
${topClause(within)}
|
${topClause(within)}
|
||||||
${TOP_ORDER_BY_SATS}
|
${await filterClause(me, models)}
|
||||||
|
${await topOrderByWeightedSats(me, models)}
|
||||||
OFFSET $2
|
OFFSET $2
|
||||||
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
|
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
|
||||||
break
|
break
|
||||||
@ -179,7 +217,8 @@ export default {
|
|||||||
WHERE "parentId" IS NULL AND "Item".created_at <= $1 AND "Item".created_at > $3
|
WHERE "parentId" IS NULL AND "Item".created_at <= $1 AND "Item".created_at > $3
|
||||||
AND "pinId" IS NULL
|
AND "pinId" IS NULL
|
||||||
${subClause(4)}
|
${subClause(4)}
|
||||||
${newTimedOrderByWeightedSats(1)}
|
${await filterClause(me, models)}
|
||||||
|
${await newTimedOrderByWeightedSats(me, models, 1)}
|
||||||
OFFSET $2
|
OFFSET $2
|
||||||
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, new Date(new Date().setDate(new Date().getDate() - 5)), sub || 'NULL')
|
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, new Date(new Date().setDate(new Date().getDate() - 5)), sub || 'NULL')
|
||||||
}
|
}
|
||||||
@ -191,7 +230,8 @@ export default {
|
|||||||
WHERE "parentId" IS NULL AND "Item".created_at <= $1
|
WHERE "parentId" IS NULL AND "Item".created_at <= $1
|
||||||
AND "pinId" IS NULL
|
AND "pinId" IS NULL
|
||||||
${subClause(3)}
|
${subClause(3)}
|
||||||
${newTimedOrderByWeightedSats(1)}
|
${await filterClause(me, models)}
|
||||||
|
${await newTimedOrderByWeightedSats(me, models, 1)}
|
||||||
OFFSET $2
|
OFFSET $2
|
||||||
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, sub || 'NULL')
|
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset, sub || 'NULL')
|
||||||
}
|
}
|
||||||
@ -219,11 +259,12 @@ export default {
|
|||||||
pins
|
pins
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
allItems: async (parent, { cursor }, { models }) => {
|
allItems: async (parent, { cursor }, { me, models }) => {
|
||||||
const decodedCursor = decodeCursor(cursor)
|
const decodedCursor = decodeCursor(cursor)
|
||||||
const items = await models.$queryRaw(`
|
const items = await models.$queryRaw(`
|
||||||
${SELECT}
|
${SELECT}
|
||||||
FROM "Item"
|
FROM "Item"
|
||||||
|
${await filterClause(me, models)}
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
OFFSET $1
|
OFFSET $1
|
||||||
LIMIT ${LIMIT}`, decodedCursor.offset)
|
LIMIT ${LIMIT}`, decodedCursor.offset)
|
||||||
@ -242,6 +283,7 @@ export default {
|
|||||||
${SELECT}
|
${SELECT}
|
||||||
FROM "Item"
|
FROM "Item"
|
||||||
WHERE "parentId" IS NOT NULL AND created_at <= $1
|
WHERE "parentId" IS NOT NULL AND created_at <= $1
|
||||||
|
${await filterClause(me, models)}
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
OFFSET $2
|
OFFSET $2
|
||||||
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
|
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
|
||||||
@ -261,6 +303,7 @@ export default {
|
|||||||
FROM "Item"
|
FROM "Item"
|
||||||
WHERE "userId" = $1 AND "parentId" IS NOT NULL
|
WHERE "userId" = $1 AND "parentId" IS NOT NULL
|
||||||
AND created_at <= $2
|
AND created_at <= $2
|
||||||
|
${await filterClause(me, models)}
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
OFFSET $3
|
OFFSET $3
|
||||||
LIMIT ${LIMIT}`, user.id, decodedCursor.time, decodedCursor.offset)
|
LIMIT ${LIMIT}`, user.id, decodedCursor.time, decodedCursor.offset)
|
||||||
@ -272,7 +315,8 @@ export default {
|
|||||||
WHERE "parentId" IS NOT NULL
|
WHERE "parentId" IS NOT NULL
|
||||||
AND "Item".created_at <= $1
|
AND "Item".created_at <= $1
|
||||||
${topClause(within)}
|
${topClause(within)}
|
||||||
${TOP_ORDER_BY_SATS}
|
${await filterClause(me, models)}
|
||||||
|
${await topOrderByWeightedSats(me, models)}
|
||||||
OFFSET $2
|
OFFSET $2
|
||||||
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
|
LIMIT ${LIMIT}`, decodedCursor.time, decodedCursor.offset)
|
||||||
break
|
break
|
||||||
@ -322,8 +366,8 @@ export default {
|
|||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
LIMIT 3`, similar)
|
LIMIT 3`, similar)
|
||||||
},
|
},
|
||||||
comments: async (parent, { id, sort }, { models }) => {
|
comments: async (parent, { id, sort }, { me, models }) => {
|
||||||
return comments(models, id, sort)
|
return comments(me, models, id, sort)
|
||||||
},
|
},
|
||||||
search: async (parent, { q: query, sub, cursor }, { me, models, search }) => {
|
search: async (parent, { q: query, sub, cursor }, { me, models, search }) => {
|
||||||
const decodedCursor = decodeCursor(cursor)
|
const decodedCursor = decodeCursor(cursor)
|
||||||
@ -636,6 +680,25 @@ export default {
|
|||||||
vote,
|
vote,
|
||||||
sats
|
sats
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
dontLikeThis: async (parent, { id }, { me, models }) => {
|
||||||
|
// need to make sure we are logged in
|
||||||
|
if (!me) {
|
||||||
|
throw new AuthenticationError('you must be logged in')
|
||||||
|
}
|
||||||
|
|
||||||
|
// disallow self down votes
|
||||||
|
const [item] = await models.$queryRaw(`
|
||||||
|
${SELECT}
|
||||||
|
FROM "Item"
|
||||||
|
WHERE id = $1 AND "userId" = $2`, Number(id), me.id)
|
||||||
|
if (item) {
|
||||||
|
throw new UserInputError('cannot downvote your self')
|
||||||
|
}
|
||||||
|
|
||||||
|
await serialize(models, models.$queryRaw`SELECT item_act(${Number(id)}, ${me.id}, 'DONT_LIKE_THIS', ${DONT_LIKE_THIS_COST})`)
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
@ -710,11 +773,11 @@ export default {
|
|||||||
}
|
}
|
||||||
return await models.user.findUnique({ where: { id: item.fwdUserId } })
|
return await models.user.findUnique({ where: { id: item.fwdUserId } })
|
||||||
},
|
},
|
||||||
comments: async (item, args, { models }) => {
|
comments: async (item, args, { me, models }) => {
|
||||||
if (item.comments) {
|
if (item.comments) {
|
||||||
return item.comments
|
return item.comments
|
||||||
}
|
}
|
||||||
return comments(models, item.id, 'hot')
|
return comments(me, models, item.id, 'hot')
|
||||||
},
|
},
|
||||||
upvotes: async (item, args, { models }) => {
|
upvotes: async (item, args, { models }) => {
|
||||||
const { sum: { sats } } = await models.itemAct.aggregate({
|
const { sum: { sats } } = await models.itemAct.aggregate({
|
||||||
@ -768,6 +831,19 @@ export default {
|
|||||||
|
|
||||||
return sats || 0
|
return sats || 0
|
||||||
},
|
},
|
||||||
|
meDontLike: async (item, args, { me, models }) => {
|
||||||
|
if (!me) return false
|
||||||
|
|
||||||
|
const dontLike = await models.itemAct.findFirst({
|
||||||
|
where: {
|
||||||
|
itemId: Number(item.id),
|
||||||
|
userId: me.id,
|
||||||
|
act: 'DONT_LIKE_THIS'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return !!dontLike
|
||||||
|
},
|
||||||
mine: async (item, args, { me, models }) => {
|
mine: async (item, args, { me, models }) => {
|
||||||
return me?.id === item.userId
|
return me?.id === item.userId
|
||||||
},
|
},
|
||||||
@ -940,10 +1016,12 @@ export const SELECT =
|
|||||||
"Item"."subName", "Item".status, "Item"."uploadId", "Item"."pollCost", "Item"."paidImgLink",
|
"Item"."subName", "Item".status, "Item"."uploadId", "Item"."pollCost", "Item"."paidImgLink",
|
||||||
"Item".sats, "Item".ncomments, "Item"."commentSats", "Item"."lastCommentAt", ltree2text("Item"."path") AS "path"`
|
"Item".sats, "Item".ncomments, "Item"."commentSats", "Item"."lastCommentAt", ltree2text("Item"."path") AS "path"`
|
||||||
|
|
||||||
function newTimedOrderByWeightedSats (num) {
|
async function newTimedOrderByWeightedSats (me, models, num) {
|
||||||
return `
|
return `
|
||||||
ORDER BY (POWER("Item"."weightedVotes", 1.2)/POWER(EXTRACT(EPOCH FROM ($${num} - "Item".created_at))/3600+2, 1.3) +
|
ORDER BY (${await orderByNumerator(me, models)}/POWER(EXTRACT(EPOCH FROM ($${num} - "Item".created_at))/3600+2, 1.3) +
|
||||||
("Item".boost/${BOOST_MIN}::float)/POWER(EXTRACT(EPOCH FROM ($${num} - "Item".created_at))/3600+2, 2.6)) DESC NULLS LAST, "Item".id DESC`
|
("Item".boost/${BOOST_MIN}::float)/POWER(EXTRACT(EPOCH FROM ($${num} - "Item".created_at))/3600+2, 2.6)) DESC NULLS LAST, "Item".id DESC`
|
||||||
}
|
}
|
||||||
|
|
||||||
const TOP_ORDER_BY_SATS = 'ORDER BY "Item"."weightedVotes" DESC NULLS LAST, "Item".id DESC'
|
async function topOrderByWeightedSats (me, models) {
|
||||||
|
return `ORDER BY ${await orderByNumerator(me, models)} DESC NULLS LAST, "Item".id DESC`
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { AuthenticationError } from 'apollo-server-micro'
|
import { AuthenticationError } from 'apollo-server-micro'
|
||||||
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
||||||
import { getItem } from './item'
|
import { getItem, filterClause } from './item'
|
||||||
import { getInvoice } from './wallet'
|
import { getInvoice } from './wallet'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -76,7 +76,8 @@ export default {
|
|||||||
FROM "Item"
|
FROM "Item"
|
||||||
JOIN "Item" p ON ${meFull.noteAllDescendants ? '"Item".path <@ p.path' : '"Item"."parentId" = p.id'}
|
JOIN "Item" p ON ${meFull.noteAllDescendants ? '"Item".path <@ p.path' : '"Item"."parentId" = p.id'}
|
||||||
WHERE p."userId" = $1
|
WHERE p."userId" = $1
|
||||||
AND "Item"."userId" <> $1 AND "Item".created_at <= $2`
|
AND "Item"."userId" <> $1 AND "Item".created_at <= $2
|
||||||
|
${await filterClause(me, models)}`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
queries.push(
|
queries.push(
|
||||||
@ -86,6 +87,7 @@ export default {
|
|||||||
JOIN "Item" p ON ${meFull.noteAllDescendants ? '"Item".path <@ p.path' : '"Item"."parentId" = p.id'}
|
JOIN "Item" p ON ${meFull.noteAllDescendants ? '"Item".path <@ p.path' : '"Item"."parentId" = p.id'}
|
||||||
WHERE p."userId" = $1
|
WHERE p."userId" = $1
|
||||||
AND "Item"."userId" <> $1 AND "Item".created_at <= $2
|
AND "Item"."userId" <> $1 AND "Item".created_at <= $2
|
||||||
|
${await filterClause(me, models)}
|
||||||
ORDER BY "sortTime" DESC
|
ORDER BY "sortTime" DESC
|
||||||
LIMIT ${LIMIT}+$3)`
|
LIMIT ${LIMIT}+$3)`
|
||||||
)
|
)
|
||||||
@ -129,6 +131,7 @@ export default {
|
|||||||
AND "Mention".created_at <= $2
|
AND "Mention".created_at <= $2
|
||||||
AND "Item"."userId" <> $1
|
AND "Item"."userId" <> $1
|
||||||
AND (p."userId" IS NULL OR p."userId" <> $1)
|
AND (p."userId" IS NULL OR p."userId" <> $1)
|
||||||
|
${await filterClause(me, models)}
|
||||||
ORDER BY "sortTime" DESC
|
ORDER BY "sortTime" DESC
|
||||||
LIMIT ${LIMIT}+$3)`
|
LIMIT ${LIMIT}+$3)`
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { AuthenticationError, UserInputError } from 'apollo-server-errors'
|
import { AuthenticationError, UserInputError } from 'apollo-server-errors'
|
||||||
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
||||||
import { mdHas } from '../../lib/md'
|
import { mdHas } from '../../lib/md'
|
||||||
import { createMentions, getItem, SELECT, updateItem } from './item'
|
import { createMentions, getItem, SELECT, updateItem, filterClause } from './item'
|
||||||
import serialize from './serial'
|
import serialize from './serial'
|
||||||
|
|
||||||
export function topClause (within) {
|
export function topClause (within) {
|
||||||
@ -317,6 +317,7 @@ export default {
|
|||||||
JOIN "Item" p ON ${user.noteAllDescendants ? '"Item".path <@ p.path' : '"Item"."parentId" = p.id'}
|
JOIN "Item" p ON ${user.noteAllDescendants ? '"Item".path <@ p.path' : '"Item"."parentId" = p.id'}
|
||||||
WHERE p."userId" = $1
|
WHERE p."userId" = $1
|
||||||
AND "Item".created_at > $2 AND "Item"."userId" <> $1
|
AND "Item".created_at > $2 AND "Item"."userId" <> $1
|
||||||
|
${await filterClause(me, models)}
|
||||||
LIMIT 1`, me.id, lastChecked)
|
LIMIT 1`, me.id, lastChecked)
|
||||||
if (newReplies.length > 0) {
|
if (newReplies.length > 0) {
|
||||||
return true
|
return true
|
||||||
|
@ -27,6 +27,7 @@ export default gql`
|
|||||||
upsertPoll(id: ID, title: String!, text: String, options: [String!]!, boost: Int, forward: String): Item!
|
upsertPoll(id: ID, title: String!, text: String, options: [String!]!, boost: Int, forward: String): Item!
|
||||||
createComment(text: String!, parentId: ID!): Item!
|
createComment(text: String!, parentId: ID!): Item!
|
||||||
updateComment(id: ID!, text: String!): Item!
|
updateComment(id: ID!, text: String!): Item!
|
||||||
|
dontLikeThis(id: ID!): Boolean!
|
||||||
act(id: ID!, sats: Int): ItemActResult!
|
act(id: ID!, sats: Int): ItemActResult!
|
||||||
pollVote(id: ID!): ID!
|
pollVote(id: ID!): ID!
|
||||||
}
|
}
|
||||||
@ -78,6 +79,7 @@ export default gql`
|
|||||||
lastCommentAt: String
|
lastCommentAt: String
|
||||||
upvotes: Int!
|
upvotes: Int!
|
||||||
meSats: Int!
|
meSats: Int!
|
||||||
|
meDontLike: Boolean!
|
||||||
paidImgLink: Boolean
|
paidImgLink: Boolean
|
||||||
ncomments: Int!
|
ncomments: Int!
|
||||||
comments: [Item!]!
|
comments: [Item!]!
|
||||||
|
@ -31,7 +31,7 @@ export default gql`
|
|||||||
setName(name: String!): Boolean
|
setName(name: String!): Boolean
|
||||||
setSettings(tipDefault: Int!, noteItemSats: Boolean!, noteEarning: Boolean!,
|
setSettings(tipDefault: Int!, noteItemSats: Boolean!, noteEarning: Boolean!,
|
||||||
noteAllDescendants: Boolean!, noteMentions: Boolean!, noteDeposits: Boolean!,
|
noteAllDescendants: Boolean!, noteMentions: Boolean!, noteDeposits: Boolean!,
|
||||||
noteInvites: Boolean!, noteJobIndicator: Boolean!, hideInvoiceDesc: Boolean!): User
|
noteInvites: Boolean!, noteJobIndicator: Boolean!, hideInvoiceDesc: Boolean!, wildWestMode: Boolean!): User
|
||||||
setPhoto(photoId: ID!): Int!
|
setPhoto(photoId: ID!): Int!
|
||||||
upsertBio(bio: String!): User!
|
upsertBio(bio: String!): User!
|
||||||
setWalkthrough(tipPopover: Boolean, upvotePopover: Boolean): Boolean
|
setWalkthrough(tipPopover: Boolean, upvotePopover: Boolean): Boolean
|
||||||
@ -72,6 +72,7 @@ export default gql`
|
|||||||
noteInvites: Boolean!
|
noteInvites: Boolean!
|
||||||
noteJobIndicator: Boolean!
|
noteJobIndicator: Boolean!
|
||||||
hideInvoiceDesc: Boolean!
|
hideInvoiceDesc: Boolean!
|
||||||
|
wildWestMode: Boolean!
|
||||||
lastCheckedJobs: String
|
lastCheckedJobs: String
|
||||||
authMethods: AuthMethods!
|
authMethods: AuthMethods!
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,9 @@ import CommentEdit from './comment-edit'
|
|||||||
import Countdown from './countdown'
|
import Countdown from './countdown'
|
||||||
import { COMMENT_DEPTH_LIMIT, NOFOLLOW_LIMIT } from '../lib/constants'
|
import { COMMENT_DEPTH_LIMIT, NOFOLLOW_LIMIT } from '../lib/constants'
|
||||||
import { ignoreClick } from '../lib/clicks'
|
import { ignoreClick } from '../lib/clicks'
|
||||||
|
import { useMe } from './me'
|
||||||
|
import DontLikeThis from './dont-link-this'
|
||||||
|
import Flag from '../svgs/flag-fill.svg'
|
||||||
|
|
||||||
function Parent ({ item, rootText }) {
|
function Parent ({ item, rootText }) {
|
||||||
const ParentFrag = () => (
|
const ParentFrag = () => (
|
||||||
@ -78,6 +81,7 @@ export default function Comment ({
|
|||||||
const [edit, setEdit] = useState()
|
const [edit, setEdit] = useState()
|
||||||
const [collapse, setCollapse] = useState(false)
|
const [collapse, setCollapse] = useState(false)
|
||||||
const ref = useRef(null)
|
const ref = useRef(null)
|
||||||
|
const me = useMe()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const mine = item.mine
|
const mine = item.mine
|
||||||
const editThreshold = new Date(item.createdAt).getTime() + 10 * 60000
|
const editThreshold = new Date(item.createdAt).getTime() + 10 * 60000
|
||||||
@ -105,7 +109,7 @@ export default function Comment ({
|
|||||||
ref={ref} className={includeParent ? '' : `${styles.comment} ${collapse ? styles.collapsed : ''}`}
|
ref={ref} className={includeParent ? '' : `${styles.comment} ${collapse ? styles.collapsed : ''}`}
|
||||||
>
|
>
|
||||||
<div className={`${itemStyles.item} ${styles.item}`}>
|
<div className={`${itemStyles.item} ${styles.item}`}>
|
||||||
<UpVote item={item} className={styles.upvote} />
|
{item.meDontLike ? <Flag width={24} height={24} className={`${styles.dontLike}`} /> : <UpVote item={item} className={styles.upvote} />}
|
||||||
<div className={`${itemStyles.hunk} ${styles.hunk}`}>
|
<div className={`${itemStyles.hunk} ${styles.hunk}`}>
|
||||||
<div className='d-flex align-items-center'>
|
<div className='d-flex align-items-center'>
|
||||||
<div className={`${itemStyles.other} ${styles.other}`}>
|
<div className={`${itemStyles.other} ${styles.other}`}>
|
||||||
@ -128,6 +132,7 @@ export default function Comment ({
|
|||||||
<a title={item.createdAt} className='text-reset'>{timeSince(new Date(item.createdAt))}</a>
|
<a title={item.createdAt} className='text-reset'>{timeSince(new Date(item.createdAt))}</a>
|
||||||
</Link>
|
</Link>
|
||||||
{includeParent && <Parent item={item} rootText={rootText} />}
|
{includeParent && <Parent item={item} rootText={rootText} />}
|
||||||
|
{me && !item.meSats && !item.meDontLike && <DontLikeThis id={item.id} />}
|
||||||
{canEdit &&
|
{canEdit &&
|
||||||
<>
|
<>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
|
@ -8,6 +8,14 @@
|
|||||||
margin-top: 9px;
|
margin-top: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dontLike {
|
||||||
|
fill: #a5a5a5;
|
||||||
|
margin-right: .2rem;
|
||||||
|
padding: 2px;
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-top: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
margin-top: .1rem;
|
margin-top: .1rem;
|
||||||
padding-right: 15px;
|
padding-right: 15px;
|
||||||
|
54
components/dont-link-this.js
Normal file
54
components/dont-link-this.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { gql, useMutation } from '@apollo/client'
|
||||||
|
import { Dropdown } from 'react-bootstrap'
|
||||||
|
import MoreIcon from '../svgs/more-fill.svg'
|
||||||
|
import { useFundError } from './fund-error'
|
||||||
|
|
||||||
|
export default function DontLikeThis ({ id }) {
|
||||||
|
const { setError } = useFundError()
|
||||||
|
|
||||||
|
const [dontLikeThis] = useMutation(
|
||||||
|
gql`
|
||||||
|
mutation dontLikeThis($id: ID!) {
|
||||||
|
dontLikeThis(id: $id)
|
||||||
|
}`, {
|
||||||
|
update (cache) {
|
||||||
|
cache.modify({
|
||||||
|
id: `Item:${id}`,
|
||||||
|
fields: {
|
||||||
|
meDontLike () {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown className='pointer' as='span'>
|
||||||
|
<Dropdown.Toggle variant='success' id='dropdown-basic' as='a'>
|
||||||
|
<MoreIcon className='fill-grey ml-1' height={16} width={16} />
|
||||||
|
</Dropdown.Toggle>
|
||||||
|
|
||||||
|
<Dropdown.Menu>
|
||||||
|
<Dropdown.Item
|
||||||
|
className='text-center'
|
||||||
|
onClick={async () => {
|
||||||
|
try {
|
||||||
|
await dontLikeThis({
|
||||||
|
variables: { id },
|
||||||
|
optimisticResponse: { dontLikeThis: true }
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
if (error.toString().includes('insufficient funds')) {
|
||||||
|
setError(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
I don't like this
|
||||||
|
</Dropdown.Item>
|
||||||
|
</Dropdown.Menu>
|
||||||
|
</Dropdown>
|
||||||
|
)
|
||||||
|
}
|
@ -11,6 +11,9 @@ import Toc from './table-of-contents'
|
|||||||
import PollIcon from '../svgs/bar-chart-horizontal-fill.svg'
|
import PollIcon from '../svgs/bar-chart-horizontal-fill.svg'
|
||||||
import { Badge } from 'react-bootstrap'
|
import { Badge } from 'react-bootstrap'
|
||||||
import { newComments } from '../lib/new-comments'
|
import { newComments } from '../lib/new-comments'
|
||||||
|
import { useMe } from './me'
|
||||||
|
import DontLikeThis from './dont-link-this'
|
||||||
|
import Flag from '../svgs/flag-fill.svg'
|
||||||
|
|
||||||
export function SearchTitle ({ title }) {
|
export function SearchTitle ({ title }) {
|
||||||
return reactStringReplace(title, /:high\[([^\]]+)\]/g, (match, i) => {
|
return reactStringReplace(title, /:high\[([^\]]+)\]/g, (match, i) => {
|
||||||
@ -36,6 +39,7 @@ export default function Item ({ item, rank, showFwdUser, toc, children }) {
|
|||||||
useState(mine && (Date.now() < editThreshold))
|
useState(mine && (Date.now() < editThreshold))
|
||||||
const [wrap, setWrap] = useState(false)
|
const [wrap, setWrap] = useState(false)
|
||||||
const titleRef = useRef()
|
const titleRef = useRef()
|
||||||
|
const me = useMe()
|
||||||
const [hasNewComments, setHasNewComments] = useState(false)
|
const [hasNewComments, setHasNewComments] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -58,7 +62,9 @@ export default function Item ({ item, rank, showFwdUser, toc, children }) {
|
|||||||
</div>)
|
</div>)
|
||||||
: <div />}
|
: <div />}
|
||||||
<div className={styles.item}>
|
<div className={styles.item}>
|
||||||
{item.position ? <Pin width={24} height={24} className={styles.pin} /> : <UpVote item={item} className={styles.upvote} />}
|
{item.position
|
||||||
|
? <Pin width={24} height={24} className={styles.pin} />
|
||||||
|
: item.meDontLike ? <Flag width={24} height={24} className={`${styles.dontLike}`} /> : <UpVote item={item} className={styles.upvote} />}
|
||||||
<div className={styles.hunk}>
|
<div className={styles.hunk}>
|
||||||
<div className={`${styles.main} flex-wrap ${wrap ? 'd-inline' : ''}`}>
|
<div className={`${styles.main} flex-wrap ${wrap ? 'd-inline' : ''}`}>
|
||||||
<Link href={`/items/${item.id}`} passHref>
|
<Link href={`/items/${item.id}`} passHref>
|
||||||
@ -104,6 +110,7 @@ export default function Item ({ item, rank, showFwdUser, toc, children }) {
|
|||||||
<Link href={`/items/${item.id}`} passHref>
|
<Link href={`/items/${item.id}`} passHref>
|
||||||
<a title={item.createdAt} className='text-reset'>{timeSince(new Date(item.createdAt))}</a>
|
<a title={item.createdAt} className='text-reset'>{timeSince(new Date(item.createdAt))}</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
{me && !item.meSats && !item.position && !item.meDontLike && <DontLikeThis id={item.id} />}
|
||||||
{item.prior &&
|
{item.prior &&
|
||||||
<>
|
<>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
|
@ -30,6 +30,13 @@ a.title:visited {
|
|||||||
margin-right: .2rem;
|
margin-right: .2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dontLike {
|
||||||
|
fill: #a5a5a5;
|
||||||
|
margin-right: .2rem;
|
||||||
|
padding: 2px;
|
||||||
|
margin-left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
.case {
|
.case {
|
||||||
fill: #a5a5a5;
|
fill: #a5a5a5;
|
||||||
margin-right: .2rem;
|
margin-right: .2rem;
|
||||||
@ -76,7 +83,7 @@ a.link:visited {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hunk {
|
.hunk {
|
||||||
overflow: hidden;
|
min-width: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
line-height: 1.06rem;
|
line-height: 1.06rem;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ export const COMMENT_FIELDS = gql`
|
|||||||
upvotes
|
upvotes
|
||||||
boost
|
boost
|
||||||
meSats
|
meSats
|
||||||
|
meDontLike
|
||||||
path
|
path
|
||||||
commentSats
|
commentSats
|
||||||
mine
|
mine
|
||||||
|
@ -21,6 +21,7 @@ export const ITEM_FIELDS = gql`
|
|||||||
boost
|
boost
|
||||||
path
|
path
|
||||||
meSats
|
meSats
|
||||||
|
meDontLike
|
||||||
ncomments
|
ncomments
|
||||||
commentSats
|
commentSats
|
||||||
lastCommentAt
|
lastCommentAt
|
||||||
|
@ -25,6 +25,7 @@ export const ME = gql`
|
|||||||
noteInvites
|
noteInvites
|
||||||
noteJobIndicator
|
noteJobIndicator
|
||||||
hideInvoiceDesc
|
hideInvoiceDesc
|
||||||
|
wildWestMode
|
||||||
lastCheckedJobs
|
lastCheckedJobs
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
@ -50,6 +51,7 @@ export const ME_SSR = gql`
|
|||||||
noteInvites
|
noteInvites
|
||||||
noteJobIndicator
|
noteJobIndicator
|
||||||
hideInvoiceDesc
|
hideInvoiceDesc
|
||||||
|
wildWestMode
|
||||||
lastCheckedJobs
|
lastCheckedJobs
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
@ -65,6 +67,7 @@ export const SETTINGS_FIELDS = gql`
|
|||||||
noteInvites
|
noteInvites
|
||||||
noteJobIndicator
|
noteJobIndicator
|
||||||
hideInvoiceDesc
|
hideInvoiceDesc
|
||||||
|
wildWestMode
|
||||||
authMethods {
|
authMethods {
|
||||||
lightning
|
lightning
|
||||||
email
|
email
|
||||||
@ -86,11 +89,11 @@ gql`
|
|||||||
${SETTINGS_FIELDS}
|
${SETTINGS_FIELDS}
|
||||||
mutation setSettings($tipDefault: Int!, $noteItemSats: Boolean!, $noteEarning: Boolean!,
|
mutation setSettings($tipDefault: Int!, $noteItemSats: Boolean!, $noteEarning: Boolean!,
|
||||||
$noteAllDescendants: Boolean!, $noteMentions: Boolean!, $noteDeposits: Boolean!,
|
$noteAllDescendants: Boolean!, $noteMentions: Boolean!, $noteDeposits: Boolean!,
|
||||||
$noteInvites: Boolean!, $noteJobIndicator: Boolean!, $hideInvoiceDesc: Boolean!) {
|
$noteInvites: Boolean!, $noteJobIndicator: Boolean!, $hideInvoiceDesc: Boolean!, $wildWestMode: Boolean!) {
|
||||||
setSettings(tipDefault: $tipDefault, noteItemSats: $noteItemSats,
|
setSettings(tipDefault: $tipDefault, noteItemSats: $noteItemSats,
|
||||||
noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants,
|
noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants,
|
||||||
noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites,
|
noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites,
|
||||||
noteJobIndicator: $noteJobIndicator, hideInvoiceDesc: $hideInvoiceDesc) {
|
noteJobIndicator: $noteJobIndicator, hideInvoiceDesc: $hideInvoiceDesc, wildWestMode: $wildWestMode) {
|
||||||
...SettingsFields
|
...SettingsFields
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,3 +14,5 @@ export const MAX_TITLE_LENGTH = 80
|
|||||||
export const MAX_POLL_CHOICE_LENGTH = 30
|
export const MAX_POLL_CHOICE_LENGTH = 30
|
||||||
export const ITEM_SPAM_INTERVAL = '10m'
|
export const ITEM_SPAM_INTERVAL = '10m'
|
||||||
export const MAX_POLL_NUM_CHOICES = 10
|
export const MAX_POLL_NUM_CHOICES = 10
|
||||||
|
export const ITEM_FILTER_THRESHOLD = 1.2
|
||||||
|
export const DONT_LIKE_THIS_COST = 1
|
||||||
|
@ -61,7 +61,8 @@ export default function Settings ({ data: { settings } }) {
|
|||||||
noteDeposits: settings?.noteDeposits,
|
noteDeposits: settings?.noteDeposits,
|
||||||
noteInvites: settings?.noteInvites,
|
noteInvites: settings?.noteInvites,
|
||||||
noteJobIndicator: settings?.noteJobIndicator,
|
noteJobIndicator: settings?.noteJobIndicator,
|
||||||
hideInvoiceDesc: settings?.hideInvoiceDesc
|
hideInvoiceDesc: settings?.hideInvoiceDesc,
|
||||||
|
wildWestMode: settings?.wildWestMode
|
||||||
}}
|
}}
|
||||||
schema={SettingsSchema}
|
schema={SettingsSchema}
|
||||||
onSubmit={async ({ tipDefault, ...values }) => {
|
onSubmit={async ({ tipDefault, ...values }) => {
|
||||||
@ -115,7 +116,7 @@ export default function Settings ({ data: { settings } }) {
|
|||||||
<div className='form-label'>privacy</div>
|
<div className='form-label'>privacy</div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={
|
label={
|
||||||
<>hide invoice descriptions
|
<div className='d-flex align-items-center'>hide invoice descriptions
|
||||||
<Info>
|
<Info>
|
||||||
<ul className='font-weight-bold'>
|
<ul className='font-weight-bold'>
|
||||||
<li>Use this if you don't want funding sources to be linkable to your SN identity.</li>
|
<li>Use this if you don't want funding sources to be linkable to your SN identity.</li>
|
||||||
@ -127,10 +128,24 @@ export default function Settings ({ data: { settings } }) {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</Info>
|
</Info>
|
||||||
</>
|
</div>
|
||||||
}
|
}
|
||||||
name='hideInvoiceDesc'
|
name='hideInvoiceDesc'
|
||||||
/>
|
/>
|
||||||
|
<div className='form-label'>content</div>
|
||||||
|
<Checkbox
|
||||||
|
label={
|
||||||
|
<div className='d-flex align-items-center'>wild west mode
|
||||||
|
<Info>
|
||||||
|
<ul className='font-weight-bold'>
|
||||||
|
<li>Don't hide flagged content</li>
|
||||||
|
<li>Don't down rank flagged content</li>
|
||||||
|
</ul>
|
||||||
|
</Info>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
name='wildWestMode'
|
||||||
|
/>
|
||||||
<div className='d-flex'>
|
<div className='d-flex'>
|
||||||
<SubmitButton variant='info' className='ml-auto mt-1 px-4'>save</SubmitButton>
|
<SubmitButton variant='info' className='ml-auto mt-1 px-4'>save</SubmitButton>
|
||||||
</div>
|
</div>
|
||||||
|
8
prisma/migrations/20220920152500_downvotes/migration.sql
Normal file
8
prisma/migrations/20220920152500_downvotes/migration.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "ItemActType" ADD VALUE 'DONT_LIKE_THIS';
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Item" ADD COLUMN "weightedDownVotes" DOUBLE PRECISION NOT NULL DEFAULT 0;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "wildWestMode" BOOLEAN NOT NULL DEFAULT false;
|
@ -0,0 +1,74 @@
|
|||||||
|
-- modify it to take DONT_LIKE_THIS
|
||||||
|
CREATE OR REPLACE FUNCTION item_act(item_id INTEGER, user_id INTEGER, act "ItemActType", act_sats INTEGER)
|
||||||
|
RETURNS INTEGER
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
user_sats INTEGER;
|
||||||
|
BEGIN
|
||||||
|
PERFORM ASSERT_SERIALIZED();
|
||||||
|
|
||||||
|
SELECT (msats / 1000) INTO user_sats FROM users WHERE id = user_id;
|
||||||
|
IF act_sats > user_sats THEN
|
||||||
|
RAISE EXCEPTION 'SN_INSUFFICIENT_FUNDS';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- deduct sats from actor
|
||||||
|
UPDATE users SET msats = msats - (act_sats * 1000) WHERE id = user_id;
|
||||||
|
|
||||||
|
IF act = 'VOTE' OR act = 'TIP' THEN
|
||||||
|
-- add sats to actee's balance and stacked count
|
||||||
|
UPDATE users
|
||||||
|
SET msats = msats + (act_sats * 1000), "stackedMsats" = "stackedMsats" + (act_sats * 1000)
|
||||||
|
WHERE id = (SELECT COALESCE("fwdUserId", "userId") FROM "Item" WHERE id = item_id);
|
||||||
|
|
||||||
|
-- if they have already voted, this is a tip
|
||||||
|
IF EXISTS (SELECT 1 FROM "ItemAct" WHERE "itemId" = item_id AND "userId" = user_id AND "ItemAct".act = 'VOTE') THEN
|
||||||
|
INSERT INTO "ItemAct" (sats, "itemId", "userId", act, created_at, updated_at)
|
||||||
|
VALUES (act_sats, item_id, user_id, 'TIP', now_utc(), now_utc());
|
||||||
|
ELSE
|
||||||
|
-- else this is a vote with a possible extra tip
|
||||||
|
INSERT INTO "ItemAct" (sats, "itemId", "userId", act, created_at, updated_at)
|
||||||
|
VALUES (1, item_id, user_id, 'VOTE', now_utc(), now_utc());
|
||||||
|
act_sats := act_sats - 1;
|
||||||
|
|
||||||
|
-- if we have sats left after vote, leave them as a tip
|
||||||
|
IF act_sats > 0 THEN
|
||||||
|
INSERT INTO "ItemAct" (sats, "itemId", "userId", act, created_at, updated_at)
|
||||||
|
VALUES (act_sats, item_id, user_id, 'TIP', now_utc(), now_utc());
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN 1;
|
||||||
|
END IF;
|
||||||
|
ELSE -- BOOST, POLL, DONT_LIKE_THIS
|
||||||
|
INSERT INTO "ItemAct" (sats, "itemId", "userId", act, created_at, updated_at)
|
||||||
|
VALUES (act_sats, item_id, user_id, act, now_utc(), now_utc());
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN 0;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION weighted_downvotes_after_act() RETURNS TRIGGER AS $$
|
||||||
|
DECLARE
|
||||||
|
user_trust DOUBLE PRECISION;
|
||||||
|
BEGIN
|
||||||
|
-- grab user's trust who is upvoting
|
||||||
|
SELECT trust INTO user_trust FROM users WHERE id = NEW."userId";
|
||||||
|
-- update item
|
||||||
|
UPDATE "Item"
|
||||||
|
SET "weightedDownVotes" = "weightedDownVotes" + user_trust
|
||||||
|
WHERE id = NEW."itemId" AND "userId" <> NEW."userId";
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS weighted_downvotes_after_act ON "ItemAct";
|
||||||
|
CREATE TRIGGER weighted_downvotes_after_act
|
||||||
|
AFTER INSERT ON "ItemAct"
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN (NEW.act = 'DONT_LIKE_THIS')
|
||||||
|
EXECUTE PROCEDURE weighted_downvotes_after_act();
|
||||||
|
|
||||||
|
ALTER TABLE "Item" ADD CONSTRAINT "weighted_votes_positive" CHECK ("weightedVotes" >= 0) NOT VALID;
|
||||||
|
ALTER TABLE "Item" ADD CONSTRAINT "weighted_down_votes_positive" CHECK ("weightedDownVotes" >= 0) NOT VALID;
|
@ -59,6 +59,9 @@ model User {
|
|||||||
// privacy settings
|
// privacy settings
|
||||||
hideInvoiceDesc Boolean @default(false)
|
hideInvoiceDesc Boolean @default(false)
|
||||||
|
|
||||||
|
// content settings
|
||||||
|
wildWestMode Boolean @default(false)
|
||||||
|
|
||||||
Earn Earn[]
|
Earn Earn[]
|
||||||
Upload Upload[] @relation(name: "Uploads")
|
Upload Upload[] @relation(name: "Uploads")
|
||||||
PollVote PollVote[]
|
PollVote PollVote[]
|
||||||
@ -183,8 +186,9 @@ model Item {
|
|||||||
paidImgLink Boolean @default(false)
|
paidImgLink Boolean @default(false)
|
||||||
|
|
||||||
// denormalized self stats
|
// denormalized self stats
|
||||||
weightedVotes Float @default(0)
|
weightedVotes Float @default(0)
|
||||||
sats Int @default(0)
|
weightedDownVotes Float @default(0)
|
||||||
|
sats Int @default(0)
|
||||||
|
|
||||||
// denormalized comment stats
|
// denormalized comment stats
|
||||||
ncomments Int @default(0)
|
ncomments Int @default(0)
|
||||||
@ -296,6 +300,7 @@ enum ItemActType {
|
|||||||
TIP
|
TIP
|
||||||
STREAM
|
STREAM
|
||||||
POLL
|
POLL
|
||||||
|
DONT_LIKE_THIS
|
||||||
}
|
}
|
||||||
|
|
||||||
model ItemAct {
|
model ItemAct {
|
||||||
|
1
svgs/cloud-fill.svg
Normal file
1
svgs/cloud-fill.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M17 7a8.003 8.003 0 0 0-7.493 5.19l1.874.703A6.002 6.002 0 0 1 23 15a6 6 0 0 1-6 6H7A6 6 0 0 1 5.008 9.339a7 7 0 0 1 13.757-2.143A8.027 8.027 0 0 0 17 7z"/></svg>
|
After Width: | Height: | Size: 291 B |
1
svgs/error-warning-fill.svg
Normal file
1
svgs/error-warning-fill.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></svg>
|
After Width: | Height: | Size: 241 B |
1
svgs/flag-2-fill.svg
Normal file
1
svgs/flag-2-fill.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M2 3h19.138a.5.5 0 0 1 .435.748L18 10l3.573 6.252a.5.5 0 0 1-.435.748H4v5H2V3z"/></svg>
|
After Width: | Height: | Size: 216 B |
1
svgs/flag-fill.svg
Normal file
1
svgs/flag-fill.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 3h9.382a1 1 0 0 1 .894.553L14 5h6a1 1 0 0 1 1 1v11a1 1 0 0 1-1 1h-6.382a1 1 0 0 1-.894-.553L12 16H5v6H3V3z"/></svg>
|
After Width: | Height: | Size: 247 B |
1
svgs/more-fill.svg
Normal file
1
svgs/more-fill.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 10c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm14 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm-7 0c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg>
|
After Width: | Height: | Size: 285 B |
1
svgs/more-line.svg
Normal file
1
svgs/more-line.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M4.5 10.5c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S6 12.825 6 12s-.675-1.5-1.5-1.5zm15 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5S21 12.825 21 12s-.675-1.5-1.5-1.5zm-7.5 0c-.825 0-1.5.675-1.5 1.5s.675 1.5 1.5 1.5 1.5-.675 1.5-1.5-.675-1.5-1.5-1.5z"/></svg>
|
After Width: | Height: | Size: 385 B |
Loading…
x
Reference in New Issue
Block a user