style(*): cleanup unused imports
This commit is contained in:
		
							parent
							
								
									5baa00bbb4
								
							
						
					
					
						commit
						c8e84c909e
					
				| @ -3,6 +3,5 @@ | |||||||
| 	"useTabs": true, | 	"useTabs": true, | ||||||
| 	"trailingComma": "all", | 	"trailingComma": "all", | ||||||
| 	"arrowParens": "always", | 	"arrowParens": "always", | ||||||
| 	"vueIndentScriptAndStyle": true, | 	"vueIndentScriptAndStyle": true | ||||||
| 	"editorconfig": true |  | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										8
									
								
								app.vue
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								app.vue
									
									
									
									
									
								
							| @ -1,14 +1,11 @@ | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| 	import { theme } from "ant-design-vue"; |  | ||||||
| 
 |  | ||||||
| 	const { getSession, signIn } = useAuth(); | 	const { getSession, signIn } = useAuth(); | ||||||
| 
 | 
 | ||||||
| 	await getSession({ force: true }); | 	await getSession({ force: true }); | ||||||
| 
 | 
 | ||||||
| 	const { data } = await useAuth(); | 	const { data } = await useAuth(); | ||||||
| 
 | 
 | ||||||
| 	const dop = data?.value as any; | 	let darkBool = ref(data.value?.user?.profile?.nightMode || false); | ||||||
| 	let darkBool = ref(dop?.user?.profile?.nightMode || false); |  | ||||||
| 	// provide("user", ref(dop?.user || null)); | 	// provide("user", ref(dop?.user || null)); | ||||||
| 	provide("dark", darkBool); | 	provide("dark", darkBool); | ||||||
| 	useHead({ | 	useHead({ | ||||||
| @ -17,8 +14,7 @@ | |||||||
| 				return darkBool.value ? "dark" : undefined; | 				return darkBool.value ? "dark" : undefined; | ||||||
| 			}).value, | 			}).value, | ||||||
| 		}, | 		}, | ||||||
| 		titleTemplate: (title) => | 		titleTemplate: (title) => (title ? `Rockfic | ${title}` : "Rockfic | Band fiction that rocks"), | ||||||
| 			title ? `Rockfic | ${title}` : "Rockfic | Band fiction that rocks", |  | ||||||
| 	}); | 	}); | ||||||
| 	// provide("loaded", useNuxtApp().$loaded); | 	// provide("loaded", useNuxtApp().$loaded); | ||||||
| 	// let loaded = ref<boolean[]>([]); | 	// let loaded = ref<boolean[]>([]); | ||||||
|  | |||||||
| @ -1,9 +1,7 @@ | |||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| 	import TinymceEditor from "@tinymce/tinymce-vue"; | 	import TinymceEditor from "@tinymce/tinymce-vue"; | ||||||
| 	import { useDark } from "@vueuse/core"; |  | ||||||
| 	import { NamePath } from "ant-design-vue/es/form/interface"; |  | ||||||
| 	import { Field as VeeField } from "vee-validate"; | 	import { Field as VeeField } from "vee-validate"; | ||||||
| 	import tinymce from "tinymce"; | 
 | ||||||
| 	const props = defineProps<{ | 	const props = defineProps<{ | ||||||
| 		name: string; | 		name: string; | ||||||
| 		init: any; | 		init: any; | ||||||
| @ -16,17 +14,8 @@ | |||||||
| </script> | </script> | ||||||
| <template> | <template> | ||||||
| 	<ClientOnly> | 	<ClientOnly> | ||||||
| 		<vee-field | 		<vee-field :name="props.name" v-slot="{ errorMessage, field, value }" :model-value="props.val"> | ||||||
| 			:name="props.name" | 			<a-form-item :validate-status="!!errorMessage ? 'error' : ''" :name="props.name" :label="props.label as any" :help="errorMessage"> | ||||||
| 			v-slot="{ errorMessage, field, value }" |  | ||||||
| 			:model-value="props.val" |  | ||||||
| 		> |  | ||||||
| 			<a-form-item |  | ||||||
| 				:validate-status="!!errorMessage ? 'error' : ''" |  | ||||||
| 				:name="props.name" |  | ||||||
| 				:label="props.label as any" |  | ||||||
| 				:help="errorMessage" |  | ||||||
| 			> |  | ||||||
| 				<tinymce-editor | 				<tinymce-editor | ||||||
| 					v-bind="field" | 					v-bind="field" | ||||||
| 					width="100%" | 					width="100%" | ||||||
|  | |||||||
| @ -1,7 +1,4 @@ | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| 	import { log } from "@server/logger"; |  | ||||||
| 	import { MenuProps } from "ant-design-vue"; |  | ||||||
| 
 |  | ||||||
| 	const { data, status } = useAuth(); | 	const { data, status } = useAuth(); | ||||||
| 	const itemMap = ref({ | 	const itemMap = ref({ | ||||||
| 		home: "/", | 		home: "/", | ||||||
| @ -18,11 +15,7 @@ | |||||||
| 		admin: "/admin", | 		admin: "/admin", | ||||||
| 		logout: "/auth/logout", | 		logout: "/auth/logout", | ||||||
| 	}); | 	}); | ||||||
| 	let cur = ref<string>( | 	let cur = ref<string>(Object.keys(itemMap.value).find((a) => itemMap.value[a] === useRoute().path) || useRoute().path); | ||||||
| 		Object.keys(itemMap.value).find( |  | ||||||
| 			(a) => itemMap.value[a] === useRoute().path, |  | ||||||
| 		) || useRoute().path, |  | ||||||
| 	); |  | ||||||
| 	let selected: string[] = [cur.value]; | 	let selected: string[] = [cur.value]; | ||||||
| 
 | 
 | ||||||
| 	const clickFn = (minfo) => { | 	const clickFn = (minfo) => { | ||||||
| @ -61,18 +54,11 @@ | |||||||
| 				<a-menu-item key="reviews"> Manage Reviews </a-menu-item> | 				<a-menu-item key="reviews"> Manage Reviews </a-menu-item> | ||||||
| 				<a-menu-item key="messages"> Private Messages </a-menu-item> | 				<a-menu-item key="messages"> Private Messages </a-menu-item> | ||||||
| 			</a-sub-menu> | 			</a-sub-menu> | ||||||
| 			<a-menu-item key="admin" v-if="data?.user?.profile.isAdmin || false"> | 			<a-menu-item key="admin" v-if="data?.user?.profile.isAdmin || false"> Admin </a-menu-item> | ||||||
| 				Admin |  | ||||||
| 			</a-menu-item> |  | ||||||
| 			<a-menu-item key="logout" v-if="!!data?.user"> Logout </a-menu-item> | 			<a-menu-item key="logout" v-if="!!data?.user"> Logout </a-menu-item> | ||||||
| 		</a-menu> | 		</a-menu> | ||||||
| 		<div> | 		<div> | ||||||
| 			<a-button | 			<a-button v-if="data?.user" type="primary" tooltip="Post a New Story" @click="() => navigateTo('/new-story')"> | ||||||
| 				v-if="data?.user" |  | ||||||
| 				type="primary" |  | ||||||
| 				tooltip="Post a New Story" |  | ||||||
| 				@click="() => navigateTo('/new-story')" |  | ||||||
| 			> |  | ||||||
| 				<!-- <template #icon> | 				<!-- <template #icon> | ||||||
| 				</template> --> | 				</template> --> | ||||||
| 				<icon istyle="regular" name="file-plus" /> | 				<icon istyle="regular" name="file-plus" /> | ||||||
| @ -80,16 +66,8 @@ | |||||||
| 			</a-button> | 			</a-button> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="acbut" v-if="!data"> | 		<div class="acbut" v-if="!data"> | ||||||
| 			<a-button size="large" @click="() => navigateTo('/auth/login')"> | 			<a-button size="large" @click="() => navigateTo('/auth/login')"> Login </a-button> | ||||||
| 				Login | 			<a-button size="large" type="primary" @click="() => navigateTo('/auth/register')"> Register </a-button> | ||||||
| 			</a-button> |  | ||||||
| 			<a-button |  | ||||||
| 				size="large" |  | ||||||
| 				type="primary" |  | ||||||
| 				@click="() => navigateTo('/auth/register')" |  | ||||||
| 			> |  | ||||||
| 				Register |  | ||||||
| 			</a-button> |  | ||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
| </template> | </template> | ||||||
|  | |||||||
| @ -1,12 +1,9 @@ | |||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| 	import type { | 	import type { MenuItemType, SubMenuType } from "ant-design-vue/es/menu/src/interface"; | ||||||
| 		MenuItemType, |  | ||||||
| 		SubMenuType, |  | ||||||
| 	} from "ant-design-vue/es/menu/src/interface"; |  | ||||||
| 	import { ItemType, theme } from "ant-design-vue"; | 	import { ItemType, theme } from "ant-design-vue"; | ||||||
| 	import Icon from "../icon.vue"; | 	import Icon from "../icon.vue"; | ||||||
| 	import { ISidebarItem } from "@models/sidebarEntry"; | 	import { ISidebarItem } from "@models/sidebarEntry"; | ||||||
| 	import { AButton, NuxtLink } from "#components"; | 	import { NuxtLink } from "#components"; | ||||||
| 
 | 
 | ||||||
| 	const loaded = inject<Ref<boolean>>("loaded"); | 	const loaded = inject<Ref<boolean>>("loaded"); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| 	import type { IStory } from "@models/stories"; | 	import type { IStory } from "@models/stories"; | ||||||
| 	import { log } from "~/lib/server/logger"; |  | ||||||
| 	import { format } from "date-fns"; | 	import { format } from "date-fns"; | ||||||
| 	import icon from "../icon.vue"; | 	import icon from "../icon.vue"; | ||||||
| 	import { theme } from "ant-design-vue"; | 	import { theme } from "ant-design-vue"; | ||||||
| @ -14,18 +13,8 @@ | |||||||
| 	const idxo = (prop.last || false ? prop.story.chapters.length : 1) - 1; | 	const idxo = (prop.last || false ? prop.story.chapters.length : 1) - 1; | ||||||
| 	// console.log("idx0->", idxo) | 	// console.log("idx0->", idxo) | ||||||
| 	// log.debug("posti->", prop.story.chapters[ prop.story.chapters.length - 1 ]); | 	// log.debug("posti->", prop.story.chapters[ prop.story.chapters.length - 1 ]); | ||||||
| 	const shortDate = format( | 	const shortDate = format(Date.parse(prop.story.chapters[prop.story.chapters.length - 1]?.posted!.toString()), "yyyy/MM/dd"); | ||||||
| 		Date.parse( | 	const longDate = format(Date.parse(prop.story.chapters[prop.story.chapters.length - 1]?.posted!.toString()), "iiii',' yyyy-MM-dd"); | ||||||
| 			prop.story.chapters[prop.story.chapters.length - 1]?.posted!.toString(), |  | ||||||
| 		), |  | ||||||
| 		"yyyy/MM/dd", |  | ||||||
| 	); |  | ||||||
| 	const longDate = format( |  | ||||||
| 		Date.parse( |  | ||||||
| 			prop.story.chapters[prop.story.chapters.length - 1]?.posted!.toString(), |  | ||||||
| 		), |  | ||||||
| 		"iiii',' yyyy-MM-dd", |  | ||||||
| 	); |  | ||||||
| </script> | </script> | ||||||
| <template> | <template> | ||||||
| 	<a-card> | 	<a-card> | ||||||
| @ -36,26 +25,16 @@ | |||||||
| 						{{ story.title }} | 						{{ story.title }} | ||||||
| 					</NuxtLink> | 					</NuxtLink> | ||||||
| 					<a-tooltip placement="topLeft"> | 					<a-tooltip placement="topLeft"> | ||||||
| 						<template #title> | 						<template #title> You'll need to log in to read this story. Register if you don't already have an account -- it's free! </template> | ||||||
| 							You'll need to log in to read this story. Register if you don't |  | ||||||
| 							already have an account -- it's free! |  | ||||||
| 						</template> |  | ||||||
| 						<icon v-if="!data?.user" istyle="solid" name="lock" /> | 						<icon v-if="!data?.user" istyle="solid" name="lock" /> | ||||||
| 					</a-tooltip> | 					</a-tooltip> | ||||||
| 				</div> | 				</div> | ||||||
| 
 | 
 | ||||||
| 				<div | 				<div style="display: flex; font-size: 0.9em; align-items: baseline" class="headerthing"> | ||||||
| 					style="display: flex; font-size: 0.9em; align-items: baseline" |  | ||||||
| 					class="headerthing" |  | ||||||
| 				> |  | ||||||
| 					<span> a </span> | 					<span> a </span> | ||||||
| 					<div | 					<div style="" v-for="(band, idx) in story.chapters[idxo].bands.filter((a) => !!a)"> | ||||||
| 						style="" |  | ||||||
| 						v-for="(band, idx) in story.chapters[idxo].bands.filter((a) => !!a)" |  | ||||||
| 					> |  | ||||||
| 						<span> | 						<span> | ||||||
| 							<NuxtLink :to="`/band/${band._id}`"> {{ band.name }} </NuxtLink | 							<NuxtLink :to="`/band/${band._id}`"> {{ band.name }} </NuxtLink>{{ idx < story.chapters[idxo].bands.length - 1 ? ", " : "" }} | ||||||
| 							>{{ idx < story.chapters[idxo].bands.length - 1 ? ", " : "" }} |  | ||||||
| 						</span> | 						</span> | ||||||
| 					</div> | 					</div> | ||||||
| 					<span> fic by </span> | 					<span> fic by </span> | ||||||
| @ -100,9 +79,7 @@ | |||||||
| 		<div class="storyMeta"> | 		<div class="storyMeta"> | ||||||
| 			<div class="inner"> | 			<div class="inner"> | ||||||
| 				<span v-if="story.chapters.length > 1"> | 				<span v-if="story.chapters.length > 1"> | ||||||
| 					<NuxtLink :to="`/story/${story._id}/chapters`"> | 					<NuxtLink :to="`/story/${story._id}/chapters`"> {{ story.chapters.length }} chapters </NuxtLink> | ||||||
| 						{{ story.chapters.length }} chapters |  | ||||||
| 					</NuxtLink> |  | ||||||
| 				</span> | 				</span> | ||||||
| 				<span v-else> {{ story.chapters.length }} chapter </span> | 				<span v-else> {{ story.chapters.length }} chapter </span> | ||||||
| 				<span> | 				<span> | ||||||
| @ -121,15 +98,10 @@ | |||||||
| 					{{ story.chapters[idxo].characters.join(", ") }} | 					{{ story.chapters[idxo].characters.join(", ") }} | ||||||
| 				</meta-item> | 				</meta-item> | ||||||
| 				<meta-item label="Relationship(s)"> | 				<meta-item label="Relationship(s)"> | ||||||
| 					<div | 					<div style="display: inline-block" v-for="(rel, idx) in story.chapters[idxo].relationships"> | ||||||
| 						style="display: inline-block" |  | ||||||
| 						v-for="(rel, idx) in story.chapters[idxo].relationships" |  | ||||||
| 					> |  | ||||||
| 						<span> | 						<span> | ||||||
| 							{{ Array.isArray(rel) ? rel.join("/") : rel }} | 							{{ Array.isArray(rel) ? rel.join("/") : rel }} | ||||||
| 							{{ | 							{{ idx < story.chapters[idxo].relationships.length - 1 ? "," : "" }} | ||||||
| 								idx < story.chapters[idxo].relationships.length - 1 ? "," : "" |  | ||||||
| 							}} |  | ||||||
| 						</span> | 						</span> | ||||||
| 					</div> | 					</div> | ||||||
| 					<span v-if="story.chapters[idxo].relationships.length < 1"> | 					<span v-if="story.chapters[idxo].relationships.length < 1"> | ||||||
| @ -149,12 +121,7 @@ | |||||||
| 		<div class="stats"> | 		<div class="stats"> | ||||||
| 			<span> | 			<span> | ||||||
| 				<span class="staticon"> | 				<span class="staticon"> | ||||||
| 					<icon | 					<icon :istyle="!dark ? 'solid' : 'regular'" icolor="#ff2883" :size="12" name="heart" /> | ||||||
| 						:istyle="!dark ? 'solid' : 'regular'" |  | ||||||
| 						icolor="#ff2883" |  | ||||||
| 						:size="12" |  | ||||||
| 						name="heart" |  | ||||||
| 					/> |  | ||||||
| 				</span> | 				</span> | ||||||
| 				<span> | 				<span> | ||||||
| 					{{ story.favs }} | 					{{ story.favs }} | ||||||
| @ -162,12 +129,7 @@ | |||||||
| 			</span> | 			</span> | ||||||
| 			<span> | 			<span> | ||||||
| 				<span class="staticon"> | 				<span class="staticon"> | ||||||
| 					<icon | 					<icon :istyle="!dark ? 'solid' : 'regular'" icolor="#1787d7" :size="12" name="book-open" /> | ||||||
| 						:istyle="!dark ? 'solid' : 'regular'" |  | ||||||
| 						icolor="#1787d7" |  | ||||||
| 						:size="12" |  | ||||||
| 						name="book-open" |  | ||||||
| 					/> |  | ||||||
| 				</span> | 				</span> | ||||||
| 				<span> | 				<span> | ||||||
| 					{{ story.views }} | 					{{ story.views }} | ||||||
| @ -175,12 +137,7 @@ | |||||||
| 			</span> | 			</span> | ||||||
| 			<span> | 			<span> | ||||||
| 				<span class="staticon"> | 				<span class="staticon"> | ||||||
| 					<icon | 					<icon :istyle="!dark ? 'solid' : 'regular'" icolor="#51e07c" :size="12" name="thumbs-up" /> | ||||||
| 						:istyle="!dark ? 'solid' : 'regular'" |  | ||||||
| 						icolor="#51e07c" |  | ||||||
| 						:size="12" |  | ||||||
| 						name="thumbs-up" |  | ||||||
| 					/> |  | ||||||
| 				</span> | 				</span> | ||||||
| 				<span> | 				<span> | ||||||
| 					{{ story.recs }} | 					{{ story.recs }} | ||||||
| @ -188,12 +145,7 @@ | |||||||
| 			</span> | 			</span> | ||||||
| 			<span> | 			<span> | ||||||
| 				<span class="staticon"> | 				<span class="staticon"> | ||||||
| 					<icon | 					<icon :istyle="!dark ? 'solid' : 'regular'" icolor="#c2d420" :size="12" name="download" /> | ||||||
| 						:istyle="!dark ? 'solid' : 'regular'" |  | ||||||
| 						icolor="#c2d420" |  | ||||||
| 						:size="12" |  | ||||||
| 						name="download" |  | ||||||
| 					/> |  | ||||||
| 				</span> | 				</span> | ||||||
| 				<span> | 				<span> | ||||||
| 					{{ story.downloads }} | 					{{ story.downloads }} | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| 	import { Form as veeForm, Field as veeField, useForm } from "vee-validate"; | 	import { Form as veeForm, Field as veeField } from "vee-validate"; | ||||||
| 	import { IReview } from "@models/stories/review"; | 	import { IReview } from "@models/stories/review"; | ||||||
| 	import { comment } from "@client/editorConfig"; | 	import { comment } from "@client/editorConfig"; | ||||||
| 	import { SingleChapterResult } from "@client/types/slightlyDifferentStory"; | 	import { SingleChapterResult } from "@client/types/slightlyDifferentStory"; | ||||||
| @ -25,13 +25,10 @@ | |||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	const replySubmit = async (values) => { | 	const replySubmit = async (values) => { | ||||||
| 		const { data } = await useApiFetch<any>( | 		const { data } = await useApiFetch<any>(`/review/${props.review._id}/reply`, { | ||||||
| 			`/review/${props.review._id}/reply`, |  | ||||||
| 			{ |  | ||||||
| 			method: "post", | 			method: "post", | ||||||
| 			body: values, | 			body: values, | ||||||
| 			}, | 		}); | ||||||
| 		); |  | ||||||
| 		review.value.replies.push(data.value.data); | 		review.value.replies.push(data.value.data); | ||||||
| 		replyFormVisible.value = false; | 		replyFormVisible.value = false; | ||||||
| 	}; | 	}; | ||||||
| @ -49,12 +46,7 @@ | |||||||
| 	<a-comment> | 	<a-comment> | ||||||
| 		<template #actions> | 		<template #actions> | ||||||
| 			<div v-if="!!data?.user" class="review-actions"> | 			<div v-if="!!data?.user" class="review-actions"> | ||||||
| 				<a-button | 				<a-button v-if="isCommentAuthor" @click="() => (isEditing = !isEditing)"> Edit </a-button> | ||||||
| 					v-if="isCommentAuthor" |  | ||||||
| 					@click="() => (isEditing = !isEditing)" |  | ||||||
| 				> |  | ||||||
| 					Edit |  | ||||||
| 				</a-button> |  | ||||||
| 				<a-popconfirm | 				<a-popconfirm | ||||||
| 					title="Are you sure you want to permanently delete this review?" | 					title="Are you sure you want to permanently delete this review?" | ||||||
| 					ok-text="Yes" | 					ok-text="Yes" | ||||||
| @ -64,13 +56,7 @@ | |||||||
| 				> | 				> | ||||||
| 					<a-button> Delete </a-button> | 					<a-button> Delete </a-button> | ||||||
| 				</a-popconfirm> | 				</a-popconfirm> | ||||||
| 				<a-button | 				<a-button v-if="isAuthor" type="primary" @click="() => (replyFormVisible = !replyFormVisible)"> Reply </a-button> | ||||||
| 					v-if="isAuthor" |  | ||||||
| 					type="primary" |  | ||||||
| 					@click="() => (replyFormVisible = !replyFormVisible)" |  | ||||||
| 				> |  | ||||||
| 					Reply |  | ||||||
| 				</a-button> |  | ||||||
| 			</div> | 			</div> | ||||||
| 		</template> | 		</template> | ||||||
| 		<template #author> | 		<template #author> | ||||||
| @ -91,25 +77,14 @@ | |||||||
| 			<div> | 			<div> | ||||||
| 				<vee-form @submit="editSubmit" v-if="isEditing"> | 				<vee-form @submit="editSubmit" v-if="isEditing"> | ||||||
| 					<vee-field name="content" v-slot="{ value, field, errorMessage }"> | 					<vee-field name="content" v-slot="{ value, field, errorMessage }"> | ||||||
| 						<base-editor | 						<base-editor :val="review.text" :width="150" label="" :name="field.name" :init="comment" /> | ||||||
| 							:val="review.text" |  | ||||||
| 							:width="150" |  | ||||||
| 							label="" |  | ||||||
| 							:name="field.name" |  | ||||||
| 							:init="comment" |  | ||||||
| 						/> |  | ||||||
| 					</vee-field> | 					</vee-field> | ||||||
| 					<a-button type="primary" html-type="submit"> Edit review </a-button> | 					<a-button type="primary" html-type="submit"> Edit review </a-button> | ||||||
| 				</vee-form> | 				</vee-form> | ||||||
| 				<div v-else v-html="review.text" /> | 				<div v-else v-html="review.text" /> | ||||||
| 				<vee-form @submit="replySubmit" v-if="replyFormVisible"> | 				<vee-form @submit="replySubmit" v-if="replyFormVisible"> | ||||||
| 					<vee-field name="content" v-slot="{ value, field, errorMessage }"> | 					<vee-field name="content" v-slot="{ value, field, errorMessage }"> | ||||||
| 						<base-editor | 						<base-editor :width="150" label="" :name="field.name" :init="comment" /> | ||||||
| 							:width="150" |  | ||||||
| 							label="" |  | ||||||
| 							:name="field.name" |  | ||||||
| 							:init="comment" |  | ||||||
| 						/> |  | ||||||
| 					</vee-field> | 					</vee-field> | ||||||
| 					<a-button type="primary" html-type="submit"> Post response </a-button> | 					<a-button type="primary" html-type="submit"> Post response </a-button> | ||||||
| 				</vee-form> | 				</vee-form> | ||||||
|  | |||||||
| @ -3,9 +3,6 @@ | |||||||
| 	import { RuleExpression, useField } from "vee-validate"; | 	import { RuleExpression, useField } from "vee-validate"; | ||||||
| 	import { cs } from "@client/storyFormSchema"; | 	import { cs } from "@client/storyFormSchema"; | ||||||
| 	import { IBand } from "@models/band"; | 	import { IBand } from "@models/band"; | ||||||
| 	import { log } from "@server/logger"; |  | ||||||
| 
 |  | ||||||
| 	import iconEl from "../icon.vue"; |  | ||||||
| 
 | 
 | ||||||
| 	const bandlist = inject<IBand[]>("bandlist"); | 	const bandlist = inject<IBand[]>("bandlist"); | ||||||
| 	const fname = inject<string>("curName"); | 	const fname = inject<string>("curName"); | ||||||
| @ -19,20 +16,13 @@ | |||||||
| 			label: a.name, | 			label: a.name, | ||||||
| 			disabled: a.locked || false, | 			disabled: a.locked || false, | ||||||
| 		})); | 		})); | ||||||
| 	let bandField = useField( | 	let bandField = useField(fname + "bands", cs.fields.bands as unknown as MaybeRef<RuleExpression<number[]>>); | ||||||
| 		fname + "bands", |  | ||||||
| 		cs.fields.bands as unknown as MaybeRef<RuleExpression<number[]>>, |  | ||||||
| 	); |  | ||||||
| 	const { value, errorMessage, name, setValue } = bandField; | 	const { value, errorMessage, name, setValue } = bandField; | ||||||
| 	// setValue(sb) | 	// setValue(sb) | ||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
| 	<a-form-item | 	<a-form-item :validate-status="!!errorMessage ? 'error' : undefined" :help="errorMessage as any" label="Bands"> | ||||||
| 		:validate-status="!!errorMessage ? 'error' : undefined" |  | ||||||
| 		:help="errorMessage" |  | ||||||
| 		label="Bands" |  | ||||||
| 	> |  | ||||||
| 		<a-select | 		<a-select | ||||||
| 			:allow-clear="true" | 			:allow-clear="true" | ||||||
| 			mode="multiple" | 			mode="multiple" | ||||||
|  | |||||||
| @ -1,15 +1,9 @@ | |||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| 	import { | 	import { Field, ErrorMessage } from "vee-validate"; | ||||||
| 		Form as VeeForm, |  | ||||||
| 		Field, |  | ||||||
| 		useForm, |  | ||||||
| 		useField, |  | ||||||
| 		ErrorMessage, |  | ||||||
| 	} from "vee-validate"; |  | ||||||
| 	import { NamePath } from "ant-design-vue/es/form/interface"; | 	import { NamePath } from "ant-design-vue/es/form/interface"; | ||||||
| 	import { FormChapter } from "@client/types/form/story"; | 	import { FormChapter } from "@client/types/form/story"; | ||||||
| 
 | 
 | ||||||
| 	import { story, bare } from "@client/editorConfig"; | 	import { bare } from "@client/editorConfig"; | ||||||
| 	import elBands from "../atoms/bands.vue"; | 	import elBands from "../atoms/bands.vue"; | ||||||
| 	import genre from "../atoms/genre.vue"; | 	import genre from "../atoms/genre.vue"; | ||||||
| 	import elCharacters from "../atoms/characters.vue"; | 	import elCharacters from "../atoms/characters.vue"; | ||||||
| @ -41,16 +35,8 @@ | |||||||
| 	<div> | 	<div> | ||||||
| 		<a-row :gutter="[10, 0]"> | 		<a-row :gutter="[10, 0]"> | ||||||
| 			<a-col :span="12"> | 			<a-col :span="12"> | ||||||
| 				<Field | 				<Field :name="name + '.chapterTitle'" v-slot="{ value, field, errorMessage }"> | ||||||
| 					:name="name + '.chapterTitle'" | 					<a-form-item :name="[field.name as string]" label="Chapter title" :help="errorMessage" :status="!!errorMessage ? 'error' : undefined"> | ||||||
| 					v-slot="{ value, field, errorMessage }" |  | ||||||
| 				> |  | ||||||
| 					<a-form-item |  | ||||||
| 						:name="[field.name]" |  | ||||||
| 						label="Chapter title" |  | ||||||
| 						:help="errorMessage" |  | ||||||
| 						:status="!!errorMessage ? 'error' : undefined" |  | ||||||
| 					> |  | ||||||
| 						<a-input v-bind="field" /> | 						<a-input v-bind="field" /> | ||||||
| 					</a-form-item> | 					</a-form-item> | ||||||
| 				</Field> | 				</Field> | ||||||
| @ -61,22 +47,10 @@ | |||||||
| 		</a-row> | 		</a-row> | ||||||
| 		<a-row :gutter="[10, 0]"> | 		<a-row :gutter="[10, 0]"> | ||||||
| 			<a-col :span="12"> | 			<a-col :span="12"> | ||||||
| 				<base-editor | 				<base-editor v-model:val="acData.summary" :name="name + '.summary'" :wrap-col="wrapc" label="Summary" :init="bare" /> | ||||||
| 					v-model:val="acData.summary" |  | ||||||
| 					:name="name + '.summary'" |  | ||||||
| 					:wrap-col="wrapc" |  | ||||||
| 					label="Summary" |  | ||||||
| 					:init="bare" |  | ||||||
| 				/> |  | ||||||
| 			</a-col> | 			</a-col> | ||||||
| 			<a-col :span="12"> | 			<a-col :span="12"> | ||||||
| 				<base-editor | 				<base-editor v-model:val="acData.notes" :name="name + '.notes'" :wrap-col="wrapc" label="Author's notes" :init="bare" /> | ||||||
| 					v-model:val="acData.notes" |  | ||||||
| 					:name="name + '.notes'" |  | ||||||
| 					:wrap-col="wrapc" |  | ||||||
| 					label="Author's notes" |  | ||||||
| 					:init="bare" |  | ||||||
| 				/> |  | ||||||
| 			</a-col> | 			</a-col> | ||||||
| 		</a-row> | 		</a-row> | ||||||
| 		<a-row :gutter="[10, 0]"> | 		<a-row :gutter="[10, 0]"> | ||||||
| @ -87,39 +61,17 @@ | |||||||
| 				<el-pairings /> | 				<el-pairings /> | ||||||
| 			</a-col> | 			</a-col> | ||||||
| 		</a-row> | 		</a-row> | ||||||
| 		<Field | 		<Field :name="name + '.nsfw'" type="checkbox" :unchecked-value="false" :value="true" v-slot="{ value, field, errorMessage }"> | ||||||
| 			:name="name + '.nsfw'" | 			<a-checkbox v-bind="field" v-model="field.value"> Has NSFW content </a-checkbox> | ||||||
| 			type="checkbox" |  | ||||||
| 			:unchecked-value="false" |  | ||||||
| 			:value="true" |  | ||||||
| 			v-slot="{ value, field, errorMessage }" |  | ||||||
| 		> |  | ||||||
| 			<a-checkbox v-bind="field" v-model="field.value"> |  | ||||||
| 				Has NSFW content |  | ||||||
| 			</a-checkbox> |  | ||||||
| 			<error-message :name="field.name" /> | 			<error-message :name="field.name" /> | ||||||
| 		</Field> | 		</Field> | ||||||
| 		<Field | 		<Field :name="name + '.loggedInOnly'" type="checkbox" :unchecked-value="false" :value="true" v-slot="{ value, field, errorMessage }"> | ||||||
| 			:name="name + '.loggedInOnly'" |  | ||||||
| 			type="checkbox" |  | ||||||
| 			:unchecked-value="false" |  | ||||||
| 			:value="true" |  | ||||||
| 			v-slot="{ value, field, errorMessage }" |  | ||||||
| 		> |  | ||||||
| 			<a-checkbox v-bind="field"> Visible only to registered users </a-checkbox> | 			<a-checkbox v-bind="field"> Visible only to registered users </a-checkbox> | ||||||
| 			<error-message :name="field.name" /> | 			<error-message :name="field.name" /> | ||||||
| 		</Field> | 		</Field> | ||||||
| 		<Field | 		<Field :name="name + '.hidden'" type="checkbox" :unchecked-value="false" :value="true" v-slot="{ value, field, errorMessage }"> | ||||||
| 			:name="name + '.hidden'" |  | ||||||
| 			type="checkbox" |  | ||||||
| 			:unchecked-value="false" |  | ||||||
| 			:value="true" |  | ||||||
| 			v-slot="{ value, field, errorMessage }" |  | ||||||
| 		> |  | ||||||
| 			<a-tooltip> | 			<a-tooltip> | ||||||
| 				<template #title> | 				<template #title> Hides your story from everyone except you and site admins. </template> | ||||||
| 					Hides your story from everyone except you and site admins. |  | ||||||
| 				</template> |  | ||||||
| 				<a-checkbox v-bind="field"> Hidden </a-checkbox> | 				<a-checkbox v-bind="field"> Hidden </a-checkbox> | ||||||
| 			</a-tooltip> | 			</a-tooltip> | ||||||
| 		</Field> | 		</Field> | ||||||
|  | |||||||
| @ -2,25 +2,10 @@ | |||||||
| 	import draggable from "vuedraggable"; | 	import draggable from "vuedraggable"; | ||||||
| 	import { v4 } from "uuid"; | 	import { v4 } from "uuid"; | ||||||
| 	import lmove from "lodash-move"; | 	import lmove from "lodash-move"; | ||||||
| 	import { | 	import { Field, FieldArray, useForm } from "vee-validate"; | ||||||
| 		Field, |  | ||||||
| 		Form as veeForm, |  | ||||||
| 		FieldArray, |  | ||||||
| 		FieldEntry, |  | ||||||
| 		useForm, |  | ||||||
| 	} from "vee-validate"; |  | ||||||
| 	import { storySchema } from "@client/storyFormSchema"; | 	import { storySchema } from "@client/storyFormSchema"; | ||||||
| 	import { | 	import { FormStory, defaultChapter } from "@client/types/form/story"; | ||||||
| 		FormChapter, | 	import { autoEdit, autoSave, debouncedAutoEdit, debouncedAutoSave } from "@client/utils"; | ||||||
| 		FormStory, |  | ||||||
| 		defaultChapter, |  | ||||||
| 	} from "@client/types/form/story"; |  | ||||||
| 	import { |  | ||||||
| 		autoEdit, |  | ||||||
| 		autoSave, |  | ||||||
| 		debouncedAutoEdit, |  | ||||||
| 		debouncedAutoSave, |  | ||||||
| 	} from "@client/utils"; |  | ||||||
| 
 | 
 | ||||||
| 	import findUser from "~/components/findUser.vue"; | 	import findUser from "~/components/findUser.vue"; | ||||||
| 
 | 
 | ||||||
| @ -79,35 +64,17 @@ | |||||||
| 		@submit="onSubmit" | 		@submit="onSubmit" | ||||||
| 		@invalid-submit="inval" | 		@invalid-submit="inval" | ||||||
| 	> --> | 	> --> | ||||||
| 	<form | 	<form @submit="subCb" @change="() => (canDraft ? debouncedAutoSave(values) : debouncedAutoEdit(values, endpoint, endpointMethod))"> | ||||||
| 		@submit="subCb" |  | ||||||
| 		@change=" |  | ||||||
| 			() => |  | ||||||
| 				canDraft |  | ||||||
| 					? debouncedAutoSave(values) |  | ||||||
| 					: debouncedAutoEdit(values, endpoint, endpointMethod) |  | ||||||
| 		" |  | ||||||
| 	> |  | ||||||
| 		<!-- <a-form v-bind:model="acData"> --> | 		<!-- <a-form v-bind:model="acData"> --> | ||||||
| 		<Field name="title" v-slot="{ value, field, errorMessage }"> | 		<Field name="title" v-slot="{ value, field, errorMessage }"> | ||||||
| 			<a-form-item | 			<a-form-item label="Title" :help="errorMessage" :validate-status="!!errorMessage ? 'error' : ''"> | ||||||
| 				label="Title" |  | ||||||
| 				:help="errorMessage" |  | ||||||
| 				:validate-status="!!errorMessage ? 'error' : ''" |  | ||||||
| 			> |  | ||||||
| 				<a-input v-bind="field" :value="value" /> | 				<a-input v-bind="field" :value="value" /> | ||||||
| 			</a-form-item> | 			</a-form-item> | ||||||
| 		</Field> | 		</Field> | ||||||
| 		<a-form-item label="Co-author (optional)"> | 		<a-form-item label="Co-author (optional)"> | ||||||
| 			<find-user :initial-option="null" fieldName="coAuthor" :multi="false" /> | 			<find-user :initial-option="null" fieldName="coAuthor" :multi="false" /> | ||||||
| 		</a-form-item> | 		</a-form-item> | ||||||
| 		<Field | 		<Field :unchecked-value="false" :value="true" type="checkbox" name="completed" v-slot="{ value, field, errorMessage }"> | ||||||
| 			:unchecked-value="false" |  | ||||||
| 			:value="true" |  | ||||||
| 			type="checkbox" |  | ||||||
| 			name="completed" |  | ||||||
| 			v-slot="{ value, field, errorMessage }" |  | ||||||
| 		> |  | ||||||
| 			<a-checkbox v-bind="field"> Complete </a-checkbox> | 			<a-checkbox v-bind="field"> Complete </a-checkbox> | ||||||
| 		</Field> | 		</Field> | ||||||
| 		<a-divider /> | 		<a-divider /> | ||||||
| @ -125,11 +92,7 @@ | |||||||
| 						if (e.moved) { | 						if (e.moved) { | ||||||
| 							// log.debug(e.moved); | 							// log.debug(e.moved); | ||||||
| 							move(e.moved.oldIndex, e.moved.newIndex); | 							move(e.moved.oldIndex, e.moved.newIndex); | ||||||
| 							acData.chapters = lmove( | 							acData.chapters = lmove(acData.chapters, e.moved.oldIndex, e.moved.newIndex); | ||||||
| 								acData.chapters, |  | ||||||
| 								e.moved.oldIndex, |  | ||||||
| 								e.moved.newIndex, |  | ||||||
| 							); |  | ||||||
| 							// log.debug(toRaw(acData.chapters.map((a) => toRaw(a)))); | 							// log.debug(toRaw(acData.chapters.map((a) => toRaw(a)))); | ||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| @ -194,16 +157,8 @@ | |||||||
| 				</template> | 				</template> | ||||||
| 			</draggable> | 			</draggable> | ||||||
| 		</field-array> | 		</field-array> | ||||||
| 		<a-button type="primary" html-type="submit">{{ | 		<a-button type="primary" html-type="submit">{{ submitText || "Post" }}</a-button> | ||||||
| 			submitText || "Post" | 		<a-button html-type="submit" v-if="canDraft" @click="() => (otherBtnInvoked = true)"> Save for Later </a-button> | ||||||
| 		}}</a-button> |  | ||||||
| 		<a-button |  | ||||||
| 			html-type="submit" |  | ||||||
| 			v-if="canDraft" |  | ||||||
| 			@click="() => (otherBtnInvoked = true)" |  | ||||||
| 		> |  | ||||||
| 			Save for Later |  | ||||||
| 		</a-button> |  | ||||||
| 	</form> | 	</form> | ||||||
| 	<!-- </vee-form> --> | 	<!-- </vee-form> --> | ||||||
| </template> | </template> | ||||||
|  | |||||||
| @ -1,11 +1,5 @@ | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| 	import { | 	import { useField } from "vee-validate"; | ||||||
| 		Form as VeeForm, |  | ||||||
| 		Field as veeField, |  | ||||||
| 		useForm, |  | ||||||
| 		useField, |  | ||||||
| 		ErrorMessage, |  | ||||||
| 	} from "vee-validate"; |  | ||||||
| 	import { story } from "@client/editorConfig"; | 	import { story } from "@client/editorConfig"; | ||||||
| 	import icon from "~/components/icon.vue"; | 	import icon from "~/components/icon.vue"; | ||||||
| 	import baseEditor from "../../baseEditor.vue"; | 	import baseEditor from "../../baseEditor.vue"; | ||||||
| @ -22,12 +16,7 @@ | |||||||
| 	</a-radio-group> | 	</a-radio-group> | ||||||
| 	<br /> | 	<br /> | ||||||
| 	<br /> | 	<br /> | ||||||
| 	<base-editor | 	<base-editor label="" v-if="pvalue === 'pasteOrType'" :init="story" :name="fname + 'content'" /> | ||||||
| 		label="" |  | ||||||
| 		v-if="pvalue === 'pasteOrType'" |  | ||||||
| 		:init="story" |  | ||||||
| 		:name="fname + 'content'" |  | ||||||
| 	/> |  | ||||||
| 	<a-upload | 	<a-upload | ||||||
| 		v-model:file-list="fileList" | 		v-model:file-list="fileList" | ||||||
| 		v-else-if="pvalue === 'upload'" | 		v-else-if="pvalue === 'upload'" | ||||||
| @ -53,10 +42,6 @@ | |||||||
| 			} | 			} | ||||||
| 		" | 		" | ||||||
| 	> | 	> | ||||||
| 		<a-button type="primary"> | 		<a-button type="primary"> <icon istyle="regular" name="upload" /><span style="margin-left: 0.5em"> Upload a file</span> </a-button> | ||||||
| 			<icon istyle="regular" name="upload" /><span style="margin-left: 0.5em"> |  | ||||||
| 				Upload a file</span |  | ||||||
| 			> |  | ||||||
| 		</a-button> |  | ||||||
| 	</a-upload> | 	</a-upload> | ||||||
| </template> | </template> | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| import { AsyncData, UseFetchOptions } from "#app"; | import { AsyncData } from "#app"; | ||||||
| import { NitroFetchRequest } from "nitropack"; |  | ||||||
| 
 | 
 | ||||||
| const useApiFetch = async <T>(url: string, options?: any) => { | const useApiFetch = async <T>(url: string, options?: any) => { | ||||||
| 	// const { token } = useAuth();
 | 	// const { token } = useAuth();
 | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ | |||||||
| 	import cfooter from "~/components/layouts/footer.vue"; | 	import cfooter from "~/components/layouts/footer.vue"; | ||||||
| 	import sidebarThing from "~/components/layouts/sidebar.vue"; | 	import sidebarThing from "~/components/layouts/sidebar.vue"; | ||||||
| 	import icon from "~/components/icon.vue"; | 	import icon from "~/components/icon.vue"; | ||||||
| 	import { ISidebarItem } from "@models/sidebarEntry"; | 
 | ||||||
| 	const { useToken } = theme; | 	const { useToken } = theme; | ||||||
| 	const { token } = useToken(); | 	const { token } = useToken(); | ||||||
| 
 | 
 | ||||||
| @ -52,10 +52,7 @@ | |||||||
| 						<div class="stat-block"> | 						<div class="stat-block"> | ||||||
| 							<div> | 							<div> | ||||||
| 								<a-typography-text> Band fiction that rocks </a-typography-text> | 								<a-typography-text> Band fiction that rocks </a-typography-text> | ||||||
| 								<a-typography-text type="secondary"> | 								<a-typography-text type="secondary"> With {{ totals?.stories ?? 0 }} stories by {{ totals?.authors ?? 0 }} authors </a-typography-text> | ||||||
| 									With {{ totals?.stories ?? 0 }} stories by |  | ||||||
| 									{{ totals?.authors ?? 0 }} authors |  | ||||||
| 								</a-typography-text> |  | ||||||
| 							</div> | 							</div> | ||||||
| 						</div> | 						</div> | ||||||
| 						<navbar /> | 						<navbar /> | ||||||
|  | |||||||
| @ -1,39 +1,20 @@ | |||||||
| import * as yup from "yup"; | import * as yup from "yup"; | ||||||
| import { FormChapter, FormStory } from "./types/form/story"; | import { FormChapter } from "./types/form/story"; | ||||||
| 
 | 
 | ||||||
| const emptySummary = "Summary cannot be blank"; | const emptySummary = "Summary cannot be blank"; | ||||||
| const emptyChapterTitle = "Chapter title cannot be blank."; | const emptyChapterTitle = "Chapter title cannot be blank."; | ||||||
| const blankTitle = "Title cannot be blank."; | const blankTitle = "Title cannot be blank."; | ||||||
| 
 | 
 | ||||||
| export const cs = yup.object<FormChapter>().shape({ | export const cs = yup.object<FormChapter>().shape({ | ||||||
| 	chapterTitle: yup | 	chapterTitle: yup.string().ensure().min(1, emptyChapterTitle).trim(emptyChapterTitle).required(emptyChapterTitle), | ||||||
| 		.string() |  | ||||||
| 		.ensure() |  | ||||||
| 		.min(1, emptyChapterTitle) |  | ||||||
| 		.trim(emptyChapterTitle) |  | ||||||
| 		.required(emptyChapterTitle), |  | ||||||
| 	summary: yup.string().ensure().min(10, emptySummary).required(emptySummary), | 	summary: yup.string().ensure().min(10, emptySummary).required(emptySummary), | ||||||
| 	notes: yup.string().ensure(), | 	notes: yup.string().ensure(), | ||||||
| 	bands: yup | 	bands: yup.array().ensure().of(yup.number()).min(1, "One or more bands must be selected."), | ||||||
| 		.array() | 	characters: yup.array().ensure().min(1, "One or more characters must be selected"), | ||||||
| 		.ensure() |  | ||||||
| 		.of(yup.number()) |  | ||||||
| 		.min(1, "One or more bands must be selected."), |  | ||||||
| 	characters: yup |  | ||||||
| 		.array() |  | ||||||
| 		.ensure() |  | ||||||
| 		.min(1, "One or more characters must be selected"), |  | ||||||
| 	relationships: yup | 	relationships: yup | ||||||
| 		.array() | 		.array() | ||||||
| 		.ensure() | 		.ensure() | ||||||
| 		.of( | 		.of(yup.array().ensure().of(yup.string()).min(2, "Pairings must have at least two characters!").max(3, "Pairings can have no more than three characters!")), | ||||||
| 			yup |  | ||||||
| 				.array() |  | ||||||
| 				.ensure() |  | ||||||
| 				.of(yup.string()) |  | ||||||
| 				.min(2, "Pairings must have at least two characters!") |  | ||||||
| 				.max(3, "Pairings can have no more than three characters!"), |  | ||||||
| 		), |  | ||||||
| 	nsfw: yup.boolean().oneOf([true, false]), | 	nsfw: yup.boolean().oneOf([true, false]), | ||||||
| 	loggedInOnly: yup.boolean().when("nsfw", ([nsfw], schema) => { | 	loggedInOnly: yup.boolean().when("nsfw", ([nsfw], schema) => { | ||||||
| 		return nsfw | 		return nsfw | ||||||
| @ -44,20 +25,13 @@ export const cs = yup.object<FormChapter>().shape({ | |||||||
| 			: schema.oneOf([true, false]); | 			: schema.oneOf([true, false]); | ||||||
| 	}), | 	}), | ||||||
| 	hidden: yup.boolean().oneOf([true, false]), | 	hidden: yup.boolean().oneOf([true, false]), | ||||||
| 	pot: yup | 	pot: yup.string().oneOf(["pasteOrType", "upload"]).required("Story content is required!"), | ||||||
| 		.string() |  | ||||||
| 		.oneOf(["pasteOrType", "upload"]) |  | ||||||
| 		.required("Story content is required!"), |  | ||||||
| 	storytext: yup.string().when("pot", ([pot], schema) => { | 	storytext: yup.string().when("pot", ([pot], schema) => { | ||||||
| 		return pot === "pasteOrType" | 		return pot === "pasteOrType" | ||||||
| 			? schema | 			? schema | ||||||
| 					.test( | 					.test("numWords", "Story must be at least 50 words", (value: any, context) => { | ||||||
| 						"numWords", |  | ||||||
| 						"Story must be at least 50 words", |  | ||||||
| 						(value: any, context) => { |  | ||||||
| 						return value?.split(/\W+/).length > 50 || false; | 						return value?.split(/\W+/).length > 50 || false; | ||||||
| 						}, | 					}) | ||||||
| 					) |  | ||||||
| 					.required("Story text can't be blank!") | 					.required("Story text can't be blank!") | ||||||
| 			: schema.min(0); | 			: schema.min(0); | ||||||
| 	}), | 	}), | ||||||
| @ -98,10 +72,6 @@ export const cs = yup.object<FormChapter>().shape({ | |||||||
| 
 | 
 | ||||||
| export const storySchema = yup.object().shape({ | export const storySchema = yup.object().shape({ | ||||||
| 	title: yup.string().ensure().min(5, blankTitle).required(blankTitle), | 	title: yup.string().ensure().min(5, blankTitle).required(blankTitle), | ||||||
| 	chapters: yup | 	chapters: yup.array().min(1, "There must be at least one chapter.").of(cs).ensure(), | ||||||
| 		.array() |  | ||||||
| 		.min(1, "There must be at least one chapter.") |  | ||||||
| 		.of(cs) |  | ||||||
| 		.ensure(), |  | ||||||
| 	completed: yup.boolean().oneOf([true, false]), | 	completed: yup.boolean().oneOf([true, false]), | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -1,5 +1,3 @@ | |||||||
| import { readFileSync } from "fs"; |  | ||||||
| import { resolve } from "path"; |  | ||||||
| // import chardet from "chardet";
 | // import chardet from "chardet";
 | ||||||
| // import iconv from "iconv-lite";
 | // import iconv from "iconv-lite";
 | ||||||
| import { GridFSBucketReadStream } from "mongodb"; | import { GridFSBucketReadStream } from "mongodb"; | ||||||
| @ -15,20 +13,13 @@ export function countWords(string: string) { | |||||||
| 	return stripHtml(string).result.split(/W+/).length; | 	return stripHtml(string).result.split(/W+/).length; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function populate<T>( | export function populate<T>(field: string, model: string): PreMiddlewareFunction<Query<T, T>> { | ||||||
| 	field: string, |  | ||||||
| 	model: string, |  | ||||||
| ): PreMiddlewareFunction<Query<T, T>> { |  | ||||||
| 	return function (next: () => any) { | 	return function (next: () => any) { | ||||||
| 		this.populate(field, undefined, model); | 		this.populate(field, undefined, model); | ||||||
| 		next(); | 		next(); | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| export function populateSelected<T>( | export function populateSelected<T>(field: string, model: string, selection: string): PreMiddlewareFunction<Query<T, T>> { | ||||||
| 	field: string, |  | ||||||
| 	model: string, |  | ||||||
| 	selection: string, |  | ||||||
| ): PreMiddlewareFunction<Query<T, T>> { |  | ||||||
| 	return function (next: () => any) { | 	return function (next: () => any) { | ||||||
| 		this.populate(field, selection, model); | 		this.populate(field, selection, model); | ||||||
| 		next(); | 		next(); | ||||||
| @ -40,15 +31,11 @@ export function isFicmasHidden(story: IStory): boolean { | |||||||
| 		((story.ficmas as IFicmas)?.year == new Date().getFullYear() && | 		((story.ficmas as IFicmas)?.year == new Date().getFullYear() && | ||||||
| 			(story.ficmas as IFicmas)?.anniversary && | 			(story.ficmas as IFicmas)?.anniversary && | ||||||
| 			new Date() < new Date(Date.parse("Aug 1 " + new Date().getFullYear()))) || | 			new Date() < new Date(Date.parse("Aug 1 " + new Date().getFullYear()))) || | ||||||
| 		((story.ficmas as IFicmas)?.year == new Date().getFullYear() && | 		((story.ficmas as IFicmas)?.year == new Date().getFullYear() && !(story.ficmas as IFicmas)?.anniversary && ficsHidden(Date.now())) | ||||||
| 			!(story.ficmas as IFicmas)?.anniversary && |  | ||||||
| 			ficsHidden(Date.now())) |  | ||||||
| 	); | 	); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function stringifyStream( | export function stringifyStream(stream: GridFSBucketReadStream): Promise<string> { | ||||||
| 	stream: GridFSBucketReadStream, |  | ||||||
| ): Promise<string> { |  | ||||||
| 	let chunks: Buffer[] = []; | 	let chunks: Buffer[] = []; | ||||||
| 	return new Promise((res, rej) => { | 	return new Promise((res, rej) => { | ||||||
| 		stream.on("data", (c) => chunks.push(Buffer.from(c))); | 		stream.on("data", (c) => chunks.push(Buffer.from(c))); | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import { H3Event, EventHandlerRequest } from "h3"; | import { H3Event, EventHandlerRequest } from "h3"; | ||||||
| import { GridFSBucket } from "mongodb"; | import { Document } from "mongoose"; | ||||||
| import mongoose, { Document } from "mongoose"; |  | ||||||
| import { norm, stringifyStream } from "@functions"; | import { norm, stringifyStream } from "@functions"; | ||||||
| import { IStory } from "@models/stories"; | import { IStory } from "@models/stories"; | ||||||
| import { IChapter } from "@models/stories/chapter"; | import { IChapter } from "@models/stories/chapter"; | ||||||
| @ -16,11 +15,7 @@ export default async function ( | |||||||
| 	const cloned: any & { chapters: IChapter[] } = { ...finObj }; | 	const cloned: any & { chapters: IChapter[] } = { ...finObj }; | ||||||
| 	delete finObj.chapters; | 	delete finObj.chapters; | ||||||
| 	const bucket = getBucket(); | 	const bucket = getBucket(); | ||||||
| 	let ds = bucket.openDownloadStreamByName( | 	let ds = bucket.openDownloadStreamByName(`/stories/${cloned.chapters[cindex || event.context.chapterIndex || 0].id}.txt`); | ||||||
| 		`/stories/${ |  | ||||||
| 			cloned.chapters[cindex || event.context.chapterIndex || 0].id |  | ||||||
| 		}.txt`,
 |  | ||||||
| 	); |  | ||||||
| 	let stream = await stringifyStream(ds); | 	let stream = await stringifyStream(ds); | ||||||
| 	finObj.currentChapter = { | 	finObj.currentChapter = { | ||||||
| 		...cloned.chapters[cindex || event.context.chapterIndex || 0], | 		...cloned.chapters[cindex || event.context.chapterIndex || 0], | ||||||
|  | |||||||
| @ -1,4 +1,3 @@ | |||||||
| import { Band } from "@models/band"; |  | ||||||
| import { Challenge } from "@models/challenges/gen"; | import { Challenge } from "@models/challenges/gen"; | ||||||
| import { IStory, Story } from "@models/stories"; | import { IStory, Story } from "@models/stories"; | ||||||
| import { log } from "../logger"; | import { log } from "../logger"; | ||||||
|  | |||||||
| @ -1,16 +1,12 @@ | |||||||
| import { H3Event, EventHandlerRequest } from "h3"; |  | ||||||
| import { apiRoot } from "./constants"; | import { apiRoot } from "./constants"; | ||||||
| export default async function (id: number): Promise<number> { | export default async function (id: number): Promise<number> { | ||||||
| 	let { data: lookup } = await useFetch<any>( | 	let { data: lookup } = await useFetch<any>(`${apiRoot}/session-sharing/lookup`, { | ||||||
| 		`${apiRoot}/session-sharing/lookup`, |  | ||||||
| 		{ |  | ||||||
| 		method: "get", | 		method: "get", | ||||||
| 		query: { | 		query: { | ||||||
| 			params: { | 			params: { | ||||||
| 				id, | 				id, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		}, | 	}); | ||||||
| 	); |  | ||||||
| 	return lookup.value.uid as number; | 	return lookup.value.uid as number; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,12 +1,10 @@ | |||||||
| import winston from "winston"; | import winston from "winston"; | ||||||
| const { combine, timestamp, json, splat, printf, colorize } = winston.format; | const { combine, timestamp, json, splat, printf, colorize } = winston.format; | ||||||
| 
 | 
 | ||||||
| winston.add; | // winston.add;
 | ||||||
| 
 | 
 | ||||||
| const fmt = printf(({ timestamp, level, message, label, durationMs }) => { | const fmt = printf(({ timestamp, level, message, label, durationMs }) => { | ||||||
| 	return `${timestamp} [${label || "misc"}] ${message} ${ | 	return `${timestamp} [${label || "misc"}] ${message} ${!!durationMs ? " (took " + durationMs + "ms)" : ""}`; | ||||||
| 		!!durationMs ? " (took " + durationMs + "ms)" : "" |  | ||||||
| 	}`;
 |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const cfmt = combine(json(), timestamp(), fmt); | const cfmt = combine(json(), timestamp(), fmt); | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ import { EventHandlerRequest, H3Event } from "h3"; | |||||||
| import { messages } from "@server/constants"; | import { messages } from "@server/constants"; | ||||||
| import { IStory } from "@models/stories"; | import { IStory } from "@models/stories"; | ||||||
| import { isFicmasHidden } from "@functions"; | import { isFicmasHidden } from "@functions"; | ||||||
| import { IDraft } from "@models/stories/draft"; |  | ||||||
| import axios from "axios"; | import axios from "axios"; | ||||||
| import { IUser } from "@models/user"; | import { IUser } from "@models/user"; | ||||||
| export function isIdNan(ev: H3Event<EventHandlerRequest>) { | export function isIdNan(ev: H3Event<EventHandlerRequest>) { | ||||||
| @ -33,11 +32,7 @@ export function isLoggedIn(ev: H3Event<EventHandlerRequest>) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function storyCheck( | export async function storyCheck(event: H3Event<EventHandlerRequest>, story: IStory, idx: number) { | ||||||
| 	event: H3Event<EventHandlerRequest>, |  | ||||||
| 	story: IStory, |  | ||||||
| 	idx: number, |  | ||||||
| ) { |  | ||||||
| 	let ret: any = {}; | 	let ret: any = {}; | ||||||
| 	if (!story) { | 	if (!story) { | ||||||
| 		ret.statusCode = 404; | 		ret.statusCode = 404; | ||||||
| @ -49,11 +44,7 @@ export async function storyCheck( | |||||||
| 				message: `TOP SECRET! This story is part of an ongoing challenge. You'll be able to read it after the challenge's reveal date.`, | 				message: `TOP SECRET! This story is part of an ongoing challenge. You'll be able to read it after the challenge's reveal date.`, | ||||||
| 			}; | 			}; | ||||||
| 		} | 		} | ||||||
| 	} else if ( | 	} else if (story.chapters[idx]?.hidden && event.context.currentUser?._id !== (story.author as IUser)._id && !event.context.currentUser?.profile.isAdmin) { | ||||||
| 		story.chapters[idx]?.hidden && |  | ||||||
| 		event.context.currentUser?._id !== (story.author as IUser)._id && |  | ||||||
| 		!event.context.currentUser?.profile.isAdmin |  | ||||||
| 	) { |  | ||||||
| 		ret.statusCode = 403; | 		ret.statusCode = 403; | ||||||
| 		ret.message = messages[403]; | 		ret.message = messages[403]; | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import mongoose, { connect, Document, Model } from "mongoose"; | import mongoose, { Model } from "mongoose"; | ||||||
| const { Schema, model } = mongoose; | const { Schema, model } = mongoose; | ||||||
| import SequenceFactory from "mongoose-sequence"; | import SequenceFactory from "mongoose-sequence"; | ||||||
| import { hasMigrated } from "@dbconfig"; | import { hasMigrated } from "@dbconfig"; | ||||||
| @ -30,11 +30,5 @@ const BandSchema = new mongoose.Schema<IBand>({ | |||||||
| 	], | 	], | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| hasMigrated && | hasMigrated && !mongoose.models.Band && BandSchema.plugin(AutoIncrement, { id: "band" }); | ||||||
| 	!mongoose.models.Band && | export const Band: Model<IBand> = /* mongoose.models.Band || */ model<IBand>("Band", BandSchema, "bands"); | ||||||
| 	BandSchema.plugin(AutoIncrement, { id: "band" }); |  | ||||||
| export const Band: Model<IBand> = /* mongoose.models.Band || */ model<IBand>( |  | ||||||
| 	"Band", |  | ||||||
| 	BandSchema, |  | ||||||
| 	"bands", |  | ||||||
| ); |  | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import mongoose, { Schema, PopulatedDoc, Document, Model } from "mongoose"; | import mongoose, { PopulatedDoc, Model } from "mongoose"; | ||||||
| import { IUser } from "@models/user"; | import { IUser } from "@models/user"; | ||||||
| import SequenceFactory from "mongoose-sequence"; | import SequenceFactory from "mongoose-sequence"; | ||||||
| import { hasMigrated } from "@dbconfig"; | import { hasMigrated } from "@dbconfig"; | ||||||
| @ -67,8 +67,5 @@ const biffnoschema = new mongoose.Schema<IBiffno>({ | |||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| hasMigrated && | hasMigrated && !mongoose.models.Biffno && biffnoschema.plugin(AutoIncrement, { start_seq: 1, id: "bif_id" }); | ||||||
| 	!mongoose.models.Biffno && | export const Biffno: Model<IBiffno> = mongoose.models.Biffno || mongoose.model("Biffno", biffnoschema, "biffno"); | ||||||
| 	biffnoschema.plugin(AutoIncrement, { start_seq: 1, id: "bif_id" }); |  | ||||||
| export const Biffno: Model<IBiffno> = |  | ||||||
| 	mongoose.models.Biffno || mongoose.model("Biffno", biffnoschema, "biffno"); |  | ||||||
|  | |||||||
| @ -1,10 +1,4 @@ | |||||||
| import mongoose, { | import mongoose, { PopulatedDoc, Model, model } from "mongoose"; | ||||||
| 	Schema, |  | ||||||
| 	PopulatedDoc, |  | ||||||
| 	Document, |  | ||||||
| 	Model, |  | ||||||
| 	model, |  | ||||||
| } from "mongoose"; |  | ||||||
| 
 | 
 | ||||||
| import { IBand } from "@models/band"; | import { IBand } from "@models/band"; | ||||||
| import { IUser } from "@models/user"; | import { IUser } from "@models/user"; | ||||||
| @ -48,9 +42,6 @@ export const FicmasSchema = new mongoose.Schema<IFicmas>({ | |||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| hasMigrated && | hasMigrated && !mongoose.models.Ficmas && FicmasSchema.plugin(AutoIncrement, { id: "ficmas_wishes", inc_field: "_id" }); | ||||||
| 	!mongoose.models.Ficmas && |  | ||||||
| 	FicmasSchema.plugin(AutoIncrement, { id: "ficmas_wishes", inc_field: "_id" }); |  | ||||||
| 
 | 
 | ||||||
| export const Ficmas: Model<IFicmas> = | export const Ficmas: Model<IFicmas> = mongoose.models.Ficmas || model("Ficmas", FicmasSchema, "ficmas_wishes"); | ||||||
| 	mongoose.models.Ficmas || model("Ficmas", FicmasSchema, "ficmas_wishes"); |  | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import mongoose, { Schema, PopulatedDoc, Document, Model } from "mongoose"; | import mongoose, { Model } from "mongoose"; | ||||||
| import SequenceFactory from "mongoose-sequence"; | import SequenceFactory from "mongoose-sequence"; | ||||||
| import { hasMigrated } from "@dbconfig"; | import { hasMigrated } from "@dbconfig"; | ||||||
| 
 | 
 | ||||||
| @ -45,9 +45,7 @@ const challengeSchema = new mongoose.Schema<IChallenge>({ | |||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| hasMigrated && | hasMigrated && !mongoose.models.Challenge && challengeSchema.plugin(AutoIncrement, { id: "challenges" }); | ||||||
| 	!mongoose.models.Challenge && |  | ||||||
| 	challengeSchema.plugin(AutoIncrement, { id: "challenges" }); |  | ||||||
| export const Challenge: Model<IChallenge> = | export const Challenge: Model<IChallenge> = | ||||||
| 	// mongoose.models.Challenge ||
 | 	// mongoose.models.Challenge ||
 | ||||||
| 	mongoose.model("Challenge", challengeSchema, "challenges"); | 	mongoose.model("Challenge", challengeSchema, "challenges"); | ||||||
|  | |||||||
| @ -1,11 +1,4 @@ | |||||||
| import mongoose, { | import mongoose, { Schema, PopulatedDoc, Model } from "mongoose"; | ||||||
| 	Schema, |  | ||||||
| 	connect, |  | ||||||
| 	PopulatedDoc, |  | ||||||
| 	Document, |  | ||||||
| 	Model, |  | ||||||
| } from "mongoose"; |  | ||||||
| import SequenceFactory from "mongoose-sequence"; |  | ||||||
| 
 | 
 | ||||||
| import { IPrivMsg } from "./privMsg"; | import { IPrivMsg } from "./privMsg"; | ||||||
| import { IUser } from "./user"; | import { IUser } from "./user"; | ||||||
| @ -41,8 +34,4 @@ const InboxSchema = new Schema<IInbox>({ | |||||||
| 	], | 	], | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export const Inbox: Model<IInbox> = mongoose.model<IInbox>( | export const Inbox: Model<IInbox> = mongoose.model<IInbox>("Inbox", InboxSchema, "inboxes"); | ||||||
| 	"Inbox", |  | ||||||
| 	InboxSchema, |  | ||||||
| 	"inboxes", |  | ||||||
| ); |  | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import mongoose, { Schema, PopulatedDoc, Document, Model } from "mongoose"; | import mongoose, { PopulatedDoc, Model } from "mongoose"; | ||||||
| import SequenceFactory from "mongoose-sequence"; | import SequenceFactory from "mongoose-sequence"; | ||||||
| import { hasMigrated } from "@dbconfig"; | import { hasMigrated } from "@dbconfig"; | ||||||
| import { IUser } from "./user"; | import { IUser } from "./user"; | ||||||
| @ -50,13 +50,6 @@ const PMSchema = new mongoose.Schema<IPrivMsg>({ | |||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| hasMigrated && | hasMigrated && !mongoose.models.PrivMsg && PMSchema.plugin(AutoIncrement, { id: "private_message" }); | ||||||
| 	!mongoose.models.PrivMsg && |  | ||||||
| 	PMSchema.plugin(AutoIncrement, { id: "private_message" }); |  | ||||||
| 
 | 
 | ||||||
| export const PrivMsg: Model<IPrivMsg> = | export const PrivMsg: Model<IPrivMsg> = /* mongoose.models.PrivMsg || */ mongoose.model("PrivMsg", PMSchema, "private_messages"); | ||||||
| 	/* mongoose.models.PrivMsg || */ mongoose.model( |  | ||||||
| 		"PrivMsg", |  | ||||||
| 		PMSchema, |  | ||||||
| 		"private_messages", |  | ||||||
| 	); |  | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import mongoose, { connect, PopulatedDoc, Document } from "mongoose"; | import mongoose from "mongoose"; | ||||||
| const { Schema, model } = mongoose; | const { Schema, model } = mongoose; | ||||||
| 
 | 
 | ||||||
| interface IAbstractQM { | interface IAbstractQM { | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import mongoose, { Schema, PopulatedDoc, Document, Model } from "mongoose"; | import mongoose, { Model } from "mongoose"; | ||||||
| 
 | 
 | ||||||
| export enum Color { | export enum Color { | ||||||
| 	"orange" = "orange", | 	"orange" = "orange", | ||||||
| @ -35,9 +35,4 @@ const SISchema = new mongoose.Schema<ISidebarItem>({ | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export const SidebarItem: Model<ISidebarItem> = | export const SidebarItem: Model<ISidebarItem> = | ||||||
| 	mongoose.models.SidebarItem || | 	mongoose.models.SidebarItem || /* mongoose.models.SidebarItem || */ mongoose.model("SidebarItem", SISchema, "sidebar"); | ||||||
| 	/* mongoose.models.SidebarItem || */ mongoose.model( |  | ||||||
| 		"SidebarItem", |  | ||||||
| 		SISchema, |  | ||||||
| 		"sidebar", |  | ||||||
| 	); |  | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { Schema, PopulatedDoc, Document, Model } from "mongoose"; | import { Schema, PopulatedDoc } from "mongoose"; | ||||||
| import { IBand } from "@models/band"; | import { IBand } from "@models/band"; | ||||||
| export interface IChapter { | export interface IChapter { | ||||||
| 	title: string; | 	title: string; | ||||||
|  | |||||||
| @ -1,19 +1,12 @@ | |||||||
| import { IStory } from "."; | import { IStory } from "."; | ||||||
| import { hasMigrated } from "@dbconfig"; | import { hasMigrated } from "@dbconfig"; | ||||||
| import { IBand } from "@models/band"; | import mongoose, { Schema, Model } from "mongoose"; | ||||||
| import { IFicmas } from "@models/challenges/ficmas"; |  | ||||||
| import { IChallenge } from "@models/challenges/gen"; |  | ||||||
| import { IUser } from "@models/user"; |  | ||||||
| import mongoose, { Schema, PopulatedDoc, Document, Model } from "mongoose"; |  | ||||||
| import SequenceFactory from "mongoose-sequence"; | import SequenceFactory from "mongoose-sequence"; | ||||||
| import { Chapter } from "./chapter"; | import { Chapter } from "./chapter"; | ||||||
| 
 | 
 | ||||||
| const AutoIncrement = SequenceFactory(mongoose); | const AutoIncrement = SequenceFactory(mongoose); | ||||||
| 
 | 
 | ||||||
| export type IDraft = Omit< | export type IDraft = Omit<IStory, "recs" | "favs" | "reviews" | "views" | "downloads" | "posted">; | ||||||
| 	IStory, |  | ||||||
| 	"recs" | "favs" | "reviews" | "views" | "downloads" | "posted" |  | ||||||
| >; |  | ||||||
| 
 | 
 | ||||||
| // const Cha
 | // const Cha
 | ||||||
| 
 | 
 | ||||||
| @ -39,9 +32,6 @@ const DraftSchema = new Schema<IDraft>( | |||||||
| 	{ timestamps: true }, | 	{ timestamps: true }, | ||||||
| ); | ); | ||||||
| 
 | 
 | ||||||
| hasMigrated && | hasMigrated && !mongoose.models.Draft && DraftSchema.plugin(AutoIncrement, { id: "drafts" }); | ||||||
| 	!mongoose.models.Draft && |  | ||||||
| 	DraftSchema.plugin(AutoIncrement, { id: "drafts" }); |  | ||||||
| 
 | 
 | ||||||
| export const Draft: Model<IDraft> = | export const Draft: Model<IDraft> = /* mongoose.models.Draft || */ mongoose.model("Draft", DraftSchema, "drafts"); | ||||||
| 	/* mongoose.models.Draft || */ mongoose.model("Draft", DraftSchema, "drafts"); |  | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import mongoose, { Schema, PopulatedDoc, Document, Model } from "mongoose"; | import mongoose, { PopulatedDoc, Model } from "mongoose"; | ||||||
| import SequenceFactory from "mongoose-sequence"; | import SequenceFactory from "mongoose-sequence"; | ||||||
| 
 | 
 | ||||||
| const AutoIncrement = SequenceFactory(mongoose); | const AutoIncrement = SequenceFactory(mongoose); | ||||||
| @ -87,13 +87,6 @@ const StorySchema = new mongoose.Schema<IStory>({ | |||||||
| 		default: new Date(), | 		default: new Date(), | ||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
| hasMigrated && | hasMigrated && !mongoose.models.Story && Chapter.plugin(AutoIncrement, { id: "chapterid", inc_field: "id" }); | ||||||
| 	!mongoose.models.Story && |  | ||||||
| 	Chapter.plugin(AutoIncrement, { id: "chapterid", inc_field: "id" }); |  | ||||||
| hasMigrated && StorySchema.plugin(AutoIncrement, { id: "storyid" }); | hasMigrated && StorySchema.plugin(AutoIncrement, { id: "storyid" }); | ||||||
| export const Story: Model<IStory> = | export const Story: Model<IStory> = /* mongoose.models.Story || */ mongoose.model("Story", StorySchema, "stories"); | ||||||
| 	/* mongoose.models.Story || */ mongoose.model( |  | ||||||
| 		"Story", |  | ||||||
| 		StorySchema, |  | ||||||
| 		"stories", |  | ||||||
| 	); |  | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import mongoose, { Schema, PopulatedDoc, Document, Model } from "mongoose"; | import mongoose, { PopulatedDoc, Model } from "mongoose"; | ||||||
| import SequenceFactory from "mongoose-sequence"; | import SequenceFactory from "mongoose-sequence"; | ||||||
| import { hasMigrated } from "@dbconfig"; | import { hasMigrated } from "@dbconfig"; | ||||||
| import { populate, populateSelected } from "@functions"; | import { populate, populateSelected } from "@functions"; | ||||||
| @ -63,22 +63,9 @@ CommentSchema | |||||||
| 	// .pre("find", populateSelected("replyingTo", modelName, "-replies -author"))
 | 	// .pre("find", populateSelected("replyingTo", modelName, "-replies -author"))
 | ||||||
| 	.pre("findOne", populate("replies", modelName)) | 	.pre("findOne", populate("replies", modelName)) | ||||||
| 	.pre("find", populate("replies", modelName)) | 	.pre("find", populate("replies", modelName)) | ||||||
| 	.pre( | 	.pre("findOne", populateSelected("author", "User", "profile username _id blocked")) | ||||||
| 		"findOne", | 	.pre("find", populateSelected("author", "User", "profile username _id blocked")); | ||||||
| 		populateSelected("author", "User", "profile username _id blocked"), |  | ||||||
| 	) |  | ||||||
| 	.pre( |  | ||||||
| 		"find", |  | ||||||
| 		populateSelected("author", "User", "profile username _id blocked"), |  | ||||||
| 	); |  | ||||||
| 
 | 
 | ||||||
| hasMigrated && | hasMigrated && !mongoose.models.Review && CommentSchema.plugin(AutoIncrement, { id: "reviews" }); | ||||||
| 	!mongoose.models.Review && |  | ||||||
| 	CommentSchema.plugin(AutoIncrement, { id: "reviews" }); |  | ||||||
| 
 | 
 | ||||||
| export const Review: Model<IReview> = | export const Review: Model<IReview> = /* mongoose.models.Review || */ mongoose.model(modelName, CommentSchema, "reviews"); | ||||||
| 	/* mongoose.models.Review || */ mongoose.model( |  | ||||||
| 		modelName, |  | ||||||
| 		CommentSchema, |  | ||||||
| 		"reviews", |  | ||||||
| 	); |  | ||||||
|  | |||||||
| @ -1,10 +1,4 @@ | |||||||
| import mongoose, { | import mongoose, { PopulatedDoc, Document, Model } from "mongoose"; | ||||||
| 	Schema, |  | ||||||
| 	connect, |  | ||||||
| 	PopulatedDoc, |  | ||||||
| 	Document, |  | ||||||
| 	Model, |  | ||||||
| } from "mongoose"; |  | ||||||
| import SequenceFactory from "mongoose-sequence"; | import SequenceFactory from "mongoose-sequence"; | ||||||
| import bcrypt from "bcryptjs"; | import bcrypt from "bcryptjs"; | ||||||
| import md5 from "blueimp-md5"; | import md5 from "blueimp-md5"; | ||||||
| @ -274,25 +268,15 @@ UserSchema.static("generateHash", function (password: string): string { | |||||||
| 	return bcrypt.hashSync(password, bcrypt.genSaltSync(8)); | 	return bcrypt.hashSync(password, bcrypt.genSaltSync(8)); | ||||||
| }); | }); | ||||||
| UserSchema.methods.validPassword = function (password: string): boolean { | UserSchema.methods.validPassword = function (password: string): boolean { | ||||||
| 	return ( | 	return md5(password) === this.password || bcrypt.compareSync(password, this.password) || false; | ||||||
| 		md5(password) === this.password || |  | ||||||
| 		bcrypt.compareSync(password, this.password) || |  | ||||||
| 		false |  | ||||||
| 	); |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| UserSchema.methods.generateJWT = function (jwtKey: string): string { | UserSchema.methods.generateJWT = function (jwtKey: string): string { | ||||||
| 	let token = jwt.sign( | 	let token = jwt.sign({ id: this._id, isAdmin: this.profile.isAdmin }, jwtKey, { | ||||||
| 		{ id: this._id, isAdmin: this.profile.isAdmin }, |  | ||||||
| 		jwtKey, |  | ||||||
| 		{ |  | ||||||
| 		expiresIn: "14 days", | 		expiresIn: "14 days", | ||||||
| 		}, | 	}); | ||||||
| 	); |  | ||||||
| 	return token; | 	return token; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| hasMigrated && | hasMigrated && !mongoose.models.User && UserSchema.plugin(AutoIncrement, { id: "userid", inc_field: "_id" }); | ||||||
| 	!mongoose.models.User && |  | ||||||
| 	UserSchema.plugin(AutoIncrement, { id: "userid", inc_field: "_id" }); |  | ||||||
| export const User = mongoose.model<IUser, UModel>("User", UserSchema, "users"); | export const User = mongoose.model<IUser, UModel>("User", UserSchema, "users"); | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| 	import { ref, reactive } from "vue"; | 	import { reactive } from "vue"; | ||||||
| 	import useApiFetch from "../../composables/useApiFetch"; |  | ||||||
| 	import { notification } from "ant-design-vue"; | 	import { notification } from "ant-design-vue"; | ||||||
| 	interface FormState { | 	interface FormState { | ||||||
| 		username: string; | 		username: string; | ||||||
| @ -38,28 +37,11 @@ | |||||||
| 	}; | 	}; | ||||||
| </script> | </script> | ||||||
| <template> | <template> | ||||||
| 	<a-form | 	<a-form :model="formState" name="basic" :label-col="{ span: 8 }" autocomplete="off" :colon="false" layout="vertical" @finish="onFinish"> | ||||||
| 		:model="formState" | 		<a-form-item label="Username" name="username" :rules="[{ required: true, message: 'Username required!' }]"> | ||||||
| 		name="basic" |  | ||||||
| 		:label-col="{ span: 8 }" |  | ||||||
| 		autocomplete="off" |  | ||||||
| 		:colon="false" |  | ||||||
| 		layout="vertical" |  | ||||||
| 		@finish="onFinish" |  | ||||||
| 	> |  | ||||||
| 		<a-form-item |  | ||||||
| 			label="Username" |  | ||||||
| 			name="username" |  | ||||||
| 			:rules="[{ required: true, message: 'Username required!' }]" |  | ||||||
| 		> |  | ||||||
| 			<a-input v-model:value="formState.username" /> | 			<a-input v-model:value="formState.username" /> | ||||||
| 		</a-form-item> | 		</a-form-item> | ||||||
| 		<a-form-item | 		<a-form-item :colon="false" label="Password" name="password" :rules="[{ required: true, message: 'Password required!' }]"> | ||||||
| 			:colon="false" |  | ||||||
| 			label="Password" |  | ||||||
| 			name="password" |  | ||||||
| 			:rules="[{ required: true, message: 'Password required!' }]" |  | ||||||
| 		> |  | ||||||
| 			<a-input-password v-model:value="formState.password" /> | 			<a-input-password v-model:value="formState.password" /> | ||||||
| 		</a-form-item> | 		</a-form-item> | ||||||
| 		<a-form-item> | 		<a-form-item> | ||||||
|  | |||||||
| @ -1,21 +1,10 @@ | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| 	import { ref, reactive } from "vue"; | 	import { Field as veeField, useField, useForm } from "vee-validate"; | ||||||
| 	import { |  | ||||||
| 		Form as veeForm, |  | ||||||
| 		Field as veeField, |  | ||||||
| 		useField, |  | ||||||
| 		useForm, |  | ||||||
| 		useSetFieldValue, |  | ||||||
| 		useFormErrors, |  | ||||||
| 		useFormValues, |  | ||||||
| 		RuleExpression, |  | ||||||
| 	} from "vee-validate"; |  | ||||||
| 	import * as yup from "yup"; | 	import * as yup from "yup"; | ||||||
| 	import { useRecaptchaProvider } from "vue-recaptcha"; | 	import { useRecaptchaProvider } from "vue-recaptcha"; | ||||||
| 
 | 
 | ||||||
| 	import { useChallengeV2 } from "vue-recaptcha"; | 	import { useChallengeV2 } from "vue-recaptcha"; | ||||||
| 	import { notification } from "ant-design-vue"; | 	import { notification } from "ant-design-vue"; | ||||||
| 	import { log } from "@server/logger"; |  | ||||||
| 	import termsOfServices from "~/components/tos.vue"; | 	import termsOfServices from "~/components/tos.vue"; | ||||||
| 	useRecaptchaProvider(); | 	useRecaptchaProvider(); | ||||||
| 
 | 
 | ||||||
| @ -34,24 +23,11 @@ | |||||||
| 	}); | 	}); | ||||||
| 	// const { execute } = useChallengeV3('submit'); | 	// const { execute } = useChallengeV3('submit'); | ||||||
| 	const vschema = yup.object<FormState>().shape({ | 	const vschema = yup.object<FormState>().shape({ | ||||||
| 		username: yup | 		username: yup.string().ensure().trim().min(1).required("Username is required!"), | ||||||
| 			.string() | 		password: yup.string().ensure().trim().min(8).required("Password is required!"), | ||||||
| 			.ensure() |  | ||||||
| 			.trim() |  | ||||||
| 			.min(1) |  | ||||||
| 			.required("Username is required!"), |  | ||||||
| 		password: yup |  | ||||||
| 			.string() |  | ||||||
| 			.ensure() |  | ||||||
| 			.trim() |  | ||||||
| 			.min(8) |  | ||||||
| 			.required("Password is required!"), |  | ||||||
| 		email: yup.string().ensure().trim().email().required("Email is required!"), | 		email: yup.string().ensure().trim().email().required("Email is required!"), | ||||||
| 		// recaptcha: yup.string().required('Please verify you are human.'), | 		// recaptcha: yup.string().required('Please verify you are human.'), | ||||||
| 		agree: yup | 		agree: yup.boolean().oneOf([true], "Please agree to the terms.").required("Please agree to the terms."), | ||||||
| 			.boolean() |  | ||||||
| 			.oneOf([true], "Please agree to the terms.") |  | ||||||
| 			.required("Please agree to the terms."), |  | ||||||
| 	}); | 	}); | ||||||
| 	const dv: FormState = { | 	const dv: FormState = { | ||||||
| 		username: "", | 		username: "", | ||||||
| @ -120,48 +96,26 @@ notification[ "error" ]({ | |||||||
| <template> | <template> | ||||||
| 	<form @submit="onFinish"> | 	<form @submit="onFinish"> | ||||||
| 		<vee-field name="username" v-slot="{ field, errorMessage, value }"> | 		<vee-field name="username" v-slot="{ field, errorMessage, value }"> | ||||||
| 			<a-form-item | 			<a-form-item label="Username" :name="field.name" :validate-status="!!errorMessage ? 'error' : ''" :help="errorMessage"> | ||||||
| 				label="Username" |  | ||||||
| 				:name="field.name" |  | ||||||
| 				:validate-status="!!errorMessage ? 'error' : ''" |  | ||||||
| 				:help="errorMessage" |  | ||||||
| 			> |  | ||||||
| 				<a-input v-bind="field" v-model:value="field.value" /> | 				<a-input v-bind="field" v-model:value="field.value" /> | ||||||
| 			</a-form-item> | 			</a-form-item> | ||||||
| 		</vee-field> | 		</vee-field> | ||||||
| 		<vee-field name="email" v-slot="{ field, errorMessage, value }"> | 		<vee-field name="email" v-slot="{ field, errorMessage, value }"> | ||||||
| 			<a-form-item | 			<a-form-item label="Email address" :name="field.name" :validate-status="!!errorMessage ? 'error' : ''" :help="errorMessage"> | ||||||
| 				label="Email address" |  | ||||||
| 				:name="field.name" |  | ||||||
| 				:validate-status="!!errorMessage ? 'error' : ''" |  | ||||||
| 				:help="errorMessage" |  | ||||||
| 			> |  | ||||||
| 				<a-input v-bind="field" v-model:value="field.value" /> | 				<a-input v-bind="field" v-model:value="field.value" /> | ||||||
| 			</a-form-item> | 			</a-form-item> | ||||||
| 		</vee-field> | 		</vee-field> | ||||||
| 		<vee-field name="password" v-slot="{ field, errorMessage, value }"> | 		<vee-field name="password" v-slot="{ field, errorMessage, value }"> | ||||||
| 			<a-form-item | 			<a-form-item label="Password" :name="field.name" :validate-status="!!errorMessage ? 'error' : ''" :help="errorMessage"> | ||||||
| 				label="Password" |  | ||||||
| 				:name="field.name" |  | ||||||
| 				:validate-status="!!errorMessage ? 'error' : ''" |  | ||||||
| 				:help="errorMessage" |  | ||||||
| 			> |  | ||||||
| 				<a-input-password v-bind="field" v-model:value="field.value" /> | 				<a-input-password v-bind="field" v-model:value="field.value" /> | ||||||
| 			</a-form-item> | 			</a-form-item> | ||||||
| 		</vee-field> | 		</vee-field> | ||||||
| 
 | 
 | ||||||
| 		<a-typography-title :level="4" :style="{ textAlign: 'center' }" | 		<a-typography-title :level="4" :style="{ textAlign: 'center' }">Terms</a-typography-title> | ||||||
| 			>Terms</a-typography-title |  | ||||||
| 		> |  | ||||||
| 		<div class="maxHeightScroller"> | 		<div class="maxHeightScroller"> | ||||||
| 			<div style="height: 100%"> | 			<div style="height: 100%"> | ||||||
| 				<terms-of-services /> | 				<terms-of-services /> | ||||||
| 				<vee-field | 				<vee-field name="agree" :unchecked-value="false" type="checkbox" v-slot="{ field, value, errorMessage }"> | ||||||
| 					name="agree" |  | ||||||
| 					:unchecked-value="false" |  | ||||||
| 					type="checkbox" |  | ||||||
| 					v-slot="{ field, value, errorMessage }" |  | ||||||
| 				> |  | ||||||
| 					<a-checkbox | 					<a-checkbox | ||||||
| 						@update:checked=" | 						@update:checked=" | ||||||
| 							(n) => { | 							(n) => { | ||||||
| @ -179,9 +133,7 @@ notification[ "error" ]({ | |||||||
| 		<div ref="root" /> | 		<div ref="root" /> | ||||||
| 		<a-row :align="'middle'" justify="center"> | 		<a-row :align="'middle'" justify="center"> | ||||||
| 			<a-col> | 			<a-col> | ||||||
| 				<a-button size="large" type="primary" html-type="submit" | 				<a-button size="large" type="primary" html-type="submit">Sign me up!</a-button> | ||||||
| 					>Sign me up!</a-button |  | ||||||
| 				> |  | ||||||
| 			</a-col> | 			</a-col> | ||||||
| 		</a-row> | 		</a-row> | ||||||
| 	</form> | 	</form> | ||||||
|  | |||||||
| @ -1,6 +1,4 @@ | |||||||
| <script setup lang="ts"> | <script setup lang="ts"> | ||||||
| 	import { IChapter } from "@models/stories/chapter"; |  | ||||||
| 	import { IStory } from "@models/stories"; |  | ||||||
| 	import { storyEditMiddleware } from "@client/middleware"; | 	import { storyEditMiddleware } from "@client/middleware"; | ||||||
| 	import { SingleChapterResult } from "@client/types/slightlyDifferentStory"; | 	import { SingleChapterResult } from "@client/types/slightlyDifferentStory"; | ||||||
| 	import SingleChapter from "~/components/story/create/singleChapter.vue"; | 	import SingleChapter from "~/components/story/create/singleChapter.vue"; | ||||||
| @ -13,9 +11,7 @@ | |||||||
| 	const rtr = useRoute(); | 	const rtr = useRoute(); | ||||||
| 	const { | 	const { | ||||||
| 		data: { value: originalStory }, | 		data: { value: originalStory }, | ||||||
| 	} = await useApiFetch<SingleChapterResult | null>( | 	} = await useApiFetch<SingleChapterResult | null>(`/story/${rtr.params.id}/${rtr.params.cidx}`); | ||||||
| 		`/story/${rtr.params.id}/${rtr.params.cidx}`, |  | ||||||
| 	); |  | ||||||
| 	if (originalStory === null) { | 	if (originalStory === null) { | ||||||
| 		throw createError({ | 		throw createError({ | ||||||
| 			statusCode: 404, | 			statusCode: 404, | ||||||
| @ -28,8 +24,5 @@ | |||||||
| </script> | </script> | ||||||
| 
 | 
 | ||||||
| <template> | <template> | ||||||
| 	<single-chapter | 	<single-chapter :data="toFormChapter(originalStory!.currentChapter)" :name="originalStory!.currentChapter.title" /> | ||||||
| 		:data="toFormChapter(originalStory!.currentChapter)" |  | ||||||
| 		:name="originalStory!.currentChapter.title" |  | ||||||
| 	/> |  | ||||||
| </template> | </template> | ||||||
|  | |||||||
| @ -6,12 +6,12 @@ | |||||||
| 	import { IChapter } from "@models/stories/chapter"; | 	import { IChapter } from "@models/stories/chapter"; | ||||||
| 
 | 
 | ||||||
| 	import { storyEditMiddleware } from "@client/middleware"; | 	import { storyEditMiddleware } from "@client/middleware"; | ||||||
|  | 	import { IBand } from "@models/band"; | ||||||
|  | 	import { IUser } from "@models/user"; | ||||||
| 	const rtr = useRoute(); | 	const rtr = useRoute(); | ||||||
| 	const { | 	const { | ||||||
| 		data: { value: originalStory }, | 		data: { value: originalStory }, | ||||||
| 	} = await useApiFetch< | 	} = await useApiFetch<({ chapters: (IChapter & { text: string })[] } & IStory) | null>(`/story/${rtr.params.id}/full`); | ||||||
| 		({ chapters: (IChapter & { text: string })[] } & IStory) | null |  | ||||||
| 	>(`/story/${rtr.params.id}/full`); |  | ||||||
| 	if (originalStory === null) { | 	if (originalStory === null) { | ||||||
| 		console.log("IT DOESN'T EXIST DAWG"); | 		console.log("IT DOESN'T EXIST DAWG"); | ||||||
| 		throw createError({ | 		throw createError({ | ||||||
| @ -20,18 +20,18 @@ | |||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| 	definePageMeta({ | 	definePageMeta({ | ||||||
| 		middleware: [storyEditMiddleware, "auth"], | 		middleware: ["auth", storyEditMiddleware], | ||||||
| 	}); | 	}); | ||||||
| 	const story: FormStory = { | 	const story: FormStory = { | ||||||
| 		title: originalStory!.title, | 		title: originalStory!.title, | ||||||
| 		coAuthor: originalStory?.coAuthor ? originalStory.coAuthor._id : null, | 		coAuthor: originalStory?.coAuthor ? (originalStory.coAuthor as IUser)._id : null, | ||||||
| 		completed: originalStory!.completed, | 		completed: originalStory!.completed, | ||||||
| 		chapters: originalStory!.chapters.map((a, i) => ({ | 		chapters: originalStory!.chapters.map((a, i) => ({ | ||||||
| 			...a, | 			...a, | ||||||
| 			id: a.id, | 			id: a.id, | ||||||
| 			chapterTitle: a.title, | 			chapterTitle: a.title, | ||||||
| 			index: i + 1, | 			index: i + 1, | ||||||
| 			bands: a.bands.map((a) => a._id), | 			bands: (a.bands as IBand[]).map((a) => a._id), | ||||||
| 			content: a.text, | 			content: a.text, | ||||||
| 			uuidKey: v4(), | 			uuidKey: v4(), | ||||||
| 		})), | 		})), | ||||||
| @ -41,14 +41,6 @@ | |||||||
| 	}); | 	}); | ||||||
| </script> | </script> | ||||||
| <template> | <template> | ||||||
| 	<a-typography-title style="text-align: center"> | 	<a-typography-title style="text-align: center"> Editing "{{ originalStory?.title }}" </a-typography-title> | ||||||
| 		Editing "{{ originalStory?.title }}" | 	<story-form :can-draft="false" :data="story" :endpoint="`/story/${rtr.params.id}`" endpoint-method="put"> </story-form> | ||||||
| 	</a-typography-title> |  | ||||||
| 	<story-form |  | ||||||
| 		:can-draft="false" |  | ||||||
| 		:data="story" |  | ||||||
| 		:endpoint="`/story/${rtr.params.id}`" |  | ||||||
| 		endpoint-method="put" |  | ||||||
| 	> |  | ||||||
| 	</story-form> |  | ||||||
| </template> | </template> | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| 	import { format, formatDistanceToNow, formatRelative } from "date-fns"; | 	import { format, formatDistanceToNow } from "date-fns"; | ||||||
| 	import { IUser } from "@models/user"; | 	import { IUser } from "@models/user"; | ||||||
| 	import { IStory } from "@models/stories"; |  | ||||||
| 	import { favourites } from "@client/listActions"; | 	import { favourites } from "@client/listActions"; | ||||||
| 	import singleStory from "~/components/listings/singleStory.vue"; | 	import singleStory from "~/components/listings/singleStory.vue"; | ||||||
| 	import icon from "~/components/icon.vue"; | 	import icon from "~/components/icon.vue"; | ||||||
| @ -16,10 +15,7 @@ | |||||||
| 	} | 	} | ||||||
| 	const activeKey = ref("main"); | 	const activeKey = ref("main"); | ||||||
| 	const activeFavKey = ref("favs/stories"); | 	const activeFavKey = ref("favs/stories"); | ||||||
| 	const uLastVisit = Date.parse( | 	const uLastVisit = Date.parse((userInfo.value?.lastVisit || userInfo.value?.lastLogin) as unknown as string); | ||||||
| 		(userInfo.value?.lastVisit || |  | ||||||
| 			userInfo.value?.lastLogin) as unknown as string, |  | ||||||
| 	); |  | ||||||
| 
 | 
 | ||||||
| 	const isSelf = userInfo.value?._id === parseInt(rtr.params.id as string); | 	const isSelf = userInfo.value?._id === parseInt(rtr.params.id as string); | ||||||
| 	useHead({ | 	useHead({ | ||||||
| @ -32,10 +28,7 @@ | |||||||
| 			<div style="height: fit-content"> | 			<div style="height: fit-content"> | ||||||
| 				<a-card-meta style="align-items: center; margin: 0.5em"> | 				<a-card-meta style="align-items: center; margin: 0.5em"> | ||||||
| 					<template #avatar> | 					<template #avatar> | ||||||
| 						<a-avatar | 						<a-avatar :size="75" :src="`/avatars/${userInfo?.profile.avatar}.png`" /> | ||||||
| 							:size="75" |  | ||||||
| 							:src="`/avatars/${userInfo?.profile.avatar}.png`" |  | ||||||
| 						/> |  | ||||||
| 					</template> | 					</template> | ||||||
| 					<template #title> | 					<template #title> | ||||||
| 						<a-typography-title :level="1" style="margin-top: 0.5em"> | 						<a-typography-title :level="1" style="margin-top: 0.5em"> | ||||||
| @ -48,15 +41,9 @@ | |||||||
| 				<template #text> Administrator </template> | 				<template #text> Administrator </template> | ||||||
| 			</a-badge-ribbon> | 			</a-badge-ribbon> | ||||||
| 		</template> | 		</template> | ||||||
| 		<a-descriptions | 		<a-descriptions :column="2" :labelStyle="{ fontWeight: 'bold' }" :colon="false"> | ||||||
| 			:column="2" |  | ||||||
| 			:labelStyle="{ fontWeight: 'bold' }" |  | ||||||
| 			:colon="false" |  | ||||||
| 		> |  | ||||||
| 			<a-descriptions-item label="Last visit"> | 			<a-descriptions-item label="Last visit"> | ||||||
| 				<div v-if="userInfo?.banned" style="color: red; font-weight: bold"> | 				<div v-if="userInfo?.banned" style="color: red; font-weight: bold">BANNED</div> | ||||||
| 					BANNED |  | ||||||
| 				</div> |  | ||||||
| 				<div v-else-if="userInfo?.profile.hidden"> | 				<div v-else-if="userInfo?.profile.hidden"> | ||||||
| 					<i>Unknown</i> | 					<i>Unknown</i> | ||||||
| 				</div> | 				</div> | ||||||
| @ -64,38 +51,23 @@ | |||||||
| 					<span> | 					<span> | ||||||
| 						{{ format(uLastVisit, "MM/dd/yyyy @ hh:mm:ss a") }} | 						{{ format(uLastVisit, "MM/dd/yyyy @ hh:mm:ss a") }} | ||||||
| 					</span> | 					</span> | ||||||
| 					<span style="margin-left: 0.6em"> | 					<span style="margin-left: 0.6em"> ({{ formatDistanceToNow(uLastVisit, { addSuffix: true }) }}) </span> | ||||||
| 						({{ formatDistanceToNow(uLastVisit, { addSuffix: true }) }}) |  | ||||||
| 					</span> |  | ||||||
| 				</div> | 				</div> | ||||||
| 			</a-descriptions-item> | 			</a-descriptions-item> | ||||||
| 			<a-descriptions-item label="Website"> | 			<a-descriptions-item label="Website"> | ||||||
| 				<a | 				<a target="_blank" :href="`${userInfo?.profile.website.startsWith('http') ? '' : 'http://'}${userInfo?.profile.website}`"> | ||||||
| 					target="_blank" |  | ||||||
| 					:href="`${ |  | ||||||
| 						userInfo?.profile.website.startsWith('http') ? '' : 'http://' |  | ||||||
| 					}${userInfo?.profile.website}`" |  | ||||||
| 				> |  | ||||||
| 					{{ userInfo?.profile.website }} | 					{{ userInfo?.profile.website }} | ||||||
| 				</a> | 				</a> | ||||||
| 			</a-descriptions-item> | 			</a-descriptions-item> | ||||||
| 			<a-descriptions-item label="Blog/Journal"> | 			<a-descriptions-item label="Blog/Journal"> | ||||||
| 				<a | 				<a target="_blank" :href="`${userInfo?.profile.blog.startsWith('http') ? '' : 'http://'}${userInfo?.profile.blog}`"> | ||||||
| 					target="_blank" |  | ||||||
| 					:href="`${ |  | ||||||
| 						userInfo?.profile.blog.startsWith('http') ? '' : 'http://' |  | ||||||
| 					}${userInfo?.profile.blog}`" |  | ||||||
| 				> |  | ||||||
| 					{{ userInfo?.profile.blog }} | 					{{ userInfo?.profile.blog }} | ||||||
| 				</a> | 				</a> | ||||||
| 			</a-descriptions-item> | 			</a-descriptions-item> | ||||||
| 			<a-descriptions-item label="Occupation"> | 			<a-descriptions-item label="Occupation"> | ||||||
| 				{{ userInfo?.profile.occupation }} | 				{{ userInfo?.profile.occupation }} | ||||||
| 			</a-descriptions-item> | 			</a-descriptions-item> | ||||||
| 			<a-descriptions-item | 			<a-descriptions-item label="Email" v-if="userInfo?.profile.showEmail || ses?.user?.profile.isAdmin"> | ||||||
| 				label="Email" |  | ||||||
| 				v-if="userInfo?.profile.showEmail || ses?.user?.profile.isAdmin" |  | ||||||
| 			> |  | ||||||
| 				{{ userInfo?.email }} | 				{{ userInfo?.email }} | ||||||
| 			</a-descriptions-item> | 			</a-descriptions-item> | ||||||
| 		</a-descriptions> | 		</a-descriptions> | ||||||
| @ -129,18 +101,7 @@ | |||||||
| 										<single-story :last="true" :story="item" /> | 										<single-story :last="true" :story="item" /> | ||||||
| 									</a-col> | 									</a-col> | ||||||
| 									<a-col :span="1" v-if="isSelf"> | 									<a-col :span="1" v-if="isSelf"> | ||||||
| 										<a | 										<a style="color: red" @click="() => favourites(userInfo?.favs.stories || [], item._id, true, 'story')"> | ||||||
| 											style="color: red" |  | ||||||
| 											@click=" |  | ||||||
| 												() => |  | ||||||
| 													favourites( |  | ||||||
| 														userInfo?.favs.stories || [], |  | ||||||
| 														item._id, |  | ||||||
| 														true, |  | ||||||
| 														'story', |  | ||||||
| 													) |  | ||||||
| 											" |  | ||||||
| 										> |  | ||||||
| 											<icon istyle="regular" name="trash" color="#f00" /> | 											<icon istyle="regular" name="trash" color="#f00" /> | ||||||
| 										</a> | 										</a> | ||||||
| 									</a-col> | 									</a-col> | ||||||
| @ -149,16 +110,10 @@ | |||||||
| 						</a-list> | 						</a-list> | ||||||
| 					</a-tab-pane> | 					</a-tab-pane> | ||||||
| 					<a-tab-pane key="favs/authors" tab="Authors"> | 					<a-tab-pane key="favs/authors" tab="Authors"> | ||||||
| 						<a-list | 						<a-list item-layout="horizontal" :data-source="userInfo?.favs.authors" :grid="{ gutter: 16, column: 3 }"> | ||||||
| 							item-layout="horizontal" |  | ||||||
| 							:data-source="userInfo?.favs.authors" |  | ||||||
| 							:grid="{ gutter: 16, column: 3 }" |  | ||||||
| 						> |  | ||||||
| 							<template #renderItem="{ item }"> | 							<template #renderItem="{ item }"> | ||||||
| 								<a-list-item style="display: flex; align-items: center"> | 								<a-list-item style="display: flex; align-items: center"> | ||||||
| 									<a-list-item-meta | 									<a-list-item-meta style="align-items: center; width: min-content"> | ||||||
| 										style="align-items: center; width: min-content" |  | ||||||
| 									> |  | ||||||
| 										<template #avatar> | 										<template #avatar> | ||||||
| 											<a-avatar :src="`/avatars/${item.profile.avatar}.png`" /> | 											<a-avatar :src="`/avatars/${item.profile.avatar}.png`" /> | ||||||
| 										</template> | 										</template> | ||||||
| @ -170,18 +125,7 @@ | |||||||
| 									</a-list-item-meta> | 									</a-list-item-meta> | ||||||
| 									<template #actions v-if="isSelf"> | 									<template #actions v-if="isSelf"> | ||||||
| 										<span> | 										<span> | ||||||
| 											<a | 											<a style="color: red" @click="() => favourites(userInfo?.favs.authors || [], item._id, true, 'author')"> | ||||||
| 												style="color: red" |  | ||||||
| 												@click=" |  | ||||||
| 													() => |  | ||||||
| 														favourites( |  | ||||||
| 															userInfo?.favs.authors || [], |  | ||||||
| 															item._id, |  | ||||||
| 															true, |  | ||||||
| 															'author', |  | ||||||
| 														) |  | ||||||
| 												" |  | ||||||
| 											> |  | ||||||
| 												<icon istyle="regular" name="trash" color="#f00" /> | 												<icon istyle="regular" name="trash" color="#f00" /> | ||||||
| 											</a> | 											</a> | ||||||
| 										</span> | 										</span> | ||||||
|  | |||||||
| @ -1,5 +1,3 @@ | |||||||
| import mongoose from "mongoose"; |  | ||||||
| import * as net from "net"; |  | ||||||
| import plugnplay from "@server/plugnplay"; | import plugnplay from "@server/plugnplay"; | ||||||
| export default defineNuxtPlugin({ | export default defineNuxtPlugin({ | ||||||
| 	name: "mongo", | 	name: "mongo", | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| import mongoose from "mongoose"; | import mongoose from "mongoose"; | ||||||
| import jwt from "jsonwebtoken"; | import jwt from "jsonwebtoken"; | ||||||
| import { IUser, User } from "@models/user"; | import { User } from "@models/user"; | ||||||
| import { log } from "@server/logger"; | import { log } from "@server/logger"; | ||||||
| 
 | 
 | ||||||
| export default eventHandler(async (event) => { | export default eventHandler(async (event) => { | ||||||
| @ -25,8 +25,7 @@ export default eventHandler(async (event) => { | |||||||
| 			if (!user.auth.emailVerified) { | 			if (!user.auth.emailVerified) { | ||||||
| 				throw createError({ | 				throw createError({ | ||||||
| 					statusCode: 401, | 					statusCode: 401, | ||||||
| 					message: | 					message: 'Account inactive!<br><a href="/activate/resend">Resend verification</a>?', | ||||||
| 						'Account inactive!<br><a href="/activate/resend">Resend verification</a>?', |  | ||||||
| 				}); | 				}); | ||||||
| 			} | 			} | ||||||
| 			let tok = user.generateJWT(useRuntimeConfig().jwt); | 			let tok = user.generateJWT(useRuntimeConfig().jwt); | ||||||
|  | |||||||
| @ -1,11 +1,5 @@ | |||||||
| import jwt from "jsonwebtoken"; |  | ||||||
| import { log } from "@server/logger"; |  | ||||||
| export default eventHandler((event) => { | export default eventHandler((event) => { | ||||||
| 	let ahead = ( | 	let ahead = (getHeaders(event).authorization || getCookie(event, "auth:token") || "")?.replace("Bearer ", ""); | ||||||
| 		getHeaders(event).authorization || |  | ||||||
| 		getCookie(event, "auth:token") || |  | ||||||
| 		"" |  | ||||||
| 	)?.replace("Bearer ", ""); |  | ||||||
| 	if (event.context.currentUser) { | 	if (event.context.currentUser) { | ||||||
| 		return { | 		return { | ||||||
| 			token: ahead, | 			token: ahead, | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import { messages } from "@server/constants"; | import { messages } from "@server/constants"; | ||||||
| import { isAdmin } from "@server/middlewareButNotReally"; | import { isAdmin } from "@server/middlewareButNotReally"; | ||||||
| import { isLoggedIn } from "@server/middlewareButNotReally"; |  | ||||||
| import { Band, IBand } from "@models/band"; | import { Band, IBand } from "@models/band"; | ||||||
| 
 | 
 | ||||||
| export default eventHandler(async (ev) => { | export default eventHandler(async (ev) => { | ||||||
|  | |||||||
| @ -1,6 +1,5 @@ | |||||||
| import { listQuerier } from "@server/dbHelpers"; | import { listQuerier } from "@server/dbHelpers"; | ||||||
| import { Band } from "@models/band"; | import { Band } from "@models/band"; | ||||||
| import { Story } from "@models/stories"; |  | ||||||
| 
 | 
 | ||||||
| export default eventHandler(async (event) => { | export default eventHandler(async (event) => { | ||||||
| 	const params = getRouterParams(event); | 	const params = getRouterParams(event); | ||||||
|  | |||||||
| @ -1,4 +1,3 @@ | |||||||
| import { usernameRegex } from "@server/constants"; |  | ||||||
| import { User } from "@models/user"; | import { User } from "@models/user"; | ||||||
| 
 | 
 | ||||||
| export default eventHandler(async (event) => { | export default eventHandler(async (event) => { | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| import san from "sanitize-html"; | import san from "sanitize-html"; | ||||||
| import { storyQuerier } from "@server/dbHelpers"; | import { storyQuerier } from "@server/dbHelpers"; | ||||||
| import { isLoggedIn } from "@server/middlewareButNotReally"; | import { isLoggedIn } from "@server/middlewareButNotReally"; | ||||||
| import { Story } from "@models/stories"; |  | ||||||
| import { Review } from "@models/stories/review"; | import { Review } from "@models/stories/review"; | ||||||
| 
 | 
 | ||||||
| export default eventHandler(async (ev) => { | export default eventHandler(async (ev) => { | ||||||
|  | |||||||
| @ -1,16 +1,10 @@ | |||||||
| import { Readable } from "stream"; |  | ||||||
| import { Document } from "mongoose"; | import { Document } from "mongoose"; | ||||||
| import { IStory, Story } from "@models/stories"; | import { IStory, Story } from "@models/stories"; | ||||||
| import { FormStory } from "@client/types/form/story"; | import { FormStory } from "@client/types/form/story"; | ||||||
| import { storyQuerier } from "@server/dbHelpers"; | import { storyQuerier } from "@server/dbHelpers"; | ||||||
| import { isLoggedIn } from "@server/middlewareButNotReally"; | import { isLoggedIn } from "@server/middlewareButNotReally"; | ||||||
| import { canModify } from "@server/middlewareButNotReally/storyPrivileges"; | import { canModify } from "@server/middlewareButNotReally/storyPrivileges"; | ||||||
| import { | import { bodyHandler, getBucket, modelFormChapter, replaceOrUploadContent } from "@server/storyHelpers"; | ||||||
| 	bodyHandler, |  | ||||||
| 	getBucket, |  | ||||||
| 	modelFormChapter, |  | ||||||
| 	replaceOrUploadContent, |  | ||||||
| } from "@server/storyHelpers"; |  | ||||||
| import { countWords } from "@functions"; | import { countWords } from "@functions"; | ||||||
| import { messages } from "@server/constants"; | import { messages } from "@server/constants"; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,10 +1,8 @@ | |||||||
| import { Readable } from "stream"; | import { Readable } from "stream"; | ||||||
| import san from "sanitize-html"; |  | ||||||
| import { FormStory } from "@client/types/form/story"; | import { FormStory } from "@client/types/form/story"; | ||||||
| import { isLoggedIn } from "@server/middlewareButNotReally"; | import { isLoggedIn } from "@server/middlewareButNotReally"; | ||||||
| import { getBucket, bodyHandler, modelFormChapter } from "@server/storyHelpers"; | import { getBucket, bodyHandler, modelFormChapter } from "@server/storyHelpers"; | ||||||
| import { Story } from "@models/stories"; | import { Story } from "@models/stories"; | ||||||
| import { sanitizeConf } from "@server/constants"; |  | ||||||
| import { countWords } from "@functions"; | import { countWords } from "@functions"; | ||||||
| 
 | 
 | ||||||
| export default eventHandler(async (ev) => { | export default eventHandler(async (ev) => { | ||||||
| @ -24,9 +22,7 @@ export default eventHandler(async (ev) => { | |||||||
| 	}); | 	}); | ||||||
| 	for (const c of body.chapters) { | 	for (const c of body.chapters) { | ||||||
| 		story.chapters.push(modelFormChapter(c)); | 		story.chapters.push(modelFormChapter(c)); | ||||||
| 		story.chapters[story.chapters.length - 1].words = countWords( | 		story.chapters[story.chapters.length - 1].words = countWords(await bodyHandler(c)); | ||||||
| 			await bodyHandler(c), |  | ||||||
| 		); |  | ||||||
| 	} | 	} | ||||||
| 	await story.save(); | 	await story.save(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,4 +1,4 @@ | |||||||
| import { FavPayload, SubPayload } from "@client/types/form/favSub"; | import { FavPayload } from "@client/types/form/favSub"; | ||||||
| import { isLoggedIn } from "@server/middlewareButNotReally"; | import { isLoggedIn } from "@server/middlewareButNotReally"; | ||||||
| import { User } from "@models/user"; | import { User } from "@models/user"; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| import san from "sanitize-html"; |  | ||||||
| import { weirdToNormalChars } from "weird-to-normal-chars"; | import { weirdToNormalChars } from "weird-to-normal-chars"; | ||||||
| import { Profile, MyStuff } from "@client/types/form/myStuff"; | import { MyStuff } from "@client/types/form/myStuff"; | ||||||
| import { apiRoot, messages } from "@server/constants"; | import { apiRoot } from "@server/constants"; | ||||||
| import { isLoggedIn } from "@server/middlewareButNotReally"; | import { isLoggedIn } from "@server/middlewareButNotReally"; | ||||||
| import { Review } from "@models/stories/review"; | import { Review } from "@models/stories/review"; | ||||||
| import { IUser, User } from "@models/user"; | import { IUser, User } from "@models/user"; | ||||||
| @ -51,14 +50,11 @@ export default eventHandler(async (ev) => { | |||||||
| 		if (exists) { | 		if (exists) { | ||||||
| 			throw createError(emsg("username")); | 			throw createError(emsg("username")); | ||||||
| 		} | 		} | ||||||
| 		let { data: lookup } = await axios.get( | 		let { data: lookup } = await axios.get(`${apiRoot}/session-sharing/lookup`, { | ||||||
| 			`${apiRoot}/session-sharing/lookup`, |  | ||||||
| 			{ |  | ||||||
| 			params: { | 			params: { | ||||||
| 				id: ev.context.currentUser!._id, | 				id: ev.context.currentUser!._id, | ||||||
| 			}, | 			}, | ||||||
| 			}, | 		}); | ||||||
| 		); |  | ||||||
| 
 | 
 | ||||||
| 		await axios.put(`${apiRoot}/v3/users/${lookup.value.uid}`, { | 		await axios.put(`${apiRoot}/v3/users/${lookup.value.uid}`, { | ||||||
| 			body: { | 			body: { | ||||||
|  | |||||||
| @ -1,4 +1,3 @@ | |||||||
| import mongoose from "mongoose"; |  | ||||||
| import { log } from "@server/logger"; | import { log } from "@server/logger"; | ||||||
| import plugnplay from "@server/plugnplay"; | import plugnplay from "@server/plugnplay"; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,23 +1,26 @@ | |||||||
| import jwt from "jsonwebtoken"; | import jwt from "jsonwebtoken"; | ||||||
| import { log } from "@server/logger"; |  | ||||||
| import { User } from "@models/user"; | import { User } from "@models/user"; | ||||||
|  | import { messages } from "@server/constants"; | ||||||
|  | import { AccessToken } from "@models/oauth"; | ||||||
| 
 | 
 | ||||||
| export default defineEventHandler(async (event) => { | export default defineEventHandler(async (event) => { | ||||||
| 	let ahead = ( | 	let ahead = (getHeaders(event).authorization || getCookie(event, "auth:token") || "")?.replace("Bearer ", ""); | ||||||
| 		getHeaders(event).authorization || |  | ||||||
| 		getCookie(event, "auth:token") || |  | ||||||
| 		"" |  | ||||||
| 	)?.replace("Bearer ", ""); |  | ||||||
| 	// console.log("in here fucknuts", ahead);
 |  | ||||||
| 	// log.debug(`'${ahead}'`, { label: "idk" });
 |  | ||||||
| 	if (ahead) { | 	if (ahead) { | ||||||
| 		let toktok = jwt.verify( | 		let toktok: jwt.JwtPayload; | ||||||
| 			ahead, | 		try { | ||||||
| 			// ahead.replace("Bearer ", ""),
 | 			toktok = jwt.verify(ahead, useRuntimeConfig().jwt) as jwt.JwtPayload; | ||||||
| 			useRuntimeConfig().jwt, |  | ||||||
| 		) as jwt.JwtPayload; |  | ||||||
| 			let user = await User.findById(toktok.id as number).exec(); | 			let user = await User.findById(toktok.id as number).exec(); | ||||||
| 			if (user && toktok) event.context.currentUser = user; | 			if (user && toktok) event.context.currentUser = user; | ||||||
| 		// setCookie(event, "auth:token", ahead)
 | 		} catch (e) { | ||||||
|  | 			const t = await AccessToken.findOne({ token: ahead }); | ||||||
|  | 			if (!t) | ||||||
|  | 				throw createError({ | ||||||
|  | 					statusCode: 401, | ||||||
|  | 					message: messages[401], | ||||||
|  | 				}); | ||||||
|  | 			let user = await User.findById(t.userID); | ||||||
|  | 			if (user) event.context.currentUser = user; | ||||||
|  | 			// else throw createError({statusCode: 401, message: messages[401]})
 | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								typings/express.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								typings/express.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,3 @@ | |||||||
| import { Document } from "mongoose"; |  | ||||||
| import { IStory } from "@models/stories"; | import { IStory } from "@models/stories"; | ||||||
| import { IUser } from "@models/user"; | import { IUser } from "@models/user"; | ||||||
| import { Request } from "express"; | import { Request } from "express"; | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								typings/h3.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								typings/h3.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -1,4 +1,3 @@ | |||||||
| import type { H3Event, H3EventContext } from "h3"; |  | ||||||
| import { IFicmas } from "@models/challenges/ficmas"; | import { IFicmas } from "@models/challenges/ficmas"; | ||||||
| import { IUser } from "@models/user"; | import { IUser } from "@models/user"; | ||||||
| declare module "h3" { | declare module "h3" { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user