CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
sagemathinc

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: sagemathinc/cocalc
Path: blob/master/src/packages/database/postgres/news.ts
Views: 687
1
/*
2
* This file is part of CoCalc: Copyright © 2023 Sagemath, Inc.
3
* License: MS-RSL – see LICENSE.md for details
4
*/
5
6
import {
7
Channel,
8
EVENT_CHANNEL,
9
NewsItem,
10
NewsPrevNext,
11
RecentHeadline,
12
} from "@cocalc/util/types/news";
13
import { LRUQueryCache } from "./util";
14
15
const C = new LRUQueryCache({ ttl_s: 10 * 60 });
16
17
export function clearCache(): void {
18
C.clear();
19
}
20
21
// We exclude hidden and future news items and items from the events channel to keep user's news
22
// feed clear
23
const Q_FEED = `
24
SELECT
25
id, channel, title, text, url,
26
extract(epoch from date::timestamp)::integer as date
27
FROM news
28
WHERE news.date <= NOW()
29
AND hide IS NOT TRUE
30
AND channel != '${EVENT_CHANNEL}'
31
ORDER BY date DESC
32
LIMIT 100`;
33
34
export async function getFeedData(): Promise<NewsItem[]> {
35
return await C.query(Q_FEED);
36
}
37
38
// ::timestamptz because if your server is not in UTC, it will be converted to UTC
39
// and the UTC epoch timestamp will be used in the browser client as the basis, adding your TZ offset
40
const Q_BY_ID = `
41
SELECT
42
id, channel, title, text, url, hide, tags,
43
extract(epoch from date::timestamptz)::INTEGER as date
44
FROM news
45
WHERE id = $1`;
46
47
// This is used for editing a news item
48
export async function getNewsItem(
49
id: number,
50
cached = true,
51
): Promise<NewsItem | null> {
52
return await C.queryOne<NewsItem>(Q_BY_ID, [id], cached);
53
}
54
55
const Q_BY_ID_USER = `
56
SELECT
57
id, channel, title, text, url, hide, tags, history,
58
date >= NOW() as future,
59
extract(epoch from date::timestamptz)::INTEGER as date
60
FROM news
61
WHERE id = $1`;
62
63
const Q_NEXT = `
64
SELECT id, title
65
FROM news
66
WHERE date >= (SELECT date FROM news WHERE id = $1)
67
AND id != $1
68
AND hide IS NOT TRUE
69
AND date < NOW()
70
AND channel != '${EVENT_CHANNEL}'
71
ORDER BY date ASC, id ASC
72
LIMIT 1`;
73
74
const Q_PREV = `
75
SELECT id, title
76
FROM news
77
WHERE date <= (SELECT date FROM news WHERE id = $1)
78
AND id != $1
79
AND hide IS NOT TRUE
80
AND date < NOW()
81
AND channel != '${EVENT_CHANNEL}'
82
ORDER BY date DESC, id DESC
83
LIMIT 1`;
84
85
// This is used for displaying one news item (and next/prev ones) to a user
86
export async function getNewsItemUserPrevNext(id: number): Promise<{
87
news: NewsItem | null;
88
prev: NewsPrevNext | null;
89
next: NewsPrevNext | null;
90
}> {
91
const [news, prev, next] = await Promise.all([
92
C.queryOne<NewsItem>(Q_BY_ID_USER, [id]),
93
C.queryOne<NewsPrevNext>(Q_PREV, [id]),
94
C.queryOne<NewsPrevNext>(Q_NEXT, [id]),
95
]);
96
return { news, prev, next };
97
}
98
99
export async function getNewsItemUser(id: number): Promise<NewsItem | null> {
100
return await C.queryOne<NewsItem>(Q_BY_ID_USER, [id]);
101
}
102
103
const Q_INDEX = `
104
SELECT
105
id, channel, title, text, url, hide, tags,
106
date >= NOW() as future,
107
extract(epoch from date::timestamptz)::INTEGER as date
108
FROM news
109
WHERE channel <> '${EVENT_CHANNEL}'
110
ORDER BY date DESC
111
LIMIT $1
112
OFFSET $2`;
113
114
export async function getIndex(
115
limit: number,
116
offset: number,
117
): Promise<NewsItem[]> {
118
return await C.query(Q_INDEX, [limit, offset]);
119
}
120
121
// get the most recent news item (excluding events)
122
const Q_MOST_RECENT = `
123
SELECT
124
id, channel, title, tags,
125
extract(epoch from date::timestamptz)::INTEGER as date
126
FROM news
127
WHERE date <= NOW()
128
AND hide IS NOT TRUE
129
AND channel != '${EVENT_CHANNEL}'
130
ORDER BY date DESC
131
LIMIT 1`;
132
133
export async function getMostRecentNews(): Promise<RecentHeadline | null> {
134
return await C.queryOne<RecentHeadline>(Q_MOST_RECENT);
135
}
136
137
const Q_RECENT = `
138
SELECT
139
id, channel, title, tags,
140
extract(epoch from date::timestamptz)::INTEGER as date
141
FROM news
142
WHERE date <= NOW()
143
AND channel != '${EVENT_CHANNEL}'
144
AND hide IS NOT TRUE
145
ORDER BY date DESC
146
LIMIT $1`;
147
148
// of the last n picked by Q_RECENT, select one deterministically different every 10 minutes
149
export async function getRecentHeadlines(
150
n: number,
151
): Promise<RecentHeadline[] | null> {
152
const headlines = await C.query(Q_RECENT, [n]);
153
if (headlines.length === 0) return null;
154
return headlines;
155
}
156
157
// Query upcoming events from a particular channel
158
const Q_UPCOMING_NEWS_CHANNEL_ITEMS = `
159
SELECT
160
id, channel, title, text, url, tags,
161
extract(epoch from date::timestamp)::integer as date
162
FROM news
163
WHERE date >= NOW()
164
AND channel = $1
165
AND hide IS NOT TRUE
166
ORDER BY date
167
LIMIT 100`;
168
169
export async function getUpcomingNewsChannelItems(
170
channel: Channel,
171
): Promise<NewsItem[]> {
172
return await C.query(Q_UPCOMING_NEWS_CHANNEL_ITEMS, [channel]);
173
}
174
175
// Query past events from a particular channel
176
const Q_PAST_NEWS_CHANNEL_ITEMS = `
177
SELECT
178
id, channel, title, text, url, tags,
179
extract(epoch from date::timestamp)::integer as date
180
FROM news
181
WHERE date <= NOW()
182
AND channel = $1
183
AND hide IS NOT TRUE
184
ORDER BY date DESC
185
LIMIT 100`;
186
187
export async function getPastNewsChannelItems(
188
channel: Channel,
189
): Promise<NewsItem[]> {
190
return await C.query(Q_PAST_NEWS_CHANNEL_ITEMS, [channel]);
191
}
192
193