Compare commits

..

20 Commits

Author SHA1 Message Date
05d11e0ea5
refactor(api): change stuff in story updating route
if the story doesn't exist/is null, throw
ensure chapter word count is updated (since we weren't already doing that for some reason....)
instead of clearing the original story's chapters and re-adding them one by one, update the existing ones in place, otherwise push to the array
use new signature for `replaceOrUploadContent` call
ensure chapters are sorted
2024-07-09 21:10:03 -04:00
d030346dde
fix(components): replace console.error -> console.warn 2024-07-09 20:54:30 -04:00
6016813f4c
refactor(components): update story form
add `draftData` prop to distinguish whether we're editing a draft or a published story
actually renumber chapter indexes after reordering
use draftData prop in form change callback
2024-07-09 20:53:50 -04:00
4838b7b624
fix(components): un-scope story list styles 2024-07-09 20:40:24 -04:00
5c6cb84383
refactor(components): sidebar looks and acts prettier now 2024-07-09 20:39:49 -04:00
25b7e723f6
refactor(api): actually use doNotSelect filter when querying user at login 2024-07-09 20:36:41 -04:00
609562b7fa
refactor(client-side): make list actions async 2024-07-09 20:33:20 -04:00
a7b8c07952
refactor(components): navbar updates
add `key` attribute to `a-menu` element
only show "new story" button to logged in users
change `data` check to check for `data.user`
2024-07-09 20:31:53 -04:00
e28f6b6974
refactor(pages): add title to login page 2024-07-09 20:23:03 -04:00
af1e08227d
refactor(pages): remove top margin from home page 2024-07-09 20:20:10 -04:00
abbdc61e79
chore(dependencies): bump everything 2024-07-09 20:18:16 -04:00
1a3135c6c4
refactor(pages, components): rearrange layout
sidebars now open beside the actual page content instead of beside the body
2024-04-20 20:59:30 -04:00
05a20ff94e
fix(pages, components): fix inconsistent theme when logging in/out
manually set body's `data-theme` attribute to the appropriate value
2024-04-20 20:56:47 -04:00
c68762ceac
chore(dependencies): update tinymce related dependencies
add @tinymce/tinymce-vue, remove base tinymce package [we have a submodule for that ;)]
2024-04-20 19:31:07 -04:00
bed12e2eee
chore(dependencies): upgrade nuxt to latest version
also change nuxt-speedkit -> nuxt-booster
2024-04-20 19:28:57 -04:00
bb6d05be72
refactor(components): update icon component
merge styles passed in from a parent component inline
2024-04-20 19:22:11 -04:00
7932152025
refactor(db/models): re-introduce chapter index field
turns out we need it after all..
2024-04-20 19:17:43 -04:00
a88f418901
refactor(server/utils): update server-side constants
define array of sensitive user model fields to exclude when querying and returning
2024-04-02 01:33:15 -04:00
0d6acdf174
refactor(workspace): update tsconfig files
enable `esmoduleinterop` and `allowsyntheticdefaultimports`
2024-04-02 01:21:13 -04:00
25582dd1f1
fix(pages): update bands page
make "subscribe" or "unsubscribe" buttons update the ui immediately
2024-04-02 01:16:34 -04:00
19 changed files with 264 additions and 200 deletions

@ -24,7 +24,8 @@
<template>
<i
:style="{
fontSize: `${size}${propUnit}`,
...$attrs.style,
fontSize: pixi,
color: icolor || 'currentcolor',
}"
:class="`fa${styleMap[istyle]} fa-${name}`"

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { Grid } from "ant-design-vue";
import { Grid, ItemType } from "ant-design-vue";
const bp = Grid.useBreakpoint();
const { data, status } = useAuth();
const itemMap = ref({
@ -22,6 +22,7 @@
let selected: string[] = [cur.value];
const clickFn = (minfo) => {
console.log("clicky", minfo);
if (itemMap.value[minfo.key] === undefined) return;
cur.value = itemMap.value[minfo.key];
selected = [cur.value];
@ -43,12 +44,13 @@
}"
@click="clickFn"
:active-key="cur"
:key="data?.user?._id"
>
<a-menu-item key="home"> Home </a-menu-item>
<a-menu-item key="bands"> Bands </a-menu-item>
<a-menu-item key="authors"> Authors </a-menu-item>
<a-menu-item key="forum"> Message Board</a-menu-item>
<a-sub-menu title="My Stuff" v-if="!!data?.user" key="group/my-stuff">
<a-sub-menu :disabled="!data?.user" v-if="!!data?.user" title="My Stuff" key="group/my-stuff">
<a-menu-item key="account"> Account </a-menu-item>
<a-menu-item key="edit-profile"> Edit Profile </a-menu-item>
<a-menu-item key="profile"> View Profile </a-menu-item>
@ -60,9 +62,9 @@
<a-menu-item key="admin" v-if="data?.user?.profile.isAdmin || false"> Admin </a-menu-item>
<a-menu-item key="logout" v-if="!!data?.user"> Logout </a-menu-item>
</a-menu>
<div>
<div v-if="data?.user">
<nuxt-link to="/new-story">
<a-button v-if="data?.user" type="primary" tooltip="Post a New Story">
<a-button type="primary" tooltip="Post a New Story">
<!-- <template #icon>
</template> -->
<icon istyle="regular" name="file-plus" />
@ -70,7 +72,7 @@
</a-button>
</nuxt-link>
</div>
<div class="acbut" v-if="!data">
<div class="acbut" v-if="!data?.user">
<a-button size="large" @click="() => navigateTo('/auth/login')"> Login </a-button>
<a-button size="large" type="primary" @click="() => navigateTo('/auth/register')"> Register </a-button>
</div>

@ -3,6 +3,7 @@
import { ItemType, theme } from "ant-design-vue";
import Icon from "../icon.vue";
import { ISidebarItem } from "@models/sidebarEntry";
import { NuxtLink } from "#components";
const { useToken } = theme;
const { token } = useToken();
@ -10,8 +11,8 @@
const selState = ref<string>("");
const { data: injecto } = await useApiFetch<ISidebarItem[]>("/sidebar");
let items = reactive<ItemType[]>([
let custItems = computed(() => (injecto.value || ([] as ISidebarItem[])).sort((a, b) => a.index - b.index));
/*let items = ref<ItemType[]>([
{
key: "important",
label: h("span", { class: "smallcaps" }, ["Pinned"]),
@ -57,9 +58,7 @@
name: "sparkles",
size: 19,
}),
children: (injecto.value || ([] as ISidebarItem[]))
.sort((a, b) => a.index - b.index)
.map((b) => ({
children: custItems.value.map((b) => ({
key: b.url,
label: h(
"span",
@ -71,13 +70,12 @@
),
})),
} as SubMenuType,
]);
// console.log("wtf", items)
]);*/
</script>
<template>
<!-- <client-only>-->
<a-menu
id="sidebar-menu"
mode="inline"
@select="
({ item, key, selectedKeys }) => {
@ -89,37 +87,41 @@
"
:trigger-sub-menu-action="'click'"
v-model:active-key="selState"
:items="items"
:inline-indent="16"
>
<!-- <a-sub-menu>
<a-sub-menu key="important">
<template #icon>
<icon istyle="regular" name="thumbtack" :size="19" />
</template>
<template #title>
<sidebar-icon>
<span class="smallcaps">Pinned</span>
</template>
<a-menu-item key="/submission-rules">
<template #icon>
<Icon name="sparkles" istyle="regular" :size="19"/>
<icon istyle="regular" name="memo" :size="15" />
</template>
<template #rest>
<div class="smallcaps">
fun features
</div>
</template>
</sidebar-icon>
</template>
<a-menu-item>
<sidebar-icon>
<template #icon>
<icon name="memo" :icolor="token.colorInfo" istyle="regular" :size="13"/>
</template>
<template #rest>
<nuxt-link to="/submission-rules">
<b :style="{ color: token.colorInfo }">
submission rules
</b>
</nuxt-link>
</template>
</sidebar-icon>
<b :style="{ color: token.colorInfo }">SUBMISSION RULES</b>
</a-menu-item>
</a-sub-menu> -->
<a-menu-item key="/terms">
<template #icon>
<icon istyle="regular" name="globe" :size="15" />
</template>
<b>Terms of Service</b>
</a-menu-item>
</a-sub-menu>
<a-sub-menu key="fun-features">
<template #icon>
<Icon name="sparkles" istyle="regular" :size="19" />
</template>
<template #title>
<div class="smallcaps">fun features</div>
</template>
<a-menu-item v-for="item in custItems" :key="item.url">
<span :style="{ color: item.color }">
<nuxt-link :to="item.url">{{ item.linkTitle }}</nuxt-link>
</span>
</a-menu-item>
</a-sub-menu>
</a-menu>
<!-- </client-only>-->
</template>
@ -127,4 +129,7 @@
.smallcaps {
font-variant: small-caps;
}
#sidebar-menu ul {
height: 100%;
}
</style>

@ -52,8 +52,8 @@
/>
</div>
</template>
<style scoped>
.ant-list-items > * + * {
<style>
ul.ant-list-items > * + * {
margin-top: 1.2em;
}

@ -5,7 +5,7 @@
import { Field, FieldArray, useForm, useFieldArray } from "vee-validate";
import { ASpin } from "#components";
import { storySchema } from "@client/storyFormSchema";
import { FormStory, defaultChapter } from "@client/types/form/story";
import { FormStory, defaultChapter, FormChapter } from "@client/types/form/story";
import { autoEdit, autoSave, debouncedAutoEdit, debouncedAutoSave } from "@client/utils";
import findUser from "~/components/findUser.vue";
@ -20,17 +20,16 @@
endpoint: string;
endpointMethod: "put" | "post";
submitText?: string;
draftData?: {
endpoint: string;
endpointMethod: "put" | "post";
};
}>();
let w;
onMounted(() => {
w = window;
});
const dc = defaultChapter;
// data: FormStory;
const sdata = defineModel<FormStory>("data", {
required: true,
});
let drag = false;
let drag: boolean = false;
const expandos = ref<string[]>([]);
function logSubmit(values, actions) {
@ -46,27 +45,32 @@
otherBtnInvoked.value = false;
await autoSave(values);
} else {
const { data: dat } = await useApiFetch(`/story/new`, {
const { data: dat } = await useApiFetch<any>(`/story/new`, {
method: "post",
body: values,
});
if (dat.success) {
await router.push(`/story/${dat.story._id}/1`);
if (dat.value.success) {
await router.push(`/story/${dat.value.story._id}/1`);
}
}
} else {
await autoEdit(values, props.endpoint, props.endpointMethod);
}
}
function inval({ values, errors, results }) {
logSubmit(values, undefined);
}
const { values, setFieldValue, handleSubmit } = useForm<FormStory>({
keepValuesOnUnmount: true,
validationSchema: storySchema,
initialValues: sdata.value,
});
// const { push, remove, move, fields } = useFieldArray<FormChapter>("chapters");
function renumber(update: (idx: number, value: FormChapter) => void) {
for (let i = 0; i < values.chapters.length; i++) {
const nv = values.chapters[i];
nv.index = i + 1;
update(i, nv);
sdata.value.chapters[i].index = i + 1;
}
}
const subCb = handleSubmit(onSubmit);
const pushHOF = (push) => (e) => {
@ -96,7 +100,18 @@
};
</script>
<template>
<form data-testid="storyform" @submit="subCb" @change="() => (canDraft ? debouncedAutoSave(values) : debouncedAutoEdit(values, endpoint, endpointMethod))">
<form
data-testid="storyform"
@submit="subCb"
@change="
() =>
canDraft
? draftData
? debouncedAutoSave(values, draftData.endpoint, draftData.endpointMethod)
: debouncedAutoSave(values)
: debouncedAutoEdit(values, endpoint, endpointMethod)
"
>
<!-- <a-form v-bind:model="acData"> -->
<Field name="title" v-slot="{ value, field, errorMessage }">
@ -112,30 +127,26 @@
</Field>
<a-divider />
<!-- <test1/> -->
<field-array name="chapters" v-slot="{ fields, push, remove, move }">
<field-array name="chapters" v-slot="{ fields, push, remove, move, update }">
<client-only :fallback="h(ASpin)">
<div>
<div v-for="(element, index) in data.chapters">
<div v-for="(element, index) in values.chapters">
<client-only>
<Teleport :to="`#chapter-\\[${element.uuidKey}\\]`">
<a-collapse v-model:active-key="expandos" collapsible="icon">
<template #expandIcon="{ isActive }">
<span :data-testid="`storyform.chapters[${index}].collapse`"> <RightOutlined :rotate="isActive ? 90 : undefined" /></span>
</template>
<a-collapse-panel :key="`${element.uuidKey}`">
<a-collapse-panel :key="`${element.uuidKey}`" :data-testid="`storyform.chapters[${index}].outer`">
<template #header>
<div :data-testid="`storyform.chapters[${index}].header`" style="display: flex; justify-content: space-between">
<div :data-testid="`storyform.chapters[${index}].header`" style="display: flex; align-items: center; justify-content: space-between">
<span :data-testid="`storyform.chapters[${index}].titleEl`">{{ values.chapters[index]?.chapterTitle || "Untitled" }}</span>
<a-button
@click="
(e) => {
// let localFields = toRaw(fields);
// log.debug(`${index} | ${element.index}`);
// log.debug('fields->', localFields);
data.chapters.splice(index, 1);
remove(index);
// todo renumber
// renumber()
renumber(update);
}
"
>
@ -172,6 +183,7 @@
console.debug(e.moved);
move(e.moved.oldIndex, e.moved.newIndex);
data.chapters = lmove(data.chapters, e.moved.oldIndex, e.moved.newIndex);
renumber(update);
// w.tinymce.remove();
// log.debug(toRaw(acData.chapters.map((a) => toRaw(a))));
}
@ -191,3 +203,11 @@
<a-button html-type="submit" v-if="canDraft" @click="() => (otherBtnInvoked = true)"> Save for Later </a-button>
</form>
</template>
<style>
.ant-collapse-item > .ant-collapse-header {
align-items: center !important;
}
.ant-collapse-header.ant-collapse-icon-collapsible-only {
align-items: center !important;
}
</style>

@ -39,7 +39,7 @@
console.log(fileField.value);
}
} catch (e) {
console.error('not yet');
console.warn('not yet');
}
}
"

@ -50,7 +50,8 @@
<i> Nothing here but crickets. </i>
</template>
<a-layout class="ylayout">
<a-layout-header class="alayhead">
<a-layout>
<a-layout-header class="alayhead" :style="{ backgroundColor: darkBool ? '#141414' : '#f5f5f5' }">
<div style="display: flex; align-items: center; flex-wrap: wrap">
<div class="siteTitle">Rockfic</div>
<div class="stat-block">
@ -61,13 +62,21 @@
</div>
<navbar v-if="!!bp['md']" :inline="false" />
</div>
<a-button class="mobileTrigger" type="primary" @click="() => (collapsed = !collapsed)" v-if="!bp['md']">
<a-button class="mobileTrigger" type="primary" @click="() => (nav = !nav)" v-if="!bp['md']">
<menu-unfold-outlined v-if="nav" />
<menu-fold-outlined v-else />
</a-button>
</a-layout-header>
<a-layout class="mlayout" has-sider style="padding-top: 1em">
<a-layout-sider :trigger="null" :collapsed="true" :collapsed-width="0" :collapsible="true" v-model:collapsed="collapsed" v-if="!bp['md']">
<a-layout-sider
:trigger="null"
:theme="darkBool ? 'dark' : 'light'"
:collapsed="nav"
:collapsed-width="0"
:collapsible="true"
v-model:collapsed="collapsed"
v-if="!bp['md']"
>
<navbar inline />
</a-layout-sider>
<a-layout-content style="flex-grow: 1">
@ -92,9 +101,8 @@
:collapsed-width="0"
:style="{
color: col,
height: '100%',
position: 'fixed',
right: '0px',
right: '-3px',
alignSelf: 'stretch',
borderLeft: `2px solid ${darko ? '#fff' : '#ccc'}`,
}"
>
@ -112,6 +120,7 @@
<cfooter />
</a-layout-footer>
</a-layout>
</a-layout>
<!-- <div class="mlayout">
<a-skeleton/>
<a-skeleton/>
@ -137,7 +146,7 @@
.sideWrap {
height: 100vh;
right: -1rem;
right: -3rem;
width: inherit;
/* display: flex;
flex-direction: column;

@ -4,14 +4,14 @@ import { useRoute, useRouter } from "#app";
const base = `/user/me`;
export const favourites = (values: (any & { _id: number })[], id: number, remove: boolean, type: "story" | "author") => {
export const favourites = async (values: (any & { _id: number })[], id: number, remove: boolean, type: "story" | "author") => {
values?.splice(
values!.findIndex((a) => a._id == id),
1,
);
const key = type === "story" ? "stories" : "authors";
const todo = [id];
useApiFetch(`${base}/favs`, {
await useApiFetch(`${base}/favs`, {
method: "put",
body: {
[key]: {
@ -22,7 +22,7 @@ export const favourites = (values: (any & { _id: number })[], id: number, remove
});
};
export const subscriptions = (
export const subscriptions = async (
values: ((any & { _id: number }) | number)[],
id: number,
action: "hide" | "subscribe" | "unsubscribe",
@ -36,7 +36,7 @@ export const subscriptions = (
values!.findIndex((a) => a._id == id || a == id),
1,
);
useApiFetch(`${base}/${action}`, {
await useApiFetch(`${base}/${action}`, {
body: {
push: {
[type]: [id],
@ -46,7 +46,7 @@ export const subscriptions = (
method: "put",
});
} else if (action == "subscribe") {
useApiFetch(`${base}/subscriptions`, {
await useApiFetch(`${base}/subscriptions`, {
body: {
push: {
[type]: [id],
@ -56,7 +56,7 @@ export const subscriptions = (
method: "put",
});
} else if (action == "unsubscribe") {
useApiFetch(`${base}/subscriptions`, {
await useApiFetch(`${base}/subscriptions`, {
body: {
pull: {
[type]: [id],

@ -142,3 +142,4 @@ export const rc: RuntimeConfig & any = {
},
app: {},
};
export const doNotSelect = ["password", "auth", "ipLog"].map((b) => `-${b}`).join(" ");

@ -4,7 +4,7 @@ export interface IChapter {
title: string;
summary: string;
id?: number;
// index: number;
index: number;
words?: number;
notes: string;
genre: string[];
@ -25,6 +25,9 @@ export const Chapter = new Schema<IChapter>({
id: {
type: Number,
},
index: {
type: Number,
},
summary: {
type: String,
},

@ -4,8 +4,9 @@
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"dev": "nuxt dev --host 127.0.0.1",
"generate": "nuxt generate",
"postinstall": "nuxt prepare",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
@ -13,17 +14,27 @@
"@nuxt/devtools": "latest",
"@nuxtjs/i18n": "^8.0.0-rc.9",
"@types/uuid": "^9.0.4",
"@vue/language-server": "^1.8.27",
"nuxt": "^3.9.0",
"@vitejs/plugin-vue": "^5.0.2",
"@vitest/browser": "^1.1.2",
"@vitest/ui": "^1.1.3",
"@vue/language-server": "latest",
"@vue/test-utils": "^2.4.3",
"happy-dom": "^12.10.3",
"jsdom": "^23.0.1",
"nuxt": "^3.12.3",
"playwright": "^1.40.1",
"playwright-core": "^1.40.1",
"prettier": "^3.0.3",
"tsconfig-to-dual-package": "^1.2.0",
"typescript": "latest"
"ts-node": "^10.9.2",
"typescript": "latest",
"unplugin-vue-components": "^0.26.0",
"vitest": "^1.4.0"
},
"dependencies": {
"@ant-design-vue/nuxt": "^1.4.1",
"@pinia/nuxt": "^0.4.11",
"@tinymce/tinymce-vue": "latest",
"@sidebase/nuxt-auth": "0.7.0",
"@tinymce/tinymce-vue": "latest",
"@types/jsonwebtoken": "^9.0.3",
"@types/lodash-es": "^4.17.12",
"@types/turndown": "^5.0.4",
@ -33,7 +44,7 @@
"axios": "^1.5.1",
"bcryptjs": "^2.4.3",
"blueimp-md5": "^2.19.0",
"date-fns": "^2.30.0",
"date-fns": "latest",
"jsonwebtoken": "^9.0.2",
"lodash-es": "^4.17.21",
"lodash-move": "^1.1.1",
@ -44,13 +55,13 @@
"mongoose-sequence": "https://github.com/amansingh63/mongoose-sequence",
"nitropack": "^2.9.4",
"nuxi": "^3.10.0",
"nuxt-booster": "^3.0.0",
"nuxt-security": "^0.14.4",
"nuxt-speedkit": "3.0.0-next.26",
"pinia": "^2.1.6",
"sanitize-html": "^2.11.0",
"sharp": "^0.33.2",
"sharp": "^0.33.3",
"string-strip-html": "^13.4.3",
"tinymce": "^6.7.0",
"turndown": "^7.1.2",
"uuid": "^9.0.1",
"vee-validate": "^4.11.7",

@ -12,22 +12,24 @@
},
middleware: ["auth"],
});
useHead({
title: "Log In",
});
const formState = reactive<FormState>({
username: "",
password: "",
});
const darkRef = inject<Ref<boolean>>("dark");
const onFinish = async (values: any) => {
const { signIn, data } = useAuth();
const { signIn } = useAuth();
let reso: any;
try {
reso = await signIn(values);
await navigateTo({
path: "/",
});
await signIn(values, { redirect: true, callbackUrl: "/" });
const { data } = useAuth();
darkRef.value = data.value.user.profile.nightMode;
if (darkRef.value) document.body.dataset.theme = "dark";
await navigateTo("/");
} catch (e: any) {
if (e.data) {
notification["error"]({

@ -6,6 +6,9 @@
signOut({
callbackUrl: "/",
});
const d = inject<Ref<boolean>>("dark");
d.value = false;
document.body.dataset.theme = undefined;
</script>
<template>
<a-typography-title :level="3"> Signed out. 👋 </a-typography-title>

@ -6,12 +6,18 @@
const { data: bands } = (await useApiFetch<NonNullable<IBand[]>>("/band/all")) as unknown as { data: Ref<IBand[]> };
const { data: rd }: { data: any } = useAuth();
const {
data: { value: rd },
getSession,
} = useAuth();
await getSession({ force: true });
let inc = ref<number>(1);
const data = ref(rd);
const refresh = async () => {
await useAuth().getSession({ force: true });
rd.value = useAuth().data.value;
data.value = useAuth().data.value;
//inc.value += 1;
};
const hider = subscriptions;
if (bands.value == null) bands.value = [];
useHead({
@ -21,7 +27,7 @@
<template>
<a-list v-model:data-source="bands" :grid="bp">
<template #renderItem="{ item }">
<a-list-item>
<a-list-item :key="item._id + inc">
<a-row :gutter="[5, 5]">
<a-col>
<nuxt-link :to="`/band/${item._id}`">
@ -29,12 +35,12 @@
</nuxt-link>
</a-col>
<!-- subscribe... -->
<a-col v-if="rd && rd.user?._id" style="margin-left: auto">
<a-col v-if="data && data.user?._id" style="margin-left: auto">
<a
v-if="!rd?.user.subscriptions.bands.includes(item._id)"
v-if="!data?.user.subscriptions.bands.includes(item._id)"
@click="
async (e) => {
hider(bands, item._id, 'subscribe', 'bands');
await hider(bands, item._id, 'subscribe', 'bands');
await refresh();
}
"
@ -45,7 +51,7 @@
v-else
@click="
async (e) => {
hider(bands, item._id, 'unsubscribe', 'bands');
await hider(bands, item._id, 'unsubscribe', 'bands');
await refresh();
}
"
@ -53,11 +59,11 @@
<icon :istyle="'regular'" name="x" :size="12" />
</a>
</a-col>
<a-col v-if="rd?.user._id">
<a-col v-if="data?.user._id">
<a
@click="
async (e) => {
hider(bands, item._id, 'hide', 'bands');
await hider(bands, item._id, 'hide', 'bands');
await refresh();
}
"

@ -7,7 +7,7 @@
<template>
<div>
<a-typography-title :level="1"> The Latest Fics </a-typography-title>
<a-typography-title :level="1" style="margin-top: 0"> The Latest Fics </a-typography-title>
<story-list :last="true" prefix="/latest" />
</div>
</template>

@ -2,11 +2,12 @@ import mongoose from "mongoose";
import jwt from "jsonwebtoken";
import { User } from "@models/user";
import { log } from "@server/logger";
import { doNotSelect } from "@server/constants";
export default eventHandler(async (event) => {
const wrongMsg = "wrong credentials";
let reqbody = await readBody(event);
let user = await User.findOne({ username: reqbody.username }).exec();
let user = await User.findOne({ username: reqbody.username }).select(doNotSelect).exec();
// log.debug(reqbody, { label: "login/body" });
// log.debug("USER -> " + user, { label: "login" });
// log.debug("conn ->" + mongoose.connection, { label: "login" });
@ -30,8 +31,9 @@ export default eventHandler(async (event) => {
}
let tok = user.generateRefreshToken(useRuntimeConfig().jwt);
// setCookie(event, "rockfic_cookie", tok);
const fu = user.toObject();
return {
user,
user: fu,
token: {
refresh: tok,
access: user.generateAccessToken(useRuntimeConfig().jwt),

@ -11,6 +11,12 @@ import { messages } from "@server/constants";
export default eventHandler(async (ev) => {
let os: (Document<unknown, {}, IStory> & IStory) | null = await storyQuerier(ev);
isLoggedIn(ev);
if (!os) {
throw createError({
statusCode: 404,
message: messages[404],
});
}
if (!canModify(ev, os)) {
throw createError({
statusCode: 403,
@ -23,39 +29,31 @@ export default eventHandler(async (ev) => {
completed: body.completed,
coAuthor: !!body.coAuthor ? body.coAuthor : null,
};
for (const oc of os.chapters) {
let filename = `/stories/${oc.id}.txt`;
const bucket = getBucket();
const curs = bucket.find({ filename }).limit(1);
for await (const d of curs) {
await bucket.delete(d._id);
}
}
const cc = os.chapters;
os.chapters = [];
await os.save();
for (const c of body.chapters) {
let idx = cc.findIndex((k) => k.id === c.id);
let idx = os.chapters.findIndex((k) => k.id === c.id);
const cont = await bodyHandler(c);
if (idx === -1) {
os.chapters!.push({
...modelFormChapter(c),
words: countWords(cont),
posted: new Date(Date.now()),
});
} else {
os.chapters!.push({
os.chapters[idx] = {
...modelFormChapter(c),
// id: os.chapters[idx].id,
id: os.chapters[idx].id,
words: countWords(cont),
posted: cc[idx].posted,
});
posted: os.chapters[idx].posted,
};
}
}
os.chapters.sort((a, b) => a.index - b.index);
await os.save();
for (let i = 0; i < os.chapters.length; i++) {
const c = os.chapters[i];
const cont = await bodyHandler(body.chapters[i]);
await replaceOrUploadContent(c.id ?? c._id, cont);
await replaceOrUploadContent(c.id ?? c._id, cont, getBucket());
}
os = await Story.findOneAndUpdate(
{

@ -4,6 +4,8 @@
"allowJs": true,
"outDir": "../out",
"noImplicitAny": false,
"verbatimModuleSyntax": false
"verbatimModuleSyntax": false,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
}
}

@ -1,14 +1,13 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json",
// https://nuxt.com/docs/guide/concepts/typescript
"compilerOptions": {
"allowJs": true,
"esModuleInterop": true,
"noImplicitAny": false,
"noImplicitThis": false,
"verbatimModuleSyntax": false
// "paths": {
// "@dbconfig": ["./lib/dbconfig.ts"],
// "@functions": ["./lib/functions.ts"]
// }
}
"verbatimModuleSyntax": false,
"forceConsistentCasingInFileNames": false,
"allowSyntheticDefaultImports": true
},
}