Add: Post creation
This commit is contained in:
parent
96a9f3971d
commit
ff34d5fd96
@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import Glowfx from "./fx/glowfx.svelte";
|
||||
import Glowfx from "./fx/glowfx.svelte";
|
||||
|
||||
/**
|
||||
* @type {boolean | null}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { sql } from '$lib/db.server';
|
||||
import { getCategoriesCachedByRef, getCategoryCached } from './category';
|
||||
import { getUser, getUsersCachedByRef } from './user';
|
||||
import { isPostgresError } from './root';
|
||||
import { getUser, getUsersCachedByRef, sqlUserFromToken } from './user';
|
||||
|
||||
/**
|
||||
* @typedef {import('$types/base').Post} Post
|
||||
@ -174,3 +175,69 @@ export async function getPost(post_id, opts = {}) {
|
||||
*/
|
||||
return parsePostFromRow(author, category, post, withMetrics);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} token
|
||||
* @param {import('$types/base').Category} category
|
||||
* @param {string} name
|
||||
* @param {string} content
|
||||
* @returns {Promise<import('$types/status').Success | import('$types/status').Error>}
|
||||
*/
|
||||
export async function createPost(token, category, name, content) {
|
||||
const insert = sql`
|
||||
INSERT INTO doki8902.message_post (author_id, category_id, name, latest_content)
|
||||
VALUES (
|
||||
(${ sqlUserFromToken(token) }),
|
||||
${ category.id }, ${ name }, ${ content }
|
||||
)
|
||||
RETURNING id;`;
|
||||
|
||||
let result;
|
||||
|
||||
try {
|
||||
result = await insert;
|
||||
} catch (e) {
|
||||
const pgerr = isPostgresError(e);
|
||||
|
||||
switch (pgerr?.constraint_name) {
|
||||
case 'message_require_user':
|
||||
return {
|
||||
error: true,
|
||||
msg: 'User token was invalid',
|
||||
};
|
||||
|
||||
case 'post_category_id_fkey':
|
||||
return {
|
||||
error: true,
|
||||
msg: 'Post category does not exist',
|
||||
};
|
||||
|
||||
case 'content_min_length':
|
||||
return {
|
||||
error: true,
|
||||
msg: 'Post content cannot be empty',
|
||||
};
|
||||
|
||||
case 'name_min_length':
|
||||
return {
|
||||
error: true,
|
||||
msg: 'Post name cannot be empty',
|
||||
};
|
||||
|
||||
default:
|
||||
console.log(e);
|
||||
return {
|
||||
error: true,
|
||||
msg: 'Unknown error (notify dev)',
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const postId = result[0]['id'];
|
||||
|
||||
return {
|
||||
success: true,
|
||||
result: postId,
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,3 +1,14 @@
|
||||
/**
|
||||
* @param {any} e
|
||||
* @returns {import('postgres').PostgresError | null}
|
||||
*/
|
||||
export function isPostgresError(e) {
|
||||
if (e && typeof(e) === 'object' && 'name' in e && e.name === 'PostgresError') {
|
||||
return e;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {import('node-cache')} cache
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { createCache } from '$lib/cache.server';
|
||||
import { cacheUpdater, cachedMethod, refExtendCachedMethod } from './root';
|
||||
import { cacheUpdater, cachedMethod, isPostgresError, refExtendCachedMethod } from './root';
|
||||
import { sql } from '$lib/db.server';
|
||||
import { argon2id, hash, verify } from 'argon2';
|
||||
import { PostgresError } from 'postgres';
|
||||
@ -33,6 +33,14 @@ function parseUserFromRow(row) {
|
||||
*/
|
||||
const updateUserCache = cacheUpdater(cache);
|
||||
|
||||
/**
|
||||
* @param {string} token
|
||||
* @returns {import('postgres').PendingQuery<import('postgres').Row[]>}
|
||||
*/
|
||||
export function sqlUserFromToken(token) {
|
||||
return sql`SELECT user_id FROM doki8902.user_session WHERE token = ${ token }`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number[]} user_ids
|
||||
* @returns {Promise<Result<User>>}
|
||||
@ -49,9 +57,9 @@ export async function getUsers(user_ids) {
|
||||
if (user_ids.length == 0) return new Map();
|
||||
|
||||
const query = sql`
|
||||
SELECT id, username, join_time
|
||||
FROM doki8902.user
|
||||
WHERE id IN ${ sql(user_ids) };`;
|
||||
SELECT id, username, join_time
|
||||
FROM doki8902.user
|
||||
WHERE id IN ${ sql(user_ids) };`;
|
||||
|
||||
let users = await query;
|
||||
|
||||
@ -100,33 +108,34 @@ export async function createUser(username, password) {
|
||||
|
||||
try {
|
||||
await insert;
|
||||
|
||||
} catch (e) {
|
||||
if (e && typeof(e) === 'object' && 'name' in e && e.name === 'PostgresError') {
|
||||
const pgerr = /** @type {PostgresError} */ (e);
|
||||
const pgerr = isPostgresError(e);
|
||||
|
||||
switch (pgerr.constraint_name) {
|
||||
case 'idx_user_username':
|
||||
return {
|
||||
error: true,
|
||||
msg: "Username taken",
|
||||
};
|
||||
switch (pgerr?.constraint_name) {
|
||||
case 'idx_user_username':
|
||||
return {
|
||||
error: true,
|
||||
msg: "Username taken",
|
||||
};
|
||||
|
||||
case 'username_length_min':
|
||||
return {
|
||||
error: true,
|
||||
msg: "Username has invalid length",
|
||||
};
|
||||
case 'username_length_min':
|
||||
return {
|
||||
error: true,
|
||||
msg: "Username has invalid length",
|
||||
};
|
||||
|
||||
case 'username_valid_symbols':
|
||||
return {
|
||||
error: true,
|
||||
msg: "Username contains invalid symbols",
|
||||
};
|
||||
case 'username_valid_symbols':
|
||||
return {
|
||||
error: true,
|
||||
msg: "Username contains invalid symbols",
|
||||
};
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.log(e);
|
||||
return {
|
||||
error: true,
|
||||
msg: 'Unknown error (notify dev)',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -178,3 +187,47 @@ export async function createUserSession(username, password) {
|
||||
result: token[0]['token'],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} token
|
||||
* @returns {Promise<number | import('$types/status').Error>}
|
||||
*/
|
||||
export async function getUserIDOfSession(token) {
|
||||
const query = sql`
|
||||
SELECT user_id
|
||||
FROM doki8902.user_session
|
||||
WHERE token = ${ token }`;
|
||||
|
||||
const result = await query;
|
||||
|
||||
if (result.length == 0) {
|
||||
return {
|
||||
error: true,
|
||||
msg: "Invalid user session",
|
||||
};
|
||||
}
|
||||
|
||||
return result[0]['user_id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} token
|
||||
* @returns {Promise<User | import('$types/status').Error>}
|
||||
*/
|
||||
export async function getUserOfSession(token) {
|
||||
const query = sql`
|
||||
SELECT user_id
|
||||
FROM doki8902.user_session
|
||||
WHERE token = ${ token }`;
|
||||
|
||||
const result = await query;
|
||||
|
||||
if (result.length == 0) {
|
||||
return {
|
||||
error: true,
|
||||
msg: "Invalid user session",
|
||||
};
|
||||
}
|
||||
|
||||
return result[0]['user_id'];
|
||||
}
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
import { getCategories, getCategoriesCached } from '$lib/server/db/category.js';
|
||||
import { createPost } from '$lib/server/db/post.js';
|
||||
import { getUserIDOfSession } from '$lib/server/db/user.js';
|
||||
import { parseIntNull } from '$lib/util.js';
|
||||
import { error, redirect } from '@sveltejs/kit';
|
||||
|
||||
export async function load({ cookies }) {
|
||||
if (!cookies.get('token')) {
|
||||
redirect(302, '/register');
|
||||
}
|
||||
|
||||
const categories = await getCategories();
|
||||
|
||||
return {
|
||||
categories: Array(...categories.values())
|
||||
};
|
||||
}
|
||||
|
||||
/** @type {import('@sveltejs/kit').Action} */
|
||||
async function POST({ request, cookies }) {
|
||||
if (request.method !== 'POST') {
|
||||
return;
|
||||
}
|
||||
|
||||
const userToken = cookies.get('token');
|
||||
|
||||
if (!userToken) {
|
||||
error(401, 'Need to be logged in!');
|
||||
}
|
||||
|
||||
const data = await request.formData();
|
||||
const categoryId = parseIntNull(data.get('category')?.toString());
|
||||
const name = data.get('name')?.toString();
|
||||
const content = data.get('content')?.toString();
|
||||
|
||||
if (!categoryId) {
|
||||
error(400, `Invalid category ID ${categoryId}`);
|
||||
}
|
||||
|
||||
if (!name || !content) {
|
||||
error(400, `Not all fields have been filled out`);
|
||||
}
|
||||
|
||||
const category = (await getCategoriesCached([categoryId])).get(categoryId);
|
||||
|
||||
if (!category) {
|
||||
error(400, `Invalid category ID ${categoryId}`);
|
||||
}
|
||||
|
||||
const result = await createPost(userToken, category, name, content);
|
||||
|
||||
if ('error' in result) {
|
||||
|
||||
} else {
|
||||
redirect(303, `/posts/${result.result}`);
|
||||
}
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
default: POST,
|
||||
};
|
||||
@ -1,5 +1,10 @@
|
||||
<script>
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* categories: import("$types/base").Category[],
|
||||
* }}
|
||||
*/
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@ -12,6 +17,12 @@
|
||||
|
||||
<form action="/compose" method="post" class="composeForm">
|
||||
<h1>Compose</h1>
|
||||
<select name="category" id="category">
|
||||
{#each data.categories as category}
|
||||
<option value={category.id}></option>
|
||||
{/each}
|
||||
</select>
|
||||
<input type="text" name="name" id="name">
|
||||
<textarea name="content" id="content" rows="4"></textarea>
|
||||
<button type="submit">Post!</button>
|
||||
</form>
|
||||
Loading…
x
Reference in New Issue
Block a user