blog: move to a seperate package
parent
58d2f59f19
commit
1a4fde4119
@ -0,0 +1,244 @@
|
||||
import {
|
||||
_, Context, DiscussionNotFoundError, DocumentModel, Filter,
|
||||
Handler, NumberKeys, ObjectId, OplogModel, paginate,
|
||||
param, PRIV, Types, UserModel,
|
||||
} from 'hydrooj';
|
||||
|
||||
export const TYPE_BLOG: 70 = 70;
|
||||
export interface BlogDoc {
|
||||
docType: 70;
|
||||
docId: ObjectId;
|
||||
owner: number;
|
||||
title: string;
|
||||
content: string;
|
||||
ip: string;
|
||||
updateAt: Date;
|
||||
nReply: number;
|
||||
views: number;
|
||||
reply: any[];
|
||||
react: Record<string, number>;
|
||||
}
|
||||
declare module 'hydrooj' {
|
||||
interface Model {
|
||||
blog: typeof BlogModel;
|
||||
}
|
||||
interface DocType {
|
||||
[TYPE_BLOG]: BlogDoc;
|
||||
}
|
||||
}
|
||||
|
||||
export class BlogModel {
|
||||
static async add(
|
||||
owner: number, title: string, content: string, ip?: string,
|
||||
): Promise<ObjectId> {
|
||||
const payload: Partial<BlogDoc> = {
|
||||
content,
|
||||
owner,
|
||||
title,
|
||||
ip,
|
||||
nReply: 0,
|
||||
updateAt: new Date(),
|
||||
views: 0,
|
||||
};
|
||||
const res = await DocumentModel.add(
|
||||
'system', payload.content!, payload.owner!, TYPE_BLOG,
|
||||
null, null, null, _.omit(payload, ['domainId', 'content', 'owner']),
|
||||
);
|
||||
payload.docId = res;
|
||||
return payload.docId;
|
||||
}
|
||||
|
||||
static async get(did: ObjectId): Promise<BlogDoc> {
|
||||
return await DocumentModel.get('system', TYPE_BLOG, did);
|
||||
}
|
||||
|
||||
static edit(did: ObjectId, title: string, content: string): Promise<BlogDoc> {
|
||||
const payload = { title, content };
|
||||
return DocumentModel.set('system', TYPE_BLOG, did, payload);
|
||||
}
|
||||
|
||||
static inc(did: ObjectId, key: NumberKeys<BlogDoc>, value: number): Promise<BlogDoc | null> {
|
||||
return DocumentModel.inc('system', TYPE_BLOG, did, key, value);
|
||||
}
|
||||
|
||||
static del(did: ObjectId): Promise<never> {
|
||||
return Promise.all([
|
||||
DocumentModel.deleteOne('system', TYPE_BLOG, did),
|
||||
DocumentModel.deleteMultiStatus('system', TYPE_BLOG, { docId: did }),
|
||||
]) as any;
|
||||
}
|
||||
|
||||
static count(query: Filter<BlogDoc>) {
|
||||
return DocumentModel.count('system', TYPE_BLOG, query);
|
||||
}
|
||||
|
||||
static getMulti(query: Filter<BlogDoc> = {}) {
|
||||
return DocumentModel.getMulti('system', TYPE_BLOG, query)
|
||||
.sort({ _id: -1 });
|
||||
}
|
||||
|
||||
static async addReply(did: ObjectId, owner: number, content: string, ip: string): Promise<ObjectId> {
|
||||
const [[, drid]] = await Promise.all([
|
||||
DocumentModel.push('system', TYPE_BLOG, did, 'reply', content, owner, { ip }),
|
||||
DocumentModel.incAndSet('system', TYPE_BLOG, did, 'nReply', 1, { updateAt: new Date() }),
|
||||
]);
|
||||
return drid;
|
||||
}
|
||||
|
||||
static setStar(did: ObjectId, uid: number, star: boolean) {
|
||||
return DocumentModel.setStatus('system', TYPE_BLOG, did, uid, { star });
|
||||
}
|
||||
|
||||
static getStatus(did: ObjectId, uid: number) {
|
||||
return DocumentModel.getStatus('system', TYPE_BLOG, did, uid);
|
||||
}
|
||||
|
||||
static setStatus(did: ObjectId, uid: number, $set) {
|
||||
return DocumentModel.setStatus('system', TYPE_BLOG, did, uid, $set);
|
||||
}
|
||||
}
|
||||
|
||||
global.Hydro.model.blog = BlogModel;
|
||||
|
||||
class BlogHandler extends Handler {
|
||||
ddoc?: BlogDoc;
|
||||
|
||||
@param('did', Types.ObjectId, true)
|
||||
async _prepare(domainId: string, did: ObjectId) {
|
||||
if (did) {
|
||||
this.ddoc = await BlogModel.get(did);
|
||||
if (!this.ddoc) throw new DiscussionNotFoundError(domainId, did);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BlogUserHandler extends BlogHandler {
|
||||
@param('uid', Types.Int)
|
||||
@param('page', Types.PositiveInt, true)
|
||||
async get(domainId: string, uid: number, page = 1) {
|
||||
const [ddocs, dpcount] = await paginate(
|
||||
BlogModel.getMulti({ owner: uid }),
|
||||
page,
|
||||
10,
|
||||
);
|
||||
const udoc = await UserModel.getById(domainId, uid);
|
||||
this.response.template = 'blog_main.html';
|
||||
this.response.body = {
|
||||
ddocs,
|
||||
dpcount,
|
||||
udoc,
|
||||
page,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class BlogDetailHandler extends BlogHandler {
|
||||
@param('did', Types.ObjectId)
|
||||
async get(domainId: string, did: ObjectId) {
|
||||
const dsdoc = this.user.hasPriv(PRIV.PRIV_USER_PROFILE)
|
||||
? await BlogModel.getStatus(did, this.user._id)
|
||||
: null;
|
||||
const udoc = await UserModel.getById(domainId, this.ddoc!.owner);
|
||||
if (!dsdoc?.view) {
|
||||
await Promise.all([
|
||||
BlogModel.inc(did, 'views', 1),
|
||||
BlogModel.setStatus(did, this.user._id, { view: true }),
|
||||
]);
|
||||
}
|
||||
this.response.template = 'blog_detail.html';
|
||||
this.response.body = {
|
||||
ddoc: this.ddoc, dsdoc, udoc,
|
||||
};
|
||||
}
|
||||
|
||||
async post() {
|
||||
this.checkPriv(PRIV.PRIV_USER_PROFILE);
|
||||
}
|
||||
|
||||
@param('did', Types.ObjectId)
|
||||
async postStar(domainId: string, did: ObjectId) {
|
||||
await BlogModel.setStar(did, this.user._id, true);
|
||||
this.back({ star: true });
|
||||
}
|
||||
|
||||
@param('did', Types.ObjectId)
|
||||
async postUnstar(domainId: string, did: ObjectId) {
|
||||
await BlogModel.setStar(did, this.user._id, false);
|
||||
this.back({ star: false });
|
||||
}
|
||||
}
|
||||
|
||||
class BlogEditHandler extends BlogHandler {
|
||||
async get() {
|
||||
this.response.template = 'blog_edit.html';
|
||||
this.response.body = { ddoc: this.ddoc };
|
||||
}
|
||||
|
||||
@param('title', Types.Title)
|
||||
@param('content', Types.Content)
|
||||
async postCreate(domainId: string, title: string, content: string) {
|
||||
await this.limitRate('add_blog', 3600, 60);
|
||||
const did = await BlogModel.add(this.user._id, title, content, this.request.ip);
|
||||
this.response.body = { did };
|
||||
this.response.redirect = this.url('blog_detail', { uid: this.user._id, did });
|
||||
}
|
||||
|
||||
@param('did', Types.ObjectId)
|
||||
@param('title', Types.Title)
|
||||
@param('content', Types.Content)
|
||||
async postUpdate(domainId: string, did: ObjectId, title: string, content: string) {
|
||||
if (!this.user.own(this.ddoc!)) this.checkPriv(PRIV.PRIV_EDIT_SYSTEM);
|
||||
await Promise.all([
|
||||
BlogModel.edit(did, title, content),
|
||||
OplogModel.log(this, 'blog.edit', this.ddoc),
|
||||
]);
|
||||
this.response.body = { did };
|
||||
this.response.redirect = this.url('blog_detail', { uid: this.user._id, did });
|
||||
}
|
||||
|
||||
@param('did', Types.ObjectId)
|
||||
async postDelete(domainId: string, did: ObjectId) {
|
||||
if (!this.user.own(this.ddoc!)) this.checkPriv(PRIV.PRIV_EDIT_SYSTEM);
|
||||
await Promise.all([
|
||||
BlogModel.del(did),
|
||||
OplogModel.log(this, 'blog.delete', this.ddoc),
|
||||
]);
|
||||
this.response.redirect = this.url('blog_main', { uid: this.ddoc!.owner });
|
||||
}
|
||||
}
|
||||
|
||||
export async function apply(ctx: Context) {
|
||||
ctx.Route('blog_main', '/blog/:uid', BlogUserHandler);
|
||||
ctx.Route('blog_create', '/blog/:uid/create', BlogEditHandler, PRIV.PRIV_USER_PROFILE);
|
||||
ctx.Route('blog_detail', '/blog/:uid/:did', BlogDetailHandler);
|
||||
ctx.Route('blog_edit', '/blog/:uid/:did/edit', BlogEditHandler, PRIV.PRIV_USER_PROFILE);
|
||||
ctx.inject('UserDropdown', 'blog_main', {
|
||||
icon: 'book', displayName: 'Blog', args: (h) => ({ uid: h.user._id }),
|
||||
}, PRIV.PRIV_USER_PROFILE);
|
||||
ctx.i18n.load('zh', {
|
||||
"{0}'s blog": '{0} 的博客',
|
||||
Blog: '博客',
|
||||
blog_detail: '博客详情',
|
||||
blog_edit: '编辑博客',
|
||||
blog_main: '博客',
|
||||
});
|
||||
ctx.i18n.load('zh_TW', {
|
||||
"{0}'s blog": '{0} 的部落格',
|
||||
Blog: '部落格',
|
||||
blog_detail: '部落格詳情',
|
||||
blog_edit: '編輯部落格',
|
||||
blog_main: '部落格',
|
||||
});
|
||||
ctx.i18n.load('kr', {
|
||||
"{0}'s blog": '{0}의 블로그',
|
||||
Blog: '블로그',
|
||||
blog_main: '블로그',
|
||||
blog_detail: '블로그 상세',
|
||||
blog_edit: '블로그 수정',
|
||||
});
|
||||
ctx.i18n.load('en', {
|
||||
blog_main: 'Blog',
|
||||
blog_detail: 'Blog Detail',
|
||||
blog_edit: 'Edit Blog',
|
||||
});
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@hydrooj/blog",
|
||||
"version": "0.0.1",
|
||||
"license": "AGPL-3.0-or-later"
|
||||
}
|
@ -1,125 +0,0 @@
|
||||
import { ObjectId } from 'mongodb';
|
||||
import { DiscussionNotFoundError } from '../error';
|
||||
import { BlogDoc } from '../interface';
|
||||
import paginate from '../lib/paginate';
|
||||
import * as blog from '../model/blog';
|
||||
import { PRIV } from '../model/builtin';
|
||||
import * as oplog from '../model/oplog';
|
||||
import * as system from '../model/system';
|
||||
import user from '../model/user';
|
||||
import { Handler, param, Types } from '../service/server';
|
||||
|
||||
class BlogHandler extends Handler {
|
||||
ddoc?: BlogDoc;
|
||||
|
||||
@param('did', Types.ObjectId, true)
|
||||
async _prepare(domainId: string, did: ObjectId) {
|
||||
if (did) {
|
||||
this.ddoc = await blog.get(did);
|
||||
if (!this.ddoc) throw new DiscussionNotFoundError(domainId, did);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BlogUserHandler extends BlogHandler {
|
||||
@param('uid', Types.Int)
|
||||
@param('page', Types.PositiveInt, true)
|
||||
async get(domainId: string, uid: number, page = 1) {
|
||||
const [ddocs, dpcount] = await paginate(
|
||||
blog.getMulti({ owner: uid }),
|
||||
page,
|
||||
10,
|
||||
);
|
||||
const udoc = await user.getById(domainId, uid);
|
||||
this.response.template = 'blog_main.html';
|
||||
this.response.body = {
|
||||
ddocs,
|
||||
dpcount,
|
||||
udoc,
|
||||
page,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class BlogDetailHandler extends BlogHandler {
|
||||
@param('did', Types.ObjectId)
|
||||
async get(domainId: string, did: ObjectId) {
|
||||
const dsdoc = this.user.hasPriv(PRIV.PRIV_USER_PROFILE)
|
||||
? await blog.getStatus(did, this.user._id)
|
||||
: null;
|
||||
const udoc = await user.getById(domainId, this.ddoc.owner);
|
||||
if (!dsdoc?.view) {
|
||||
await Promise.all([
|
||||
blog.inc(did, 'views', 1),
|
||||
blog.setStatus(did, this.user._id, { view: true }),
|
||||
]);
|
||||
}
|
||||
this.response.template = 'blog_detail.html';
|
||||
this.response.body = {
|
||||
ddoc: this.ddoc, dsdoc, udoc,
|
||||
};
|
||||
}
|
||||
|
||||
async post() {
|
||||
this.checkPriv(PRIV.PRIV_USER_PROFILE);
|
||||
}
|
||||
|
||||
@param('did', Types.ObjectId)
|
||||
async postStar(domainId: string, did: ObjectId) {
|
||||
await blog.setStar(did, this.user._id, true);
|
||||
this.back({ star: true });
|
||||
}
|
||||
|
||||
@param('did', Types.ObjectId)
|
||||
async postUnstar(domainId: string, did: ObjectId) {
|
||||
await blog.setStar(did, this.user._id, false);
|
||||
this.back({ star: false });
|
||||
}
|
||||
}
|
||||
|
||||
class BlogEditHandler extends BlogHandler {
|
||||
async get() {
|
||||
this.response.template = 'blog_edit.html';
|
||||
this.response.body = { ddoc: this.ddoc };
|
||||
}
|
||||
|
||||
@param('title', Types.Title)
|
||||
@param('content', Types.Content)
|
||||
async postCreate(domainId: string, title: string, content: string) {
|
||||
await this.limitRate('add_blog', 3600, 60);
|
||||
const did = await blog.add(this.user._id, title, content, this.request.ip);
|
||||
this.response.body = { did };
|
||||
this.response.redirect = this.url('blog_detail', { uid: this.user._id, did });
|
||||
}
|
||||
|
||||
@param('did', Types.ObjectId)
|
||||
@param('title', Types.Title)
|
||||
@param('content', Types.Content)
|
||||
async postUpdate(domainId: string, did: ObjectId, title: string, content: string) {
|
||||
if (!this.user.own(this.ddoc)) this.checkPriv(PRIV.PRIV_EDIT_SYSTEM);
|
||||
await Promise.all([
|
||||
blog.edit(did, title, content),
|
||||
oplog.log(this, 'blog.edit', this.ddoc),
|
||||
]);
|
||||
this.response.body = { did };
|
||||
this.response.redirect = this.url('blog_detail', { uid: this.user._id, did });
|
||||
}
|
||||
|
||||
@param('did', Types.ObjectId)
|
||||
async postDelete(domainId: string, did: ObjectId) {
|
||||
if (!this.user.own(this.ddoc)) this.checkPriv(PRIV.PRIV_EDIT_SYSTEM);
|
||||
await Promise.all([
|
||||
blog.del(did),
|
||||
oplog.log(this, 'blog.delete', this.ddoc),
|
||||
]);
|
||||
this.response.redirect = this.url('blog_main', { uid: this.ddoc.owner });
|
||||
}
|
||||
}
|
||||
|
||||
export async function apply(ctx) {
|
||||
if (!system.get('server.blog')) return;
|
||||
ctx.Route('blog_main', '/blog/:uid', BlogUserHandler);
|
||||
ctx.Route('blog_create', '/blog/:uid/create', BlogEditHandler, PRIV.PRIV_USER_PROFILE);
|
||||
ctx.Route('blog_detail', '/blog/:uid/:did', BlogDetailHandler);
|
||||
ctx.Route('blog_edit', '/blog/:uid/:did/edit', BlogEditHandler, PRIV.PRIV_USER_PROFILE);
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
import { omit } from 'lodash';
|
||||
import { Filter, ObjectId } from 'mongodb';
|
||||
import { BlogDoc } from '../interface';
|
||||
import { NumberKeys } from '../typeutils';
|
||||
import * as document from './document';
|
||||
|
||||
export async function add(
|
||||
owner: number, title: string, content: string,
|
||||
ip: string | null = null,
|
||||
): Promise<ObjectId> {
|
||||
const payload: Partial<BlogDoc> = {
|
||||
content,
|
||||
owner,
|
||||
title,
|
||||
ip,
|
||||
nReply: 0,
|
||||
updateAt: new Date(),
|
||||
views: 0,
|
||||
};
|
||||
const res = await document.add(
|
||||
'system', payload.content!, payload.owner!, document.TYPE_BLOG,
|
||||
null, null, null, omit(payload, ['domainId', 'content', 'owner']),
|
||||
);
|
||||
payload.docId = res;
|
||||
return payload.docId;
|
||||
}
|
||||
|
||||
export async function get(did: ObjectId): Promise<BlogDoc> {
|
||||
return await document.get('system', document.TYPE_BLOG, did);
|
||||
}
|
||||
|
||||
export function edit(did: ObjectId, title: string, content: string): Promise<BlogDoc> {
|
||||
const payload = { title, content };
|
||||
return document.set('system', document.TYPE_BLOG, did, payload);
|
||||
}
|
||||
|
||||
export function inc(did: ObjectId, key: NumberKeys<BlogDoc>, value: number): Promise<BlogDoc | null> {
|
||||
return document.inc('system', document.TYPE_BLOG, did, key, value);
|
||||
}
|
||||
|
||||
export function del(did: ObjectId): Promise<never> {
|
||||
return Promise.all([
|
||||
document.deleteOne('system', document.TYPE_BLOG, did),
|
||||
document.deleteMultiStatus('system', document.TYPE_BLOG, { docId: did }),
|
||||
]) as any;
|
||||
}
|
||||
|
||||
export function count(query: Filter<BlogDoc>) {
|
||||
return document.count('system', document.TYPE_BLOG, query);
|
||||
}
|
||||
|
||||
export function getMulti(query: Filter<BlogDoc> = {}) {
|
||||
return document.getMulti('system', document.TYPE_BLOG, query)
|
||||
.sort({ _id: -1 });
|
||||
}
|
||||
|
||||
export async function addReply(did: ObjectId, owner: number, content: string, ip: string): Promise<ObjectId> {
|
||||
const [[, drid]] = await Promise.all([
|
||||
document.push('system', document.TYPE_BLOG, did, 'reply', content, owner, { ip }),
|
||||
document.incAndSet('system', document.TYPE_BLOG, did, 'nReply', 1, { updateAt: new Date() }),
|
||||
]);
|
||||
return drid;
|
||||
}
|
||||
|
||||
export function setStar(did: ObjectId, uid: number, star: boolean) {
|
||||
return document.setStatus('system', document.TYPE_BLOG, did, uid, { star });
|
||||
}
|
||||
|
||||
export function getStatus(did: ObjectId, uid: number) {
|
||||
return document.getStatus('system', document.TYPE_BLOG, did, uid);
|
||||
}
|
||||
|
||||
export function setStatus(did: ObjectId, uid: number, $set) {
|
||||
return document.setStatus('system', document.TYPE_BLOG, did, uid, $set);
|
||||
}
|
||||
|
||||
global.Hydro.model.blog = {
|
||||
add,
|
||||
get,
|
||||
inc,
|
||||
edit,
|
||||
del,
|
||||
count,
|
||||
getMulti,
|
||||
addReply,
|
||||
setStar,
|
||||
getStatus,
|
||||
setStatus,
|
||||
};
|
Loading…
Reference in New Issue