mirror of
https://github.com/wassname/talk.git
synced 2026-07-25 13:30:59 +08:00
initial commit
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { Db } from 'mongodb';
|
||||
import { FilterQuery } from './types';
|
||||
import { defaults } from 'lodash';
|
||||
import uuid from 'uuid';
|
||||
import { Omit } from 'talk-common/types';
|
||||
import dotize from 'dotize';
|
||||
|
||||
export interface Asset {
|
||||
readonly id: string;
|
||||
url: string;
|
||||
scraped?: Date;
|
||||
closedAt?: Date;
|
||||
closedMessage?: string;
|
||||
title?: string;
|
||||
description?: string;
|
||||
image?: string;
|
||||
section?: string;
|
||||
subsection?: string;
|
||||
author?: string;
|
||||
publication_date?: Date;
|
||||
modified_date?: Date;
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
export type CreateAssetInput = Pick<Asset, 'id' | 'url'>;
|
||||
|
||||
export async function create(db: Db, input: CreateAssetInput): Promise<Asset> {
|
||||
const now = new Date();
|
||||
|
||||
// Construct the filter.
|
||||
const filter: FilterQuery<Asset> = {};
|
||||
if (input.id) {
|
||||
filter.id = input.id;
|
||||
} else {
|
||||
filter.url = input.url;
|
||||
}
|
||||
|
||||
// Craft the update object.
|
||||
const update: { $setOnInsert: Asset } = {
|
||||
$setOnInsert: defaults(input, {
|
||||
id: uuid.v4(),
|
||||
created_at: now,
|
||||
}),
|
||||
};
|
||||
|
||||
// Perform the upsert operation.
|
||||
const result = await db
|
||||
.collection<Asset>('assets')
|
||||
.findOneAndUpdate(filter, update, {
|
||||
// Create the object if it doesn't already exist.
|
||||
upsert: true,
|
||||
// False to return the updated document instead of the original
|
||||
// document.
|
||||
returnOriginal: false,
|
||||
});
|
||||
|
||||
return result.value;
|
||||
}
|
||||
|
||||
export async function exists(db: Db, id: string): Promise<boolean> {
|
||||
// TODO: implement
|
||||
// const cursor = await db.collection<Asset>('assets').find({ id }).limit(1);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function retrieve(db: Db, id: string): Promise<Asset> {
|
||||
return await db.collection<Asset>('assets').findOne({ id });
|
||||
}
|
||||
|
||||
export async function retrieveMany(
|
||||
db: Db,
|
||||
ids: string[]
|
||||
): Promise<Array<Asset>> {
|
||||
const cursor = await db
|
||||
.collection<Asset>('assets')
|
||||
.find({ id: { $in: ids } });
|
||||
|
||||
const assets = await cursor.toArray();
|
||||
|
||||
return ids.map(id => assets.find(asset => asset.id === id));
|
||||
}
|
||||
|
||||
export type UpdateAssetInput = Omit<
|
||||
Partial<Asset>,
|
||||
'id' | 'url' | 'created_at'
|
||||
>;
|
||||
|
||||
export async function update(
|
||||
db: Db,
|
||||
id: string,
|
||||
update: UpdateAssetInput
|
||||
): Promise<Readonly<Asset>> {
|
||||
const result = await db.collection<Asset>('assets').findOneAndUpdate(
|
||||
{ id },
|
||||
// Only update fields that have been updated.
|
||||
{ $set: dotize(update) },
|
||||
// False to return the updated document instead of the original
|
||||
// document.
|
||||
{ returnOriginal: false }
|
||||
);
|
||||
|
||||
return result.value;
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { Db } from 'mongodb';
|
||||
import { Omit, Sub } from 'talk-common/types';
|
||||
import { merge } from 'lodash';
|
||||
import uuid from 'uuid';
|
||||
|
||||
export interface BodyHistoryItem {
|
||||
body: string;
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
export interface StatusHistoryItem {
|
||||
status: CommentStatus; // TODO: migrate field
|
||||
assigned_by?: string;
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
export enum CommentStatus {
|
||||
ACCEPTED = 'ACCEPTED',
|
||||
REJECTED = 'REJECTED',
|
||||
PREMOD = 'PREMOD',
|
||||
SYSTEM_WITHHELD = 'SYSTEM_WITHHELD',
|
||||
NONE = 'NONE',
|
||||
}
|
||||
|
||||
export interface ActionCounts {
|
||||
[_: string]: number;
|
||||
}
|
||||
|
||||
export interface Comment {
|
||||
readonly id: string;
|
||||
parent_id?: string;
|
||||
author_id: string;
|
||||
asset_id: string;
|
||||
body: string;
|
||||
body_history: BodyHistoryItem[];
|
||||
status: CommentStatus;
|
||||
status_history: StatusHistoryItem[];
|
||||
action_counts: ActionCounts;
|
||||
reply_count: number;
|
||||
created_at: Date;
|
||||
deleted_at?: Date;
|
||||
}
|
||||
|
||||
export type CreateCommentInput = Omit<
|
||||
Comment,
|
||||
'id' | 'created_at' | 'reply_count' | 'body_history' | 'status_history'
|
||||
>;
|
||||
|
||||
export async function create(
|
||||
db: Db,
|
||||
input: CreateCommentInput
|
||||
): Promise<Readonly<Comment>> {
|
||||
const now = new Date();
|
||||
|
||||
// Pull out some useful properties from the input.
|
||||
const { body, status } = input;
|
||||
|
||||
// default are the properties set by the application when a new comment is
|
||||
// created.
|
||||
const defaults: Sub<Comment, CreateCommentInput> = {
|
||||
id: uuid.v4(),
|
||||
created_at: now,
|
||||
reply_count: 0,
|
||||
body_history: [
|
||||
{
|
||||
body,
|
||||
created_at: now,
|
||||
},
|
||||
],
|
||||
status_history: [
|
||||
{
|
||||
status,
|
||||
created_at: now,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// Merge the defaults and the input together.
|
||||
const comment: Comment = merge({}, defaults, input);
|
||||
|
||||
// TODO: Check for existence of the parent ID before we create the comment.
|
||||
|
||||
// TODO: Check for existence of the asset ID before we create the comment.
|
||||
|
||||
// Insert it into the database.
|
||||
await db.collection<Comment>('comments').insertOne(comment);
|
||||
|
||||
// TODO: update reply count of parent if exists.
|
||||
|
||||
return comment;
|
||||
}
|
||||
|
||||
async function incrementReplyCount(db: Db, parentID: string): Promise<void> {
|
||||
return null;
|
||||
}
|
||||
|
||||
export async function retrieve(db: Db, id: string): Promise<Comment> {
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { Db } from 'mongodb';
|
||||
import { defaultsDeep } from 'lodash';
|
||||
import dotize from 'dotize';
|
||||
|
||||
// selector is the single document selector for the Settings model stored in the
|
||||
// settings collection in MongoDB.
|
||||
const selector = { id: '1' };
|
||||
|
||||
export interface Wordlist {
|
||||
banned: string[];
|
||||
suspect: string[];
|
||||
}
|
||||
|
||||
export enum Moderation {
|
||||
PRE = 'PRE',
|
||||
POST = 'POST',
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
readonly id: string;
|
||||
|
||||
moderation: Moderation;
|
||||
requireEmailConfirmation: boolean;
|
||||
infoBoxEnable: boolean;
|
||||
infoBoxContent?: string;
|
||||
questionBoxEnable: boolean;
|
||||
questionBoxIcon?: string;
|
||||
questionBoxContent?: string;
|
||||
premodLinksEnable: boolean;
|
||||
autoCloseStream: boolean;
|
||||
closedTimeout: number;
|
||||
closedMessage?: string;
|
||||
customCssUrl?: string;
|
||||
disableCommenting: boolean;
|
||||
disableCommentingMessage?: string;
|
||||
|
||||
// editCommentWindowLength is the length of time (in milliseconds) after a
|
||||
// comment is posted that it can still be edited by the author.
|
||||
editCommentWindowLength: number;
|
||||
charCountEnable: boolean;
|
||||
charCount?: number;
|
||||
organizationName?: string;
|
||||
organizationContactEmail?: string;
|
||||
|
||||
// wordlist stores all the banned/suspect words.
|
||||
wordlist: Wordlist;
|
||||
|
||||
// domains is the set of whitelisted domains.
|
||||
domains: string[];
|
||||
}
|
||||
|
||||
const defaultSettings: Settings = {
|
||||
// Include the selector.
|
||||
...selector,
|
||||
|
||||
// Default to post moderation.
|
||||
moderation: Moderation.POST,
|
||||
|
||||
// Email confirmation is default off.
|
||||
requireEmailConfirmation: false,
|
||||
infoBoxEnable: false,
|
||||
questionBoxEnable: false,
|
||||
premodLinksEnable: false,
|
||||
autoCloseStream: false,
|
||||
// Two weeks timeout.
|
||||
closedTimeout: 60 * 60 * 24 * 7 * 2,
|
||||
disableCommenting: false,
|
||||
editCommentWindowLength: 30 * 1000,
|
||||
charCountEnable: false,
|
||||
wordlist: {
|
||||
suspect: [],
|
||||
banned: [],
|
||||
},
|
||||
domains: [],
|
||||
};
|
||||
|
||||
export async function create(
|
||||
db: Db,
|
||||
settingsInput: Partial<Settings>
|
||||
): Promise<Readonly<Settings>> {
|
||||
const result = await db
|
||||
.collection<Settings>('settings')
|
||||
.findOneAndReplace(
|
||||
selector,
|
||||
defaultsDeep({}, settingsInput, defaultSettings),
|
||||
{
|
||||
upsert: true,
|
||||
returnOriginal: false,
|
||||
}
|
||||
);
|
||||
|
||||
return result.value;
|
||||
}
|
||||
|
||||
export async function retrieve(db: Db): Promise<Readonly<Settings>> {
|
||||
const settings = await db
|
||||
.collection<Settings>('settings')
|
||||
.findOne(selector);
|
||||
if (!settings) {
|
||||
throw new Error('settings not initialized'); // FIXME: return actual typed error
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
export async function update(
|
||||
db: Db,
|
||||
update: Partial<Settings>
|
||||
): Promise<Readonly<Settings>> {
|
||||
// Get the settings from the database.
|
||||
const result = await db.collection<Settings>('settings').findOneAndUpdate(
|
||||
selector,
|
||||
// Only update fields that have been updated.
|
||||
{ $set: dotize(update) },
|
||||
// False to return the updated document instead of the original
|
||||
// document.
|
||||
{ returnOriginal: false }
|
||||
);
|
||||
|
||||
return result.value;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { FilterQuery } from 'mongodb';
|
||||
import { Writeable } from '../../common/types';
|
||||
|
||||
export type FilterQuery<T> = Writeable<Partial<T>> &
|
||||
FilterQuery<Writeable<Partial<T>>>;
|
||||
Reference in New Issue
Block a user