Path: blob/master/src/packages/database/postgres/news.ts
5614 views
/*1* This file is part of CoCalc: Copyright © 2023 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45import {6Channel,7EVENT_CHANNEL,8NewsItem,9NewsPrevNext,10RecentHeadline,11} from "@cocalc/util/types/news";12import { LRUQueryCache } from "./util";1314const C = new LRUQueryCache({ ttl_s: 10 * 60 });1516export function clearCache(): void {17C.clear();18}1920// We exclude hidden and future news items and items from the events channel to keep user's news21// feed clear22const Q_FEED = `23SELECT24id, channel, title, text, url,25extract(epoch from date::timestamp)::integer as date,26extract(epoch from until::timestamp)::integer as until27FROM news28WHERE news.date <= NOW()29AND hide IS NOT TRUE30AND channel != '${EVENT_CHANNEL}'31AND (until IS NULL OR until > NOW())32ORDER BY date DESC33LIMIT 100`;3435export async function getFeedData(): Promise<NewsItem[]> {36return await C.query(Q_FEED);37}3839// ::timestamptz because if your server is not in UTC, it will be converted to UTC40// and the UTC epoch timestamp will be used in the browser client as the basis, adding your TZ offset41const Q_BY_ID = `42SELECT43id, channel, title, text, url, hide, tags,44extract(epoch from date::timestamptz)::INTEGER as date,45extract(epoch from until::timestamptz)::INTEGER as until46FROM news47WHERE id = $1`;4849// This is used for editing a news item50export async function getNewsItem(51id: number,52cached = true,53): Promise<NewsItem | null> {54return await C.queryOne<NewsItem>(Q_BY_ID, [id], cached);55}5657const Q_BY_ID_USER = `58SELECT59id, channel, title, text, url, hide, tags, history,60date >= NOW() as future,61until IS NOT NULL AND until <= NOW() as expired,62extract(epoch from date::timestamptz)::INTEGER as date,63extract(epoch from until::timestamptz)::INTEGER as until64FROM news65WHERE id = $1`;6667const Q_NEXT = `68SELECT id, title69FROM news70WHERE date >= (SELECT date FROM news WHERE id = $1)71AND id != $172AND hide IS NOT TRUE73AND date < NOW()74AND channel != '${EVENT_CHANNEL}'75AND (until IS NULL OR until > NOW())76ORDER BY date ASC, id ASC77LIMIT 1`;7879const Q_PREV = `80SELECT id, title81FROM news82WHERE date <= (SELECT date FROM news WHERE id = $1)83AND id != $184AND hide IS NOT TRUE85AND date < NOW()86AND channel != '${EVENT_CHANNEL}'87AND (until IS NULL OR until > NOW())88ORDER BY date DESC, id DESC89LIMIT 1`;9091// This is used for displaying one news item (and next/prev ones) to a user92export async function getNewsItemUserPrevNext(id: number): Promise<{93news: NewsItem | null;94prev: NewsPrevNext | null;95next: NewsPrevNext | null;96}> {97const [news, prev, next] = await Promise.all([98C.queryOne<NewsItem>(Q_BY_ID_USER, [id]),99C.queryOne<NewsPrevNext>(Q_PREV, [id]),100C.queryOne<NewsPrevNext>(Q_NEXT, [id]),101]);102return { news, prev, next };103}104105export async function getNewsItemUser(id: number): Promise<NewsItem | null> {106return await C.queryOne<NewsItem>(Q_BY_ID_USER, [id]);107}108109const Q_INDEX = `110SELECT111id, channel, title, text, url, hide, tags,112date >= NOW() as future,113until IS NOT NULL AND until <= NOW() as expired,114extract(epoch from date::timestamptz)::INTEGER as date,115extract(epoch from until::timestamptz)::INTEGER as until116FROM news117WHERE channel <> '${EVENT_CHANNEL}'118ORDER BY date DESC119LIMIT $1120OFFSET $2`;121122export async function getIndex(123limit: number,124offset: number,125): Promise<NewsItem[]> {126return await C.query(Q_INDEX, [limit, offset]);127}128129// get the most recent news item (excluding events)130const Q_MOST_RECENT = `131SELECT132id, channel, title, tags,133extract(epoch from date::timestamptz)::INTEGER as date,134extract(epoch from until::timestamptz)::INTEGER as until135FROM news136WHERE date <= NOW()137AND hide IS NOT TRUE138AND channel != '${EVENT_CHANNEL}'139AND (until IS NULL OR until > NOW())140ORDER BY date DESC141LIMIT 1`;142143export async function getMostRecentNews(): Promise<RecentHeadline | null> {144return await C.queryOne<RecentHeadline>(Q_MOST_RECENT);145}146147const Q_RECENT = `148SELECT149id, channel, title, tags,150extract(epoch from date::timestamptz)::INTEGER as date,151extract(epoch from until::timestamptz)::INTEGER as until152FROM news153WHERE date <= NOW()154AND channel != '${EVENT_CHANNEL}'155AND hide IS NOT TRUE156AND (until IS NULL OR until > NOW())157ORDER BY date DESC158LIMIT $1`;159160// of the last n picked by Q_RECENT, select one deterministically different every 10 minutes161export async function getRecentHeadlines(162n: number,163): Promise<RecentHeadline[] | null> {164const headlines = await C.query(Q_RECENT, [n]);165if (headlines.length === 0) return null;166return headlines;167}168169// Query upcoming events from a particular channel170const Q_UPCOMING_NEWS_CHANNEL_ITEMS = `171SELECT172id, channel, title, text, url, tags,173extract(epoch from date::timestamp)::integer as date,174extract(epoch from until::timestamp)::integer as until175FROM news176WHERE date >= NOW()177AND channel = $1178AND hide IS NOT TRUE179AND (until IS NULL OR until > NOW())180ORDER BY date181LIMIT 100`;182183export async function getUpcomingNewsChannelItems(184channel: Channel,185): Promise<NewsItem[]> {186return await C.query(Q_UPCOMING_NEWS_CHANNEL_ITEMS, [channel]);187}188189// Query past events from a particular channel190const Q_PAST_NEWS_CHANNEL_ITEMS = `191SELECT192id, channel, title, text, url, tags,193extract(epoch from date::timestamp)::integer as date,194extract(epoch from until::timestamp)::integer as until195FROM news196WHERE date <= NOW()197AND channel = $1198AND hide IS NOT TRUE199AND (until IS NULL OR until > NOW())200ORDER BY date DESC201LIMIT 100`;202203export async function getPastNewsChannelItems(204channel: Channel,205): Promise<NewsItem[]> {206return await C.query(Q_PAST_NEWS_CHANNEL_ITEMS, [channel]);207}208209210