diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 16423b5..269dced 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -19,5 +19,6 @@ module.exports = { }, rules: { 'no-async-promise-executor': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', } }; diff --git a/package.json b/package.json index 8715295..e3cabc6 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "scripts": { "dev": "svelte-kit dev --port 60000", "build": "svelte-kit build", + "start": "node run.js", "package": "svelte-kit package", "preview": "svelte-kit preview", "prepare": "svelte-kit sync", @@ -17,6 +18,7 @@ "@playwright/test": "^1.22.2", "@sveltejs/adapter-auto": "next", "@sveltejs/kit": "next", + "@types/js-cookie": "^3.0.2", "@types/lodash-es": "^4.17.6", "@types/secure-random": "^1.1.0", "@typescript-eslint/eslint-plugin": "^5.27.0", @@ -42,12 +44,15 @@ "argon2": "^0.28.5", "date-fns": "^2.28.0", "date-fns-tz": "^1.3.5", + "dto-mapping": "^1.1.0", "http-status-codes": "^2.2.0", + "js-cookie": "^3.0.1", "ky": "^0.31.0", "ky-universal": "^0.10.1", "lodash-es": "^4.17.21", "material-icons": "^1.11.3", "njwt": "^1.2.0", + "polka": "^0.5.2", "sass": "^1.53.0", "secure-random": "^1.1.2", "svelte-material-icons": "^2.0.2" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6ed1105..0c73a2a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,7 @@ specifiers: '@sveltejs/adapter-auto': next '@sveltejs/adapter-node': 1.0.0-next.78 '@sveltejs/kit': next + '@types/js-cookie': ^3.0.2 '@types/lodash-es': ^4.17.6 '@types/secure-random': ^1.1.0 '@typescript-eslint/eslint-plugin': ^5.27.0 @@ -14,15 +15,18 @@ specifiers: autoprefixer: ^10.4.7 date-fns: ^2.28.0 date-fns-tz: ^1.3.5 + dto-mapping: ^1.1.0 eslint: ^8.16.0 eslint-config-prettier: ^8.3.0 eslint-plugin-svelte3: ^4.0.0 http-status-codes: ^2.2.0 + js-cookie: ^3.0.1 ky: ^0.31.0 ky-universal: ^0.10.1 lodash-es: ^4.17.21 material-icons: ^1.11.3 njwt: ^1.2.0 + polka: ^0.5.2 postcss: ^8.4.14 prettier: ^2.6.2 prettier-plugin-svelte: ^2.7.0 @@ -42,20 +46,24 @@ dependencies: argon2: 0.28.5 date-fns: 2.28.0 date-fns-tz: 1.3.5_date-fns@2.28.0 + dto-mapping: 1.1.0 http-status-codes: 2.2.0 + js-cookie: 3.0.1 ky: 0.31.0 ky-universal: 0.10.1_ky@0.31.0 lodash-es: 4.17.21 material-icons: 1.11.3 njwt: 1.2.0 + polka: 0.5.2 sass: 1.53.0 secure-random: 1.1.2 svelte-material-icons: 2.0.2_svelte@3.48.0 devDependencies: '@playwright/test': 1.23.0 - '@sveltejs/adapter-auto': 1.0.0-next.52 - '@sveltejs/kit': 1.0.0-next.355_sass@1.53.0+svelte@3.48.0 + '@sveltejs/adapter-auto': 1.0.0-next.53 + '@sveltejs/kit': 1.0.0-next.357_sass@1.53.0+svelte@3.48.0 + '@types/js-cookie': 3.0.2 '@types/lodash-es': 4.17.6 '@types/secure-random': 1.1.0 '@typescript-eslint/eslint-plugin': 5.30.0_5mtqsiui4sk53pmkx7i7ue45wm @@ -76,6 +84,11 @@ devDependencies: packages: + /@arr/every/1.0.1: + resolution: {integrity: sha512-UQFQ6SgyJ6LX42W8rHCs8KVc0JS0tzVL9ct4XYedJukskYVWTo49tNiMEK9C2HTyarbNiT/RVIRSY82vH+6sTg==} + engines: {node: '>=4'} + dev: false + /@eslint/eslintrc/1.3.0: resolution: {integrity: sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -180,6 +193,10 @@ packages: playwright-core: 1.23.0 dev: true + /@polka/url/0.5.0: + resolution: {integrity: sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw==} + dev: false + /@rollup/pluginutils/4.2.1: resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} @@ -188,26 +205,26 @@ packages: picomatch: 2.3.1 dev: true - /@sveltejs/adapter-auto/1.0.0-next.52: - resolution: {integrity: sha512-jOuC7RauiwGg7BQQEZxBGcwtwynNqQSuGJ7MJ9kk5WIrFCMrZSclwnpO1yLmUUYFKvJ61Z7bvVoDqm6+CgLEaw==} + /@sveltejs/adapter-auto/1.0.0-next.53: + resolution: {integrity: sha512-LyaeU0rkcymGWvV/3K26AZxqG/+ZQHwa+hrx3xsbmOykjQ2WQPTXRVwmH23zV4A5ABvni76LRMsQOoqWzP3G9Q==} dependencies: - '@sveltejs/adapter-cloudflare': 1.0.0-next.23 - '@sveltejs/adapter-netlify': 1.0.0-next.65 + '@sveltejs/adapter-cloudflare': 1.0.0-next.24 + '@sveltejs/adapter-netlify': 1.0.0-next.66 '@sveltejs/adapter-vercel': 1.0.0-next.59 transitivePeerDependencies: - encoding - supports-color dev: true - /@sveltejs/adapter-cloudflare/1.0.0-next.23: - resolution: {integrity: sha512-WaDE25Ib3Q9kM1BBxvGxr57vfExg0Q1Wu2H3dSFV4Apw18UHKS89P/U6wd4u4zAzAw+Mcm8gduX/rRs5z0YMwA==} + /@sveltejs/adapter-cloudflare/1.0.0-next.24: + resolution: {integrity: sha512-g1QSrjWYjM6sfJB+pQn52EIfbVFjpk23GYsj5PLt2Gi3zRNfLRbpkFkPeyAOZbAfT4k/9lUqfLW+pkh+W3yxlg==} dependencies: esbuild: 0.14.47 worktop: 0.8.0-next.14 dev: true - /@sveltejs/adapter-netlify/1.0.0-next.65: - resolution: {integrity: sha512-81LYVqT0Fez7xqvOdE9ITD7b5kxdzzXjXwJ0ISBfJYt6wqg0fmABm3mcDy3opXau7DoQkhkhnlqkharTHfhJQg==} + /@sveltejs/adapter-netlify/1.0.0-next.66: + resolution: {integrity: sha512-UypTRnTd+R1O6SaDdc8l3A3c9/mQF8xLNoVb3Ay5ipb7uPU5WmjVYjfLVGyeVy67gztFfeFC/9Esu4OI2Ayx1A==} dependencies: '@iarna/toml': 2.2.5 esbuild: 0.14.47 @@ -231,8 +248,8 @@ packages: - supports-color dev: true - /@sveltejs/kit/1.0.0-next.355_sass@1.53.0+svelte@3.48.0: - resolution: {integrity: sha512-4M+BZL/kKgw5tMRkAYcpg38hxMX80iz6H9+nv2v6VzvKhD4Pw6zatzMs2D6PCT2uBHnrKGrUCheBBcK8kZdWKg==} + /@sveltejs/kit/1.0.0-next.357_sass@1.53.0+svelte@3.48.0: + resolution: {integrity: sha512-nCAehVybIEpQNnPu61V/EFVdfDb1nBSiQUfW9EcSSDEUbyAMCVBOKZZuzQ0qQDp3xniqRkyDzpBA4wN+ADxHBw==} engines: {node: '>=16.7'} hasBin: true peerDependencies: @@ -274,6 +291,10 @@ packages: - supports-color dev: true + /@types/js-cookie/3.0.2: + resolution: {integrity: sha512-6+0ekgfusHftJNYpihfkMu8BWdeHs9EOJuGcSofErjstGPfPGEu9yTu4t460lTzzAMl2cM5zngQJqPMHbbnvYA==} + dev: true + /@types/json-schema/7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true @@ -855,6 +876,10 @@ packages: resolution: {integrity: sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==} dev: false + /dto-mapping/1.1.0: + resolution: {integrity: sha512-vuUbiZXzwWf8eXxVs88rXgTw3uP6UbluIZT4J6QET5vqI1UaSCR9pywxT1LZ8Y3zU0l0HTfFkSt6cEm4FIOXng==} + dev: false + /ecdsa-sig-formatter/1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} dependencies: @@ -1566,6 +1591,11 @@ packages: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true + /js-cookie/3.0.1: + resolution: {integrity: sha512-+0rgsUXZu4ncpPxRL+lNEptWMOWl9etvPHc/koSRp6MPwpRYAhmk0dUG00J4bxVV3r9uUzfo24wW0knS07SKSw==} + engines: {node: '>=12'} + dev: false + /js-yaml/4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -1652,6 +1682,13 @@ packages: dependencies: semver: 6.3.0 + /matchit/1.1.0: + resolution: {integrity: sha512-+nGYoOlfHmxe5BW5tE0EMJppXEwdSf8uBA1GTZC7Q77kbT35+VKLYJMzVNWCHSsga1ps1tPYFtFyvxvKzWVmMA==} + engines: {node: '>=6'} + dependencies: + '@arr/every': 1.0.1 + dev: false + /material-icons/1.11.3: resolution: {integrity: sha512-pGkZ5LJOLH/R3jlqL2HXGVi36cucTn/RxSkPtqsinxB2xoDE0fZStwAUZlMKcW0z4qqDamBwVvzM1IVQe6OQDw==} dev: false @@ -2020,6 +2057,13 @@ packages: hasBin: true dev: true + /polka/0.5.2: + resolution: {integrity: sha512-FVg3vDmCqP80tOrs+OeNlgXYmFppTXdjD5E7I4ET1NjvtNmQrb1/mJibybKkb/d4NA7YWAr1ojxuhpL3FHqdlw==} + dependencies: + '@polka/url': 0.5.0 + trouter: 2.0.1 + dev: false + /postcss-import/14.1.0_postcss@8.4.14: resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} engines: {node: '>=10.0.0'} @@ -2594,6 +2638,13 @@ packages: /tr46/0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + /trouter/2.0.1: + resolution: {integrity: sha512-kr8SKKw94OI+xTGOkfsvwZQ8mWoikZDd2n8XZHjJVZUARZT+4/VV6cacRS6CLsH9bNm+HFIPU1Zx4CnNnb4qlQ==} + engines: {node: '>=6'} + dependencies: + matchit: 1.1.0 + dev: false + /tslib/1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true diff --git a/run.js b/run.js new file mode 100644 index 0000000..32beee0 --- /dev/null +++ b/run.js @@ -0,0 +1,8 @@ +import polka from 'polka'; +import {handler} from './build/handler'; + +const server = polka(); + +server + .use(handler) + .listen(process?.env?.PORT ?? 3000) \ No newline at end of file diff --git a/src/hooks.ts b/src/hooks.ts index 5f85ee4..92e8a52 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -1,13 +1,55 @@ import type {RequestEvent, ResolveOptions} from '@sveltejs/kit'; import type {MaybePromise} from '@sveltejs/kit/types/private'; - +import _ from 'lodash-es'; +import njwt from 'njwt'; +import {CookieParser} from './lib/cookie-parser'; +import {key} from './lib/auth/user/server'; /** @type {import('@sveltejs/kit').Handle} */ export async function handle({event, resolve}: HandleParameter) { + try { + event.locals.user = await getUser(event.request.headers.get('cookie')); + } catch (e) { + console.error('[hooks]', e); + } const response = await resolve(event); return response; } +async function getUser(cookie: string | null) { + if (_.isEmpty(cookie)) { + return undefined; + } + + const cookies = (new CookieParser(cookie!)).get(); + if (!cookies.token) { + return undefined; + } + + const jwt = njwt.verify(cookies.token, key); + if (!jwt) { + return undefined; + } + + if (jwt.isExpired()) { + const refresh = njwt.verify(cookies.refresh ?? ''); + if (refresh?.isExpired() === false) { + // todo: sign again + } + } + + const body = jwt.body.toJSON(); +} + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace App { + interface Locals { + user: any + } + } +} + interface HandleParameter { event: RequestEvent, resolve: (event: RequestEvent, opts?: ResolveOptions) => MaybePromise diff --git a/src/lib/auth/user/server.ts b/src/lib/auth/user/server.ts index b9bf862..f2a900a 100644 --- a/src/lib/auth/user/server.ts +++ b/src/lib/auth/user/server.ts @@ -4,22 +4,28 @@ import {aql} from 'arangojs/aql'; import type {IArangoDocumentIdentifier} from '$lib/database'; import njwt from 'njwt'; import secureRandom from 'secure-random'; -import {EUserRanks} from '$lib/types/UserRanks'; +import {EUserRanks} from '$lib/types/user-ranks'; -const key = +export const key = process.env.USE_SPECIFIC_KEY ?? secureRandom(256, {type: 'Buffer'}); +console.log(key); + export class User { - constructor(private readonly id: string) { + constructor(readonly id: string) { } private stored: IUserInfo | undefined; get data(): Promise { return new Promise(async (resolve, reject) => { + if (!await this.exists) { + return reject('user not exists'); + } + db.query(aql` for user in users - filter user._key == ${this.id} + filter user.id == ${this.id} return user`) .then(async (cursor) => { if (!cursor.hasNext) { @@ -40,25 +46,48 @@ export class User { get exists(): Promise { return new Promise(async (resolve, reject) => { - const user = await this.loadUserData(); - }) + db.query(aql` + for user in users + filter user.id == ${this.id} + return user`) + .then(async (r) => { + resolve(r.hasNext); + }) + .catch(reject); + }); } async register(password: string) { + if (await this.exists) { + throw Error('user exists already'); + } + + const hashed = await argon2.hash(password); + await db.query(aql` - insert ${{_key: this.id, password, rank: EUserRanks.User}} into users`); + insert ${{id: this.id, password: hashed, rank: EUserRanks.User}} into users`); } - async verify(password: string) { - const user = await this.loadUserData(); - return await argon2.verify(user.password, password); + /** + * + * @param password 비밀번호 평문 + */ + async verify(password: string): Promise { + try { + const user = await this.loadUserData(); + return await argon2.verify(user.password, password); + } catch (e) { + console.log(e) + return false; + } } - token(type: 'user' | 'refesh') { + token(type: 'user' | 'refesh', payload: Rec = {}) { return njwt.create({ iss: 'https://now.gd/', - sub: `users/${this.id}`, + sub: `user/${this.id}`, scope: type, + ...payload, }, key); } } diff --git a/src/lib/community/article.ts b/src/lib/community/article.ts index e87d7a0..0089ce5 100644 --- a/src/lib/community/article.ts +++ b/src/lib/community/article.ts @@ -1,7 +1,7 @@ -import db from '../database/instance'; +import db from '$lib/database/instance'; import {aql} from 'arangojs/aql'; -export class ArticleManage { +export class ArticleManager { title: string | undefined; content: string | undefined; @@ -9,6 +9,10 @@ export class ArticleManage { constructor(private readonly id: string) { } + post(title: string, content: string) { + + } + get exists(): Promise { return new Promise(async (resolve, reject) => { db.query(aql` diff --git a/src/lib/community/board.ts b/src/lib/community/board.ts index 5b1c678..fbd64f3 100644 --- a/src/lib/community/board.ts +++ b/src/lib/community/board.ts @@ -1,6 +1,6 @@ import db from '../database/instance'; import {aql} from 'arangojs/aql'; -import type {EUserRanks} from '../types/UserRanks'; +import type {EUserRanks} from '../types/user-ranks'; export class BoardManager { constructor(private readonly id: string) { diff --git a/src/lib/components/ArticleList.svelte b/src/lib/components/ArticleList.svelte new file mode 100644 index 0000000..90f6c0b --- /dev/null +++ b/src/lib/components/ArticleList.svelte @@ -0,0 +1,7 @@ + + + diff --git a/src/lib/cookie-parser.ts b/src/lib/cookie-parser.ts new file mode 100644 index 0000000..e4013d8 --- /dev/null +++ b/src/lib/cookie-parser.ts @@ -0,0 +1,21 @@ +export class CookieParser { + constructor(private readonly cookie: string) { + cookie + .split(';') + .map(v => v.trim()) + .map(v => v.split('=')) + .forEach((value) => { + this.cookies[value[0]] = value[1] ?? undefined; + }); + } + + private readonly cookies: Rec = {}; + + static new(cookie: string): CookieParser { + return new CookieParser(cookie); + } + + get(): Rec { + return this.cookies; + } +} \ No newline at end of file diff --git a/src/lib/database/index.ts b/src/lib/database/index.ts index ce9f1f3..cb083da 100644 --- a/src/lib/database/index.ts +++ b/src/lib/database/index.ts @@ -26,10 +26,14 @@ export default class DefaultDatabase { constructor() { DefaultDatabase.url = DefaultDatabase.info.url; this.system = new Database(DefaultDatabase.url); - this.init().then(); + // this.init().then(); } private async init() { + if (this.db) { + return; + } + // console.log('init') const info = DefaultDatabase.info; const token = await this.system.login(info.user, info.password); @@ -62,7 +66,7 @@ export default class DefaultDatabase { } query(q: AqlQuery) { - return this.db.query(q); + return this.init().then(() => this.db.query(q)); } } diff --git a/src/lib/types/article.ts b/src/lib/types/article.ts new file mode 100644 index 0000000..444a7e8 --- /dev/null +++ b/src/lib/types/article.ts @@ -0,0 +1,11 @@ +import type {IArangoDocumentIdentifier} from '$lib/database'; +import type {ArticleDto} from './dto/article.dto'; + +export interface IArticle extends ArticleDto, IArangoDocumentIdentifier { + // title: string; + // content: string; + author: string; // uid + comments: string[]; // comments ids + board: string; // board id + // tags: string[]; +} \ No newline at end of file diff --git a/src/lib/types/dto/article.dto.ts b/src/lib/types/dto/article.dto.ts new file mode 100644 index 0000000..436e6d6 --- /dev/null +++ b/src/lib/types/dto/article.dto.ts @@ -0,0 +1,22 @@ +/* eslint-disable @typescript-eslint/no-explicit-any, + @typescript-eslint/no-empty-function, + @typescript-eslint/no-unused-vars*/ + +import {Entity, SafeType} from 'dto-mapping'; + +@Entity() +export class ArticleDto { + constructor(_obj: any) {} + + @SafeType({type: String}) + bid?: string; + + @SafeType({ type: String }) + title?: string; + + @SafeType({type: String}) + content?: string; + + @SafeType({type: Array}) + tags?: string[]; +} \ No newline at end of file diff --git a/src/lib/types/dto/login.dto.ts b/src/lib/types/dto/login.dto.ts new file mode 100644 index 0000000..3b1a903 --- /dev/null +++ b/src/lib/types/dto/login.dto.ts @@ -0,0 +1,16 @@ +/* eslint-disable @typescript-eslint/no-explicit-any, + @typescript-eslint/no-empty-function, + @typescript-eslint/no-unused-vars*/ + +import {Entity, SafeType} from 'dto-mapping'; + +@Entity() +export class LoginDto { + constructor(_obj: any) {} + + @SafeType({type: String}) + id?: string; + + @SafeType({ type: String }) + password?: string; +} \ No newline at end of file diff --git a/src/lib/types/UserRanks.ts b/src/lib/types/user-ranks.ts similarity index 100% rename from src/lib/types/UserRanks.ts rename to src/lib/types/user-ranks.ts diff --git a/src/lib/types/user.ts b/src/lib/types/user.ts new file mode 100644 index 0000000..724d476 --- /dev/null +++ b/src/lib/types/user.ts @@ -0,0 +1,14 @@ +import type {IArangoDocumentIdentifier} from '$lib/database'; +import type {EUserRanks} from './user-ranks'; + +export interface IUser extends IArangoDocumentIdentifier { + id: string; + rank: EUserRanks; + profile?: IUserProfile; + remainTags: number; +} + +interface IUserProfile { + name: string; + ext: string[]; +} \ No newline at end of file diff --git a/src/routes/community/[id=integer]/[article]/delete.svelte b/src/routes/community/[id=integer]/[article=integer]/delete.svelte similarity index 100% rename from src/routes/community/[id=integer]/[article]/delete.svelte rename to src/routes/community/[id=integer]/[article=integer]/delete.svelte diff --git a/src/routes/community/[id=integer]/[article]/edit.svelte b/src/routes/community/[id=integer]/[article=integer]/edit.svelte similarity index 100% rename from src/routes/community/[id=integer]/[article]/edit.svelte rename to src/routes/community/[id=integer]/[article=integer]/edit.svelte diff --git a/src/routes/community/[id=integer]/[article]/index.svelte b/src/routes/community/[id=integer]/[article=integer]/index.svelte similarity index 100% rename from src/routes/community/[id=integer]/[article]/index.svelte rename to src/routes/community/[id=integer]/[article=integer]/index.svelte diff --git a/src/routes/community/[id=integer]/[article]/move.svelte b/src/routes/community/[id=integer]/[article=integer]/move.svelte similarity index 100% rename from src/routes/community/[id=integer]/[article]/move.svelte rename to src/routes/community/[id=integer]/[article=integer]/move.svelte diff --git a/src/routes/community/[id=integer]/[article=integer]/write.ts b/src/routes/community/[id=integer]/[article=integer]/write.ts new file mode 100644 index 0000000..34b7bcd --- /dev/null +++ b/src/routes/community/[id=integer]/[article=integer]/write.ts @@ -0,0 +1,83 @@ +import type {RequestEvent, RequestHandlerOutput} from '@sveltejs/kit'; +import _ from 'lodash-es'; +import HttpStatus from 'http-status-codes'; +import db from '$lib/database/instance'; +import { aql } from 'arangojs/aql'; +import {BoardManager} from '$lib/community/board'; +import {ArticleDto} from '$lib/types/dto/article.dto'; + +// noinspection JSUnusedGlobalSymbols +export async function post({request, locals}: RequestEvent): Promise { + const article = new ArticleDto(await request.json()) + const write = new WriteRequest(article); + + try { + await write.saveToDB() + } catch (e: any) { + return { + status: HttpStatus.FORBIDDEN, + body: { + reason: e.toString(), + } + } + } + + return { + status: write.status, + body: { + aid: write.id, + } + } +} + +class WriteRequest { + error: string | null = null; + id?: string + private board: BoardManager; + + constructor(private body: ArticleDto) { + if (this.isTitleEmpty || this.isContentEmpty) { + this.error = 'some field is empty'; + } + + this.board = new BoardManager(this.boardId); + } + + get boardId(): string | undefined { + return this.body.bid + } + + get title(): string | undefined { + return this.body.title + } + + get content(): string | undefined { + return this.body.content + } + + private get isBoardExists(): Promise { + return this.board.exists; + } + + get isTitleEmpty(): boolean { + return _.isEmpty(this.title); + } + + get isContentEmpty(): boolean { + return _.isEmpty(this.content); + } + + async saveToDB() { + if (await this.isBoardExists) { + this.error = 'board not exsists' + return; + } + + return await db.query(aql` + insert ${{title: this.title, content: this.content}} into articles`); + } + + get status(): number { + return _.isEmpty(this.error) ? HttpStatus.CREATED : HttpStatus.BAD_REQUEST; + } +} \ No newline at end of file diff --git a/src/routes/community/[id=integer]/[article]/write.ts b/src/routes/community/[id=integer]/[article]/write.ts deleted file mode 100644 index b2055c7..0000000 --- a/src/routes/community/[id=integer]/[article]/write.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type {RequestEvent, RequestHandlerOutput} from '@sveltejs/kit'; -import _ from 'lodash-es'; -import HttpStatus from 'http-status-codes'; -import db from '$lib/database/instance'; -import { aql } from 'arangojs/aql'; -import {BoardManager} from '../../../../lib/community/board'; - -export async function post({request}: RequestEvent): Promise { - const write = new WriteRequst(await request.json()); - - return { - - } -} - -class WriteRequst { - error: string | null = null; - private board: BoardManager; - - constructor(private body: Rec) { - if (this.isTitleEmpty || this.isBodyEmpty) { - this.error = 'some field is empty'; - } - - this.board = new BoardManager(this.boardId); - } - - get boardId(): string { - return this.body.id.toString() - } - - private get isBoardExists(): Promise { - return this.board.exists; - } - - get isTitleEmpty(): boolean { - return _.isEmpty(this.body.title); - } - - get isBodyEmpty(): boolean { - return _.isEmpty(this.body.body); - } - - async saveToDB() { - if (await this.isBoardExists) { - this.error = 'board not exsists' - return; - } - - return await db.query(aql`insert to articles`); - } - - get status(): number { - return _.isEmpty(this.error) ? HttpStatus.CREATED : HttpStatus.BAD_REQUEST; - } -} \ No newline at end of file diff --git a/src/routes/join.svelte b/src/routes/join.svelte index c8b3664..0140d8e 100644 --- a/src/routes/join.svelte +++ b/src/routes/join.svelte @@ -1,6 +1,6 @@