Make it public!
This commit is contained in:
138
lib/data/contacts.ts
Normal file
138
lib/data/contacts.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import Database, { sql } from '/lib/interfaces/database.ts';
|
||||
import { Contact } from '/lib/types.ts';
|
||||
import { CONTACTS_PER_PAGE_COUNT } from '/lib/utils.ts';
|
||||
import { updateUserContactRevision } from './user.ts';
|
||||
|
||||
const db = new Database();
|
||||
|
||||
export async function getContacts(userId: string, pageIndex: number) {
|
||||
const contacts = await db.query<Pick<Contact, 'id' | 'first_name' | 'last_name'>>(
|
||||
sql`SELECT "id", "first_name", "last_name" FROM "bewcloud_contacts" WHERE "user_id" = $1 ORDER BY "first_name" ASC, "last_name" ASC LIMIT ${CONTACTS_PER_PAGE_COUNT} OFFSET $2`,
|
||||
[
|
||||
userId,
|
||||
pageIndex * CONTACTS_PER_PAGE_COUNT,
|
||||
],
|
||||
);
|
||||
|
||||
return contacts;
|
||||
}
|
||||
|
||||
export async function getContactsCount(userId: string) {
|
||||
const results = await db.query<{ count: number }>(
|
||||
sql`SELECT COUNT("id") AS "count" FROM "bewcloud_contacts" WHERE "user_id" = $1`,
|
||||
[
|
||||
userId,
|
||||
],
|
||||
);
|
||||
|
||||
return Number(results[0]?.count || 0);
|
||||
}
|
||||
|
||||
export async function searchContacts(search: string, userId: string, pageIndex: number) {
|
||||
const contacts = await db.query<Pick<Contact, 'id' | 'first_name' | 'last_name'>>(
|
||||
sql`SELECT "id", "first_name", "last_name" FROM "bewcloud_contacts" WHERE "user_id" = $1 AND ("first_name" ILIKE $3 OR "last_name" ILIKE $3 OR "extra"::text ILIKE $3) ORDER BY "first_name" ASC, "last_name" ASC LIMIT ${CONTACTS_PER_PAGE_COUNT} OFFSET $2`,
|
||||
[
|
||||
userId,
|
||||
pageIndex * CONTACTS_PER_PAGE_COUNT,
|
||||
`%${search}%`,
|
||||
],
|
||||
);
|
||||
|
||||
return contacts;
|
||||
}
|
||||
|
||||
export async function searchContactsCount(search: string, userId: string) {
|
||||
const results = await db.query<{ count: number }>(
|
||||
sql`SELECT COUNT("id") AS "count" FROM "bewcloud_contacts" WHERE "user_id" = $1 AND ("first_name" ILIKE $2 OR "last_name" ILIKE $2 OR "extra"::text ILIKE $2)`,
|
||||
[
|
||||
userId,
|
||||
`%${search}%`,
|
||||
],
|
||||
);
|
||||
|
||||
return Number(results[0]?.count || 0);
|
||||
}
|
||||
|
||||
export async function getAllContacts(userId: string) {
|
||||
const contacts = await db.query<Contact>(sql`SELECT * FROM "bewcloud_contacts" WHERE "user_id" = $1`, [
|
||||
userId,
|
||||
]);
|
||||
|
||||
return contacts;
|
||||
}
|
||||
|
||||
export async function getContact(id: string, userId: string) {
|
||||
const contacts = await db.query<Contact>(
|
||||
sql`SELECT * FROM "bewcloud_contacts" WHERE "id" = $1 AND "user_id" = $2 LIMIT 1`,
|
||||
[
|
||||
id,
|
||||
userId,
|
||||
],
|
||||
);
|
||||
|
||||
return contacts[0];
|
||||
}
|
||||
|
||||
export async function createContact(userId: string, firstName: string, lastName: string) {
|
||||
const extra: Contact['extra'] = {};
|
||||
|
||||
const revision = crypto.randomUUID();
|
||||
|
||||
const newContact = (await db.query<Contact>(
|
||||
sql`INSERT INTO "bewcloud_contacts" (
|
||||
"user_id",
|
||||
"revision",
|
||||
"first_name",
|
||||
"last_name",
|
||||
"extra"
|
||||
) VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING *`,
|
||||
[
|
||||
userId,
|
||||
revision,
|
||||
firstName,
|
||||
lastName,
|
||||
JSON.stringify(extra),
|
||||
],
|
||||
))[0];
|
||||
|
||||
await updateUserContactRevision(userId);
|
||||
|
||||
return newContact;
|
||||
}
|
||||
|
||||
export async function updateContact(contact: Contact) {
|
||||
const revision = crypto.randomUUID();
|
||||
|
||||
await db.query(
|
||||
sql`UPDATE "bewcloud_contacts" SET
|
||||
"revision" = $3,
|
||||
"first_name" = $4,
|
||||
"last_name" = $5,
|
||||
"extra" = $6,
|
||||
"updated_at" = now()
|
||||
WHERE "id" = $1 AND "revision" = $2`,
|
||||
[
|
||||
contact.id,
|
||||
contact.revision,
|
||||
revision,
|
||||
contact.first_name,
|
||||
contact.last_name,
|
||||
JSON.stringify(contact.extra),
|
||||
],
|
||||
);
|
||||
|
||||
await updateUserContactRevision(contact.user_id);
|
||||
}
|
||||
|
||||
export async function deleteContact(id: string, userId: string) {
|
||||
await db.query(
|
||||
sql`DELETE FROM "bewcloud_contacts" WHERE "id" = $1 AND "user_id" = $2`,
|
||||
[
|
||||
id,
|
||||
userId,
|
||||
],
|
||||
);
|
||||
|
||||
await updateUserContactRevision(userId);
|
||||
}
|
||||
42
lib/data/dashboard.ts
Normal file
42
lib/data/dashboard.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import Database, { sql } from '/lib/interfaces/database.ts';
|
||||
import { Dashboard } from '/lib/types.ts';
|
||||
|
||||
const db = new Database();
|
||||
|
||||
export async function getDashboardByUserId(userId: string) {
|
||||
const dashboard = (await db.query<Dashboard>(sql`SELECT * FROM "bewcloud_dashboards" WHERE "user_id" = $1 LIMIT 1`, [
|
||||
userId,
|
||||
]))[0];
|
||||
|
||||
return dashboard;
|
||||
}
|
||||
|
||||
export async function createDashboard(userId: string) {
|
||||
const data: Dashboard['data'] = { links: [], notes: '' };
|
||||
|
||||
const newDashboard = (await db.query<Dashboard>(
|
||||
sql`INSERT INTO "bewcloud_dashboards" (
|
||||
"user_id",
|
||||
"data"
|
||||
) VALUES ($1, $2)
|
||||
RETURNING *`,
|
||||
[
|
||||
userId,
|
||||
JSON.stringify(data),
|
||||
],
|
||||
))[0];
|
||||
|
||||
return newDashboard;
|
||||
}
|
||||
|
||||
export async function updateDashboard(dashboard: Dashboard) {
|
||||
await db.query(
|
||||
sql`UPDATE "bewcloud_dashboards" SET
|
||||
"data" = $2
|
||||
WHERE "id" = $1`,
|
||||
[
|
||||
dashboard.id,
|
||||
JSON.stringify(dashboard.data),
|
||||
],
|
||||
);
|
||||
}
|
||||
298
lib/data/news.ts
Normal file
298
lib/data/news.ts
Normal file
@@ -0,0 +1,298 @@
|
||||
import { Feed } from 'https://deno.land/x/rss@1.0.0/mod.ts';
|
||||
|
||||
import Database, { sql } from '/lib/interfaces/database.ts';
|
||||
import { NewsFeed, NewsFeedArticle } from '/lib/types.ts';
|
||||
import {
|
||||
findFeedInUrl,
|
||||
getArticleUrl,
|
||||
getFeedInfo,
|
||||
JsonFeed,
|
||||
parseTextFromHtml,
|
||||
parseUrl,
|
||||
parseUrlAsGooglebot,
|
||||
parseUrlWithProxy,
|
||||
} from '/lib/feed.ts';
|
||||
|
||||
const db = new Database();
|
||||
|
||||
export async function getNewsFeeds(userId: string) {
|
||||
const newsFeeds = await db.query<NewsFeed>(sql`SELECT * FROM "bewcloud_news_feeds" WHERE "user_id" = $1`, [
|
||||
userId,
|
||||
]);
|
||||
|
||||
return newsFeeds;
|
||||
}
|
||||
|
||||
export async function getNewsFeed(id: string, userId: string) {
|
||||
const newsFeeds = await db.query<NewsFeed>(
|
||||
sql`SELECT * FROM "bewcloud_news_feeds" WHERE "id" = $1 AND "user_id" = $2 LIMIT 1`,
|
||||
[
|
||||
id,
|
||||
userId,
|
||||
],
|
||||
);
|
||||
|
||||
return newsFeeds[0];
|
||||
}
|
||||
|
||||
export async function getNewsArticles(userId: string) {
|
||||
const articles = await db.query<NewsFeedArticle>(
|
||||
sql`SELECT * FROM "bewcloud_news_feed_articles" WHERE "user_id" = $1 ORDER BY "article_date" DESC`,
|
||||
[
|
||||
userId,
|
||||
],
|
||||
);
|
||||
|
||||
return articles;
|
||||
}
|
||||
|
||||
export async function getNewsArticlesByFeedId(feedId: string) {
|
||||
const articles = await db.query<NewsFeedArticle>(
|
||||
sql`SELECT * FROM "bewcloud_news_feed_articles" WHERE "feed_id" = $1 ORDER BY "article_date" DESC`,
|
||||
[
|
||||
feedId,
|
||||
],
|
||||
);
|
||||
|
||||
return articles;
|
||||
}
|
||||
|
||||
export async function getNewsArticle(id: string, userId: string) {
|
||||
const articles = await db.query<NewsFeedArticle>(
|
||||
sql`SELECT * FROM "bewcloud_news_feed_articles" WHERE "id" = $1 AND "user_id" = $2 LIMIT 1`,
|
||||
[
|
||||
id,
|
||||
userId,
|
||||
],
|
||||
);
|
||||
|
||||
return articles[0];
|
||||
}
|
||||
|
||||
export async function createNewsFeed(userId: string, feedUrl: string) {
|
||||
const extra: NewsFeed['extra'] = {};
|
||||
|
||||
const newNewsFeed = (await db.query<NewsFeed>(
|
||||
sql`INSERT INTO "bewcloud_news_feeds" (
|
||||
"user_id",
|
||||
"feed_url",
|
||||
"extra"
|
||||
) VALUES ($1, $2, $3)
|
||||
RETURNING *`,
|
||||
[
|
||||
userId,
|
||||
feedUrl,
|
||||
JSON.stringify(extra),
|
||||
],
|
||||
))[0];
|
||||
|
||||
return newNewsFeed;
|
||||
}
|
||||
|
||||
export async function updateNewsFeed(newsFeed: NewsFeed) {
|
||||
await db.query(
|
||||
sql`UPDATE "bewcloud_news_feeds" SET
|
||||
"feed_url" = $2,
|
||||
"last_crawled_at" = $3,
|
||||
"extra" = $4
|
||||
WHERE "id" = $1`,
|
||||
[
|
||||
newsFeed.id,
|
||||
newsFeed.feed_url,
|
||||
newsFeed.last_crawled_at,
|
||||
JSON.stringify(newsFeed.extra),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteNewsFeed(id: string) {
|
||||
await db.query(
|
||||
sql`DELETE FROM "bewcloud_news_feed_articles" WHERE "feed_id" = $1`,
|
||||
[
|
||||
id,
|
||||
],
|
||||
);
|
||||
|
||||
await db.query(
|
||||
sql`DELETE FROM "bewcloud_news_feeds" WHERE "id" = $1`,
|
||||
[
|
||||
id,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
export async function createsNewsArticle(
|
||||
userId: string,
|
||||
feedId: string,
|
||||
article: Omit<NewsFeedArticle, 'id' | 'user_id' | 'feed_id' | 'extra' | 'is_read' | 'created_at'>,
|
||||
) {
|
||||
const extra: NewsFeedArticle['extra'] = {};
|
||||
|
||||
const newNewsArticle = (await db.query<NewsFeedArticle>(
|
||||
sql`INSERT INTO "bewcloud_news_feed_articles" (
|
||||
"user_id",
|
||||
"feed_id",
|
||||
"article_url",
|
||||
"article_title",
|
||||
"article_summary",
|
||||
"article_date",
|
||||
"extra"
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING *`,
|
||||
[
|
||||
userId,
|
||||
feedId,
|
||||
article.article_url,
|
||||
article.article_title,
|
||||
article.article_summary,
|
||||
article.article_date,
|
||||
JSON.stringify(extra),
|
||||
],
|
||||
))[0];
|
||||
|
||||
return newNewsArticle;
|
||||
}
|
||||
|
||||
export async function updateNewsArticle(article: NewsFeedArticle) {
|
||||
await db.query(
|
||||
sql`UPDATE "bewcloud_news_feed_articles" SET
|
||||
"is_read" = $2,
|
||||
"extra" = $3
|
||||
WHERE "id" = $1`,
|
||||
[
|
||||
article.id,
|
||||
article.is_read,
|
||||
JSON.stringify(article.extra),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
export async function markAllArticlesRead(userId: string) {
|
||||
await db.query(
|
||||
sql`UPDATE "bewcloud_news_feed_articles" SET
|
||||
"is_read" = TRUE
|
||||
WHERE "user_id" = $1`,
|
||||
[
|
||||
userId,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchNewsArticles(newsFeed: NewsFeed): Promise<Feed['entries'] | JsonFeed['items']> {
|
||||
try {
|
||||
if (!newsFeed.extra.title || !newsFeed.extra.feed_type || !newsFeed.extra.crawl_type) {
|
||||
throw new Error('Invalid News Feed!');
|
||||
}
|
||||
|
||||
let feed: JsonFeed | Feed | null = null;
|
||||
|
||||
if (newsFeed.extra.crawl_type === 'direct') {
|
||||
feed = await parseUrl(newsFeed.feed_url);
|
||||
} else if (newsFeed.extra.crawl_type === 'googlebot') {
|
||||
feed = await parseUrlAsGooglebot(newsFeed.feed_url);
|
||||
} else if (newsFeed.extra.crawl_type === 'proxy') {
|
||||
feed = await parseUrlWithProxy(newsFeed.feed_url);
|
||||
}
|
||||
|
||||
return (feed as Feed)?.entries || (feed as JsonFeed)?.items || [];
|
||||
} catch (error) {
|
||||
console.log('Failed parsing feed to get articles', newsFeed.feed_url);
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
type FeedArticle = Feed['entries'][number];
|
||||
type JsonFeedArticle = JsonFeed['items'][number];
|
||||
|
||||
const MAX_ARTICLES_CRAWLED_PER_RUN = 10;
|
||||
|
||||
export async function crawlNewsFeed(newsFeed: NewsFeed) {
|
||||
// TODO: Lock this per feedId, so no two processes run this at the same time
|
||||
|
||||
if (!newsFeed.extra.title || !newsFeed.extra.feed_type || !newsFeed.extra.crawl_type) {
|
||||
const feedUrl = await findFeedInUrl(newsFeed.feed_url);
|
||||
|
||||
if (!feedUrl) {
|
||||
throw new Error(
|
||||
`Invalid URL for feed: "${feedUrl}"`,
|
||||
);
|
||||
}
|
||||
|
||||
if (feedUrl !== newsFeed.feed_url) {
|
||||
newsFeed.feed_url = feedUrl;
|
||||
}
|
||||
|
||||
const feedInfo = await getFeedInfo(newsFeed.feed_url);
|
||||
|
||||
newsFeed.extra.title = feedInfo.title;
|
||||
newsFeed.extra.feed_type = feedInfo.feed_type;
|
||||
newsFeed.extra.crawl_type = feedInfo.crawl_type;
|
||||
}
|
||||
|
||||
const feedArticles = await fetchNewsArticles(newsFeed);
|
||||
|
||||
const articles: Omit<NewsFeedArticle, 'id' | 'user_id' | 'feed_id' | 'extra' | 'is_read' | 'created_at'>[] = [];
|
||||
|
||||
for (const feedArticle of feedArticles) {
|
||||
// Don't add too many articles per run
|
||||
if (articles.length >= MAX_ARTICLES_CRAWLED_PER_RUN) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const url = (feedArticle as JsonFeedArticle).url || getArticleUrl((feedArticle as FeedArticle).links) ||
|
||||
feedArticle.id;
|
||||
|
||||
const articleIsoDate = (feedArticle as JsonFeedArticle).date_published ||
|
||||
(feedArticle as FeedArticle).published?.toISOString() || (feedArticle as JsonFeedArticle).date_modified ||
|
||||
(feedArticle as FeedArticle).updated?.toISOString();
|
||||
|
||||
const articleDate = articleIsoDate ? new Date(articleIsoDate) : new Date();
|
||||
|
||||
const summary = await parseTextFromHtml(
|
||||
(feedArticle as FeedArticle).description?.value || (feedArticle as FeedArticle).content?.value ||
|
||||
(feedArticle as JsonFeedArticle).content_text || (feedArticle as JsonFeedArticle).content_html ||
|
||||
(feedArticle as JsonFeedArticle).summary || '',
|
||||
);
|
||||
|
||||
if (url) {
|
||||
articles.push({
|
||||
article_title: (feedArticle as FeedArticle).title?.value || (feedArticle as JsonFeedArticle).title ||
|
||||
url.replace('http://', '').replace('https://', ''),
|
||||
article_url: url,
|
||||
article_summary: summary,
|
||||
article_date: articleDate,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const existingArticles = await getNewsArticlesByFeedId(newsFeed.id);
|
||||
const existingArticleUrls = new Set<string>(existingArticles.map((article) => article.article_url));
|
||||
const previousLatestArticleUrl = existingArticles[0]?.article_url;
|
||||
let seenPreviousLatestArticleUrl = false;
|
||||
let addedArticlesCount = 0;
|
||||
|
||||
for (const article of articles) {
|
||||
// Stop looking after seeing the previous latest article
|
||||
if (article.article_url === previousLatestArticleUrl) {
|
||||
seenPreviousLatestArticleUrl = true;
|
||||
}
|
||||
|
||||
if (!seenPreviousLatestArticleUrl && !existingArticleUrls.has(article.article_url)) {
|
||||
try {
|
||||
await createsNewsArticle(newsFeed.user_id, newsFeed.id, article);
|
||||
++addedArticlesCount;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error(`Failed to add new article: "${article.article_url}"`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Added', addedArticlesCount, 'new articles');
|
||||
|
||||
newsFeed.last_crawled_at = new Date();
|
||||
|
||||
await updateNewsFeed(newsFeed);
|
||||
}
|
||||
296
lib/data/user.ts
Normal file
296
lib/data/user.ts
Normal file
@@ -0,0 +1,296 @@
|
||||
import Database, { sql } from '/lib/interfaces/database.ts';
|
||||
import { User, UserSession, VerificationCode } from '/lib/types.ts';
|
||||
import { generateRandomCode } from '/lib/utils.ts';
|
||||
|
||||
const db = new Database();
|
||||
|
||||
export async function isThereAnAdmin() {
|
||||
const user =
|
||||
(await db.query<User>(sql`SELECT * FROM "bewcloud_users" WHERE ("extra" ->> 'is_admin')::boolean IS TRUE LIMIT 1`))[
|
||||
0
|
||||
];
|
||||
|
||||
return Boolean(user);
|
||||
}
|
||||
|
||||
export async function getUserByEmail(email: string) {
|
||||
const lowercaseEmail = email.toLowerCase().trim();
|
||||
|
||||
const user = (await db.query<User>(sql`SELECT * FROM "bewcloud_users" WHERE "email" = $1 LIMIT 1`, [
|
||||
lowercaseEmail,
|
||||
]))[0];
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function getUserById(id: string) {
|
||||
const user = (await db.query<User>(sql`SELECT * FROM "bewcloud_users" WHERE "id" = $1 LIMIT 1`, [
|
||||
id,
|
||||
]))[0];
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
export async function createUser(email: User['email'], hashedPassword: User['hashed_password']) {
|
||||
const trialDays = 30;
|
||||
const now = new Date();
|
||||
const trialEndDate = new Date(new Date().setUTCDate(new Date().getUTCDate() + trialDays));
|
||||
|
||||
const subscription: User['subscription'] = {
|
||||
external: {},
|
||||
expires_at: trialEndDate.toISOString(),
|
||||
updated_at: now.toISOString(),
|
||||
};
|
||||
|
||||
const extra: User['extra'] = { is_email_verified: false };
|
||||
|
||||
// First signup will be an admin "forever"
|
||||
if (!(await isThereAnAdmin())) {
|
||||
extra.is_admin = true;
|
||||
subscription.expires_at = new Date('2100-12-31').toISOString();
|
||||
}
|
||||
|
||||
const newUser = (await db.query<User>(
|
||||
sql`INSERT INTO "bewcloud_users" (
|
||||
"email",
|
||||
"subscription",
|
||||
"status",
|
||||
"hashed_password",
|
||||
"extra"
|
||||
) VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING *`,
|
||||
[
|
||||
email,
|
||||
JSON.stringify(subscription),
|
||||
extra.is_admin ? 'active' : 'trial',
|
||||
hashedPassword,
|
||||
JSON.stringify(extra),
|
||||
],
|
||||
))[0];
|
||||
|
||||
return newUser;
|
||||
}
|
||||
|
||||
export async function updateUser(user: User) {
|
||||
await db.query(
|
||||
sql`UPDATE "bewcloud_users" SET
|
||||
"email" = $2,
|
||||
"subscription" = $3,
|
||||
"status" = $4,
|
||||
"hashed_password" = $5,
|
||||
"extra" = $6
|
||||
WHERE "id" = $1`,
|
||||
[
|
||||
user.id,
|
||||
user.email,
|
||||
JSON.stringify(user.subscription),
|
||||
user.status,
|
||||
user.hashed_password,
|
||||
JSON.stringify(user.extra),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteUser(userId: string) {
|
||||
await db.query(
|
||||
sql`DELETE FROM "bewcloud_user_sessions" WHERE "user_id" = $1`,
|
||||
[
|
||||
userId,
|
||||
],
|
||||
);
|
||||
|
||||
await db.query(
|
||||
sql`DELETE FROM "bewcloud_verification_codes" WHERE "user_id" = $1`,
|
||||
[
|
||||
userId,
|
||||
],
|
||||
);
|
||||
|
||||
await db.query(
|
||||
sql`DELETE FROM "bewcloud_news_feed_articles" WHERE "user_id" = $1`,
|
||||
[
|
||||
userId,
|
||||
],
|
||||
);
|
||||
|
||||
await db.query(
|
||||
sql`DELETE FROM "bewcloud_news_feeds" WHERE "user_id" = $1`,
|
||||
[
|
||||
userId,
|
||||
],
|
||||
);
|
||||
|
||||
await db.query(
|
||||
sql`DELETE FROM "bewcloud_users" WHERE "id" = $1`,
|
||||
[
|
||||
userId,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
export async function getSessionById(id: string) {
|
||||
const session = (await db.query<UserSession>(
|
||||
sql`SELECT * FROM "bewcloud_user_sessions" WHERE "id" = $1 AND "expires_at" > now() LIMIT 1`,
|
||||
[
|
||||
id,
|
||||
],
|
||||
))[0];
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function createUserSession(user: User, isShortLived = false) {
|
||||
const oneMonthFromToday = new Date(new Date().setUTCMonth(new Date().getUTCMonth() + 1));
|
||||
const oneWeekFromToday = new Date(new Date().setUTCDate(new Date().getUTCDate() + 7));
|
||||
|
||||
const newSession: Omit<UserSession, 'id' | 'created_at'> = {
|
||||
user_id: user.id,
|
||||
expires_at: isShortLived ? oneWeekFromToday : oneMonthFromToday,
|
||||
last_seen_at: new Date(),
|
||||
};
|
||||
|
||||
const newUserSessionResult = (await db.query<UserSession>(
|
||||
sql`INSERT INTO "bewcloud_user_sessions" (
|
||||
"user_id",
|
||||
"expires_at",
|
||||
"last_seen_at"
|
||||
) VALUES ($1, $2, $3)
|
||||
RETURNING *`,
|
||||
[
|
||||
newSession.user_id,
|
||||
newSession.expires_at,
|
||||
newSession.last_seen_at,
|
||||
],
|
||||
))[0];
|
||||
|
||||
return newUserSessionResult;
|
||||
}
|
||||
|
||||
export async function updateSession(session: UserSession) {
|
||||
await db.query(
|
||||
sql`UPDATE "bewcloud_user_sessions" SET
|
||||
"expires_at" = $2,
|
||||
"last_seen_at" = $3
|
||||
WHERE "id" = $1`,
|
||||
[
|
||||
session.id,
|
||||
session.expires_at,
|
||||
session.last_seen_at,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
export async function deleteUserSession(sessionId: string) {
|
||||
await db.query(
|
||||
sql`DELETE FROM "bewcloud_user_sessions" WHERE "id" = $1`,
|
||||
[
|
||||
sessionId,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
export async function validateUserAndSession(userId: string, sessionId: string) {
|
||||
const user = await getUserById(userId);
|
||||
|
||||
if (!user) {
|
||||
throw new Error('Not Found');
|
||||
}
|
||||
|
||||
const session = await getSessionById(sessionId);
|
||||
|
||||
if (!session || session.user_id !== user.id) {
|
||||
throw new Error('Not Found');
|
||||
}
|
||||
|
||||
const oneMonthFromToday = new Date(new Date().setUTCMonth(new Date().getUTCMonth() + 1));
|
||||
|
||||
session.last_seen_at = new Date();
|
||||
session.expires_at = oneMonthFromToday;
|
||||
|
||||
await updateSession(session);
|
||||
|
||||
return { user, session };
|
||||
}
|
||||
|
||||
export async function createVerificationCode(
|
||||
user: User,
|
||||
verificationId: string,
|
||||
type: VerificationCode['verification']['type'],
|
||||
) {
|
||||
const inThirtyMinutes = new Date(new Date().setUTCMinutes(new Date().getUTCMinutes() + 30));
|
||||
|
||||
const code = generateRandomCode();
|
||||
|
||||
const newVerificationCode: Omit<VerificationCode, 'id' | 'created_at'> = {
|
||||
user_id: user.id,
|
||||
code,
|
||||
expires_at: inThirtyMinutes,
|
||||
verification: {
|
||||
id: verificationId,
|
||||
type,
|
||||
},
|
||||
};
|
||||
|
||||
await db.query(
|
||||
sql`INSERT INTO "bewcloud_verification_codes" (
|
||||
"user_id",
|
||||
"code",
|
||||
"expires_at",
|
||||
"verification"
|
||||
) VALUES ($1, $2, $3, $4)
|
||||
RETURNING "id"`,
|
||||
[
|
||||
newVerificationCode.user_id,
|
||||
newVerificationCode.code,
|
||||
newVerificationCode.expires_at,
|
||||
JSON.stringify(newVerificationCode.verification),
|
||||
],
|
||||
);
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
export async function validateVerificationCode(
|
||||
user: User,
|
||||
verificationId: string,
|
||||
code: string,
|
||||
type: VerificationCode['verification']['type'],
|
||||
) {
|
||||
const verificationCode = (await db.query<VerificationCode>(
|
||||
sql`SELECT * FROM "bewcloud_verification_codes"
|
||||
WHERE "user_id" = $1 AND
|
||||
"code" = $2 AND
|
||||
"verification" ->> 'type' = $3 AND
|
||||
"verification" ->> 'id' = $4 AND
|
||||
"expires_at" > now()
|
||||
LIMIT 1`,
|
||||
[
|
||||
user.id,
|
||||
code,
|
||||
type,
|
||||
verificationId,
|
||||
],
|
||||
))[0];
|
||||
|
||||
if (verificationCode) {
|
||||
await db.query(
|
||||
sql`DELETE FROM "bewcloud_verification_codes" WHERE "id" = $1`,
|
||||
[
|
||||
verificationCode.id,
|
||||
],
|
||||
);
|
||||
} else {
|
||||
throw new Error('Not Found');
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateUserContactRevision(id: string) {
|
||||
const user = await getUserById(id);
|
||||
|
||||
const revision = crypto.randomUUID();
|
||||
|
||||
user.extra.contacts_revision = revision;
|
||||
user.extra.contacts_updated_at = new Date().toISOString();
|
||||
|
||||
await updateUser(user);
|
||||
}
|
||||
Reference in New Issue
Block a user