style(*): edit indents
This commit is contained in:
		
							parent
							
								
									c8e84c909e
								
							
						
					
					
						commit
						8f45dbe563
					
				| @ -1,6 +1,7 @@ | ||||
| [*.*] | ||||
| root = true | ||||
| [*] | ||||
| tab_width = 2 | ||||
| indent_size = 1 | ||||
| indent_size = 2 | ||||
| indent_style = tab | ||||
| charset = utf-8 | ||||
| max_line_length = 160 | ||||
| @ -2,25 +2,17 @@ | ||||
| <template> | ||||
| 	<div> | ||||
| 		<p> | ||||
| 			© Rockfic.com, since 2004. Rockfic.com is in no way associated with any | ||||
| 			band listed on this website. Rockfic.com is entertainment. All stories | ||||
| 			contained on this site are fictional, which means that while the | ||||
| 			characters may be loosely based on the public personas of real people, the | ||||
| 			stories themselves are completely ungrounded from reality and are in no | ||||
| 			way meant to reflect the private lives, actual practices, or activities of | ||||
| 			any persons named. Rockfic.com will remove a work of fiction if an | ||||
| 			individual named within requests its removal.<br /> | ||||
| 			© Rockfic.com, since 2004. Rockfic.com is in no way associated with any band listed on this website. Rockfic.com is entertainment. All stories contained | ||||
| 			on this site are fictional, which means that while the characters may be loosely based on the public personas of real people, the stories themselves are | ||||
| 			completely ungrounded from reality and are in no way meant to reflect the private lives, actual practices, or activities of any persons named. Rockfic.com | ||||
| 			will remove a work of fiction if an individual named within requests its removal.<br /> | ||||
| 			For site problems and/or bugs, contact | ||||
| 			<a style="font-weight: bold" href="mailto:bugs@rockfic.com" | ||||
| 				>bugs@rockfic.com</a | ||||
| 			>.<br /> | ||||
| 			<a style="font-weight: bold" href="mailto:bugs@rockfic.com">bugs@rockfic.com</a>.<br /> | ||||
| 			For everything else, contact | ||||
| 			<a href="mailto:admin@rockfic.com">admin@rockfic.com</a>. | ||||
| 		</p> | ||||
| 		<b>Copyright Notice</b><br /> | ||||
| 		All content on this site is copyright of its respective author. You may not, | ||||
| 		except with our express written permission, distribute or commercially | ||||
| 		exploit the content. Nor may you transmit it or store it in any other | ||||
| 		website or other form of electronic retrieval system. | ||||
| 		All content on this site is copyright of its respective author. You may not, except with our express written permission, distribute or commercially exploit | ||||
| 		the content. Nor may you transmit it or store it in any other website or other form of electronic retrieval system. | ||||
| 	</div> | ||||
| </template> | ||||
|  | ||||
| @ -30,11 +30,7 @@ | ||||
| 				promote: !short, | ||||
| 			}, | ||||
| 		}); | ||||
| 		messageApi.success( | ||||
| 			`User ${props.user?.username} is now ${ | ||||
| 				short ? "an admin" : "a regular user" | ||||
| 			}.`, | ||||
| 		); | ||||
| 		messageApi.success(`User ${props.user?.username} is now ${short ? "an admin" : "a regular user"}.`); | ||||
| 		setTimeout(() => { | ||||
| 			showDemote.value = false; | ||||
| 		}, 1000); | ||||
| @ -44,26 +40,15 @@ | ||||
| <template> | ||||
| 	<a-space :size="10" direction="vertical"> | ||||
| 		<div> | ||||
| 			<a-descriptions | ||||
| 				:colon="false" | ||||
| 				:label-style="{ fontWeight: 'bold' }" | ||||
| 				:column="1" | ||||
| 			> | ||||
| 			<a-descriptions :colon="false" :label-style="{ fontWeight: 'bold' }" :column="1"> | ||||
| 				<a-descriptions-item label="IP addresses"> | ||||
| 					<a-list :data-source="user?.ipLog"> | ||||
| 						<template #renderItem="{ item }"> | ||||
| 							{{ item.ip }}<br /> | ||||
| 							<a-typography-title :level="5" | ||||
| 								>Other users with this IP:</a-typography-title | ||||
| 							> | ||||
| 							<a-typography-title :level="5">Other users with this IP:</a-typography-title> | ||||
| 							<div v-if="commonIps != null"> | ||||
| 								<i v-if="!commonIps[item.ip]?.length"> | ||||
| 									No other users share this IP. | ||||
| 								</i> | ||||
| 								<a-list | ||||
| 									v-else | ||||
| 									:data-source="!!commonIps ? commonIps[item.ip] : []" | ||||
| 								> | ||||
| 								<i v-if="!commonIps[item.ip]?.length"> No other users share this IP. </i> | ||||
| 								<a-list v-else :data-source="!!commonIps ? commonIps[item.ip] : []"> | ||||
| 									<template #renderItem="{ item: otherItem }"> | ||||
| 										<nuxt-link :to="`/user/${otherItem._id}`"> | ||||
| 											{{ otherItem.username }} | ||||
| @ -83,16 +68,10 @@ | ||||
| 					<b>{{ user?.profile.isAdmin ? "an admin" : "a regular user" }}</b | ||||
| 					>. | ||||
| 				</span> | ||||
| 				<a-button | ||||
| 					danger | ||||
| 					v-if="!user?.profile.isAdmin" | ||||
| 					@click="() => (showDemote = true)" | ||||
| 				> | ||||
| 				<a-button danger v-if="!user?.profile.isAdmin" @click="() => (showDemote = true)"> | ||||
| 					<b>Promote to Admin</b> | ||||
| 				</a-button> | ||||
| 				<a-button v-else @click="() => (showDemote = true)"> | ||||
| 					Demote to regular user | ||||
| 				</a-button> | ||||
| 				<a-button v-else @click="() => (showDemote = true)"> Demote to regular user </a-button> | ||||
| 			</a-space> | ||||
| 			<a-divider /> | ||||
| 			<div style="display: flex"> | ||||
| @ -110,8 +89,7 @@ | ||||
| 		v-model:open="showBanUnban" | ||||
| 		:title="`${user?.banned ? 'Unban' : 'Ban'} ${user?.username}`" | ||||
| 	> | ||||
| 		Are you sure you want to {{ `${user?.banned ? "unban" : "ban"}` }} | ||||
| 		{{ user?.username }}? | ||||
| 		Are you sure you want to {{ `${user?.banned ? "unban" : "ban"}` }} {{ user?.username }}? | ||||
| 	</a-modal> | ||||
| 	<a-modal | ||||
| 		cancel-text="No" | ||||
| @ -119,20 +97,13 @@ | ||||
| 		@ok="prodem" | ||||
| 		@cancel="() => (showDemote = false)" | ||||
| 		v-model:open="showDemote" | ||||
| 		:title="`${short ? 'Demoting' : 'Promoting'} ${user?.username} ${ | ||||
| 			!short ? 'to an administrator' : 'to a regular user' | ||||
| 		}`" | ||||
| 		:title="`${short ? 'Demoting' : 'Promoting'} ${user?.username} ${!short ? 'to an administrator' : 'to a regular user'}`" | ||||
| 	> | ||||
| 		<div v-if="!short"> | ||||
| 			Are you <b><u>absolutely sure</u></b> you want to | ||||
| 			<b>promote this user to an admin</b>? | ||||
| 			Are you <b><u>absolutely sure</u></b> you want to <b>promote this user to an admin</b>? | ||||
| 			<br /> | ||||
| 			<a-typography-title :level="5"> | ||||
| 				This is a VERY dangerous permission to grant. | ||||
| 			</a-typography-title> | ||||
| 		</div> | ||||
| 		<div v-else> | ||||
| 			Are you sure you want to remove this user as an administrator? | ||||
| 			<a-typography-title :level="5"> This is a VERY dangerous permission to grant. </a-typography-title> | ||||
| 		</div> | ||||
| 		<div v-else>Are you sure you want to remove this user as an administrator?</div> | ||||
| 	</a-modal> | ||||
| </template> | ||||
|  | ||||
| @ -4,9 +4,7 @@ | ||||
| 	import { SingleChapterResult } from "@client/types/slightlyDifferentStory"; | ||||
| 	const props = defineProps<{ endpoint: string }>(); | ||||
| 	const story = inject<SingleChapterResult>("story"); | ||||
| 	const { data: reviews } = (await useApiFetch<IReview[]>( | ||||
| 		`${props.endpoint}/reviews`, | ||||
| 	)) as unknown as { | ||||
| 	const { data: reviews } = (await useApiFetch<IReview[]>(`${props.endpoint}/reviews`)) as unknown as { | ||||
| 		data: IReview[]; | ||||
| 	}; | ||||
| </script> | ||||
|  | ||||
| @ -12,20 +12,12 @@ | ||||
| 		}); | ||||
| 		return unflattened.flat(Infinity).map((a) => ({ value: a, label: a })); | ||||
| 	}); | ||||
| 	const charField = useField<string[]>( | ||||
| 		fname + "characters", | ||||
| 		cs.fields.characters as unknown as MaybeRef<RuleExpression<string[]>>, | ||||
| 	); | ||||
| 	const charField = useField<string[]>(fname + "characters", cs.fields.characters as unknown as MaybeRef<RuleExpression<string[]>>); | ||||
| 	const { value, errorMessage, name: bandName, setValue } = charField; | ||||
| 	// setValue([]); | ||||
| </script> | ||||
| <template> | ||||
| 	<a-form-item | ||||
| 		:help="errorMessage" | ||||
| 		label="Characters" | ||||
| 		:name="bandName as string" | ||||
| 		:validate-status="!!errorMessage ? 'error' : undefined" | ||||
| 	> | ||||
| 	<a-form-item :help="errorMessage" label="Characters" :name="bandName as string" :validate-status="!!errorMessage ? 'error' : undefined"> | ||||
| 		<a-select mode="multiple" :options="opts" v-model:value="value"> | ||||
| 			<template #removeIcon> | ||||
| 				<i class="far fa-circle-x" /> | ||||
|  | ||||
| @ -6,22 +6,11 @@ | ||||
| 		value: a, | ||||
| 		label: a, | ||||
| 	})); | ||||
| 	const { value, errorMessage, name, setValue } = useField<string[]>( | ||||
| 		fname + "genre", | ||||
| 	); | ||||
| 	const { value, errorMessage, name, setValue } = useField<string[]>(fname + "genre"); | ||||
| </script> | ||||
| <template> | ||||
| 	<a-form-item | ||||
| 		:help="errorMessage" | ||||
| 		label="Genre(s)" | ||||
| 		:validate-status="!!errorMessage ? 'error' : undefined" | ||||
| 	> | ||||
| 		<a-select | ||||
| 			:allow-clear="true" | ||||
| 			:options="opts" | ||||
| 			v-model:value="value" | ||||
| 			mode="multiple" | ||||
| 		> | ||||
| 	<a-form-item :help="errorMessage" label="Genre(s)" :validate-status="!!errorMessage ? 'error' : undefined"> | ||||
| 		<a-select :allow-clear="true" :options="opts" v-model:value="value" mode="multiple"> | ||||
| 			<template #removeIcon> | ||||
| 				<i class="far fa-circle-x" /> | ||||
| 			</template> | ||||
|  | ||||
| @ -12,26 +12,14 @@ | ||||
| 		}); | ||||
| 		return uf.flat(Infinity).map((a) => ({ value: a, label: a })); | ||||
| 	}); | ||||
| 	const { fields, push, remove, replace, update } = useFieldArray<string[]>( | ||||
| 		fname + "relationships", | ||||
| 	); | ||||
| 	const { fields, push, remove, replace, update } = useFieldArray<string[]>(fname + "relationships"); | ||||
| 	// replace([]); | ||||
| </script> | ||||
| <template> | ||||
| 	<a-form-item label="Pairings"> | ||||
| 		<a-row | ||||
| 			:gutter="5" | ||||
| 			:wrap="true" | ||||
| 			v-for="(field, idx) in fields" | ||||
| 			:key="field.key" | ||||
| 		> | ||||
| 		<a-row :gutter="5" :wrap="true" v-for="(field, idx) in fields" :key="field.key"> | ||||
| 			<Field :name="fname + 'relationships' + `[${idx}]`"> | ||||
| 				<a-select | ||||
| 					mode="multiple" | ||||
| 					:options="opts" | ||||
| 					v-model:value="field.value as string[]" | ||||
| 					@change="(val) => update(idx, val as string[])" | ||||
| 				> | ||||
| 				<a-select mode="multiple" :options="opts" v-model:value="field.value as string[]" @change="(val) => update(idx, val as string[])"> | ||||
| 					<template #removeIcon> | ||||
| 						<i class="far fa-circle-x" /> | ||||
| 					</template> | ||||
|  | ||||
| @ -3,9 +3,7 @@ | ||||
| 		mode="multiple" | ||||
| 		style="width: 100%" | ||||
| 		placeholder="Please select" | ||||
| 		:options=" | ||||
| 			[...Array(25)].map((_, i) => ({ value: (i + 10).toString(36) + (i + 1) })) | ||||
| 		" | ||||
| 		:options="[...Array(25)].map((_, i) => ({ value: (i + 10).toString(36) + (i + 1) }))" | ||||
| 		@change="handleChange" | ||||
| 	></a-select> | ||||
| </template> | ||||
|  | ||||
| @ -8,77 +8,45 @@ | ||||
| </script> | ||||
| <template> | ||||
| 	<a-card style="width: 45%; float: left; margin-right: 1.2em" v-if="!!story"> | ||||
| 		<a-descriptions | ||||
| 			:label-style="{ fontWeight: 'bold' }" | ||||
| 			:colon="false" | ||||
| 			:column="1" | ||||
| 		> | ||||
| 		<a-descriptions :label-style="{ fontWeight: 'bold' }" :colon="false" :column="1"> | ||||
| 			<a-descriptions-item label="Author"> | ||||
| 				<nuxt-link :to="`/user/${story?.author._id}`">{{ | ||||
| 					story?.author.username | ||||
| 				}}</nuxt-link> | ||||
| 				<nuxt-link :to="`/user/${story?.author._id}`">{{ story?.author.username }}</nuxt-link> | ||||
| 			</a-descriptions-item> | ||||
| 			<a-descriptions-item label="Bands"> | ||||
| 				<div | ||||
| 					class="wrapLong" | ||||
| 					v-for="(item, index) in story?.currentChapter.bands" | ||||
| 				> | ||||
| 				<div class="wrapLong" v-for="(item, index) in story?.currentChapter.bands"> | ||||
| 					<span> | ||||
| 						<nuxt-link :to="`/band/${item._id}`"> | ||||
| 							{{ item.name }} | ||||
| 						</nuxt-link> | ||||
| 						{{ | ||||
| 							(index < story!.currentChapter?.bands.length - 1 && ", ") || | ||||
| 							"" | ||||
| 						}} | ||||
| 						{{ (index < story!.currentChapter?.bands.length - 1 && ", ") || "" }} | ||||
| 					</span> | ||||
| 				</div> | ||||
| 			</a-descriptions-item> | ||||
| 			<a-descriptions-item label="Genre(s)"> | ||||
| 				<div | ||||
| 					class="wrapLong" | ||||
| 					v-for="(item, index) in story?.currentChapter.genre" | ||||
| 				> | ||||
| 				<div class="wrapLong" v-for="(item, index) in story?.currentChapter.genre"> | ||||
| 					<span> | ||||
| 						{{ item }} | ||||
| 						{{ | ||||
| 							(index < story!.currentChapter?.genre.length - 1 && ", ") || | ||||
| 							"" | ||||
| 						}} | ||||
| 						{{ (index < story!.currentChapter?.genre.length - 1 && ", ") || "" }} | ||||
| 					</span> | ||||
| 				</div> | ||||
| 			</a-descriptions-item> | ||||
| 			<a-descriptions-item label="Relationship(s)"> | ||||
| 				<div | ||||
| 					class="wrapLong" | ||||
| 					v-for="(item, index) in story?.currentChapter.relationships" | ||||
| 				> | ||||
| 				<div class="wrapLong" v-for="(item, index) in story?.currentChapter.relationships"> | ||||
| 					<span> | ||||
| 						{{ item.join("/") }} | ||||
| 						{{ | ||||
| 							(index < story!.currentChapter?.relationships.length - 1 && | ||||
| 								", ") || | ||||
| 							"" | ||||
| 						}} | ||||
| 						{{ (index < story!.currentChapter?.relationships.length - 1 && ", ") || "" }} | ||||
| 					</span> | ||||
| 				</div> | ||||
| 			</a-descriptions-item> | ||||
| 			<a-descriptions-item label="Character(s)"> | ||||
| 				<div class="wrapLong"> | ||||
| 					<span v-for="(item, index) in story?.currentChapter.characters"> | ||||
| 						{{ item | ||||
| 						}}{{ | ||||
| 							(index < story!.currentChapter?.characters.length - 1 && | ||||
| 								", ") || | ||||
| 							"" | ||||
| 						}} | ||||
| 						{{ item }}{{ (index < story!.currentChapter?.characters.length - 1 && ", ") || "" }} | ||||
| 					</span> | ||||
| 				</div> | ||||
| 			</a-descriptions-item> | ||||
| 			<a-descriptions-item label="Rating"> | ||||
| 				{{ | ||||
| 					story?.currentChapter.nsfw ? "Adult" : "Suitable for most audiences" | ||||
| 				}} | ||||
| 				{{ story?.currentChapter.nsfw ? "Adult" : "Suitable for most audiences" }} | ||||
| 			</a-descriptions-item> | ||||
| 			<a-descriptions-item label="Summary"> | ||||
| 				<div v-html="story?.currentChapter.summary"></div> | ||||
| @ -86,19 +54,9 @@ | ||||
| 			<a-descriptions-item label="Date posted"> | ||||
| 				<a-tooltip> | ||||
| 					<template #title> | ||||
| 						{{ | ||||
| 							format( | ||||
| 								Date.parse(story?.currentChapter.posted as unknown as string), | ||||
| 								"EEEE, LLL dd yyyy @ hh:mm:ss.SSS aa", | ||||
| 							) | ||||
| 						}} | ||||
| 						{{ format(Date.parse(story?.currentChapter.posted as unknown as string), "EEEE, LLL dd yyyy @ hh:mm:ss.SSS aa") }} | ||||
| 					</template> | ||||
| 					{{ | ||||
| 						format( | ||||
| 							Date.parse(story?.currentChapter.posted as unknown as string), | ||||
| 							"yyyy-MM-dd", | ||||
| 						) | ||||
| 					}} | ||||
| 					{{ format(Date.parse(story?.currentChapter.posted as unknown as string), "yyyy-MM-dd") }} | ||||
| 				</a-tooltip> | ||||
| 			</a-descriptions-item> | ||||
| 		</a-descriptions> | ||||
| @ -106,12 +64,7 @@ | ||||
| 		<div class="stats"> | ||||
| 			<span> | ||||
| 				<span class="staticon"> | ||||
| 					<icon | ||||
| 						:istyle="!dark ? 'solid' : 'regular'" | ||||
| 						icolor="#ff2883" | ||||
| 						:size="12" | ||||
| 						name="heart" | ||||
| 					/> | ||||
| 					<icon :istyle="!dark ? 'solid' : 'regular'" icolor="#ff2883" :size="12" name="heart" /> | ||||
| 				</span> | ||||
| 				<span> | ||||
| 					{{ story.favs }} | ||||
| @ -119,12 +72,7 @@ | ||||
| 			</span> | ||||
| 			<span> | ||||
| 				<span class="staticon"> | ||||
| 					<icon | ||||
| 						:istyle="!dark ? 'solid' : 'regular'" | ||||
| 						icolor="#1787d7" | ||||
| 						:size="12" | ||||
| 						name="book-open" | ||||
| 					/> | ||||
| 					<icon :istyle="!dark ? 'solid' : 'regular'" icolor="#1787d7" :size="12" name="book-open" /> | ||||
| 				</span> | ||||
| 				<span> | ||||
| 					{{ story.views }} | ||||
| @ -132,12 +80,7 @@ | ||||
| 			</span> | ||||
| 			<span> | ||||
| 				<span class="staticon"> | ||||
| 					<icon | ||||
| 						:istyle="!dark ? 'solid' : 'regular'" | ||||
| 						icolor="#51e07c" | ||||
| 						:size="12" | ||||
| 						name="thumbs-up" | ||||
| 					/> | ||||
| 					<icon :istyle="!dark ? 'solid' : 'regular'" icolor="#51e07c" :size="12" name="thumbs-up" /> | ||||
| 				</span> | ||||
| 				<span> | ||||
| 					{{ story.recs }} | ||||
| @ -145,12 +88,7 @@ | ||||
| 			</span> | ||||
| 			<span> | ||||
| 				<span class="staticon"> | ||||
| 					<icon | ||||
| 						:istyle="!dark ? 'solid' : 'regular'" | ||||
| 						icolor="#c2d420" | ||||
| 						:size="12" | ||||
| 						name="download" | ||||
| 					/> | ||||
| 					<icon :istyle="!dark ? 'solid' : 'regular'" icolor="#c2d420" :size="12" name="download" /> | ||||
| 				</span> | ||||
| 				<span> | ||||
| 					{{ story.downloads }} | ||||
|  | ||||
| @ -2,95 +2,59 @@ | ||||
| <template> | ||||
| 	<div> | ||||
| 		<h3>Age Policy</h3> | ||||
| 		You must be 18 years of age or older to have an account. If you are found to | ||||
| 		be underage, your account will be suspended without notice. | ||||
| 		You must be 18 years of age or older to have an account. If you are found to be underage, your account will be suspended without notice. | ||||
| 		<h3>General</h3> | ||||
| 		<ol> | ||||
| 			<li>Rockfic.com is not affiliated with any band listed on the site;</li> | ||||
| 			<li> | ||||
| 				All stories are fictional and for entertainment purposes only, which | ||||
| 				means that while the characters may be loosely based on the public | ||||
| 				personas of real people, the stories are completely ungrounded from | ||||
| 				reality and are in no way meant to reflect the private lives, actual | ||||
| 				practices, or activities of any persons named; | ||||
| 				All stories are fictional and for entertainment purposes only, which means that while the characters may be loosely based on the public personas of real | ||||
| 				people, the stories are completely ungrounded from reality and are in no way meant to reflect the private lives, actual practices, or activities of any | ||||
| 				persons named; | ||||
| 			</li> | ||||
| 			<li>Rockfic.com will remove a work of fiction if an individual named within requests its removal;</li> | ||||
| 			<li>We do not sell, trade or otherwise disclose personal user information to any third party;</li> | ||||
| 			<li> | ||||
| 				Rockfic.com will remove a work of fiction if an individual named within | ||||
| 				requests its removal; | ||||
| 			</li> | ||||
| 			<li> | ||||
| 				We do not sell, trade or otherwise disclose personal user information to | ||||
| 				any third party; | ||||
| 			</li> | ||||
| 			<li> | ||||
| 				We reserve the right to access and disclose individually identifiable | ||||
| 				information to comply with any legal obligation or governmental request | ||||
| 				to enforce or apply our Terms of Use, or to ensure the constitutional | ||||
| 				rights and safety of Rockfic.com users. | ||||
| 				We reserve the right to access and disclose individually identifiable information to comply with any legal obligation or governmental request to enforce | ||||
| 				or apply our Terms of Use, or to ensure the constitutional rights and safety of Rockfic.com users. | ||||
| 			</li> | ||||
| 		</ol> | ||||
| 
 | ||||
| 		<h3>Content</h3> | ||||
| 		<ol> | ||||
| 			<li> | ||||
| 				Rockfic.com does not own any of the stories published on the site, nor | ||||
| 				are its administrators legally accountable for its content; | ||||
| 			</li> | ||||
| 			<li>Rockfic.com does not own any of the stories published on the site, nor are its administrators legally accountable for its content;</li> | ||||
| 			<li>Authors are the sole copyright owners of their stories;</li> | ||||
| 			<li>Authors are responsible for managing any content they create; this includes managing the privacy settings facilitated by the site;</li> | ||||
| 			<li> | ||||
| 				Authors are responsible for managing any content they create; this | ||||
| 				includes managing the privacy settings facilitated by the site; | ||||
| 				Rockfic.com reserves the right to remove content, including anything we consider offensive, inappropriate or defamatory, at our discretion; any content | ||||
| 				we decide to remove will be done in accordance with our Submission Rules; | ||||
| 			</li> | ||||
| 			<li>Rockfic.com reserves the right to edit content, in line with our Submission Rules.</li> | ||||
| 			<li> | ||||
| 				If you believe that you own the copyright in any of the content on Rockfic.com, and you have not been recognized as the copyright owner, please contact | ||||
| 				us and your case will be investigated; | ||||
| 			</li> | ||||
| 			<li> | ||||
| 				Rockfic.com reserves the right to remove content, including anything we | ||||
| 				consider offensive, inappropriate or defamatory, at our discretion; any | ||||
| 				content we decide to remove will be done in accordance with our | ||||
| 				Submission Rules; | ||||
| 			</li> | ||||
| 			<li> | ||||
| 				Rockfic.com reserves the right to edit content, in line with our | ||||
| 				Submission Rules. | ||||
| 			</li> | ||||
| 			<li> | ||||
| 				If you believe that you own the copyright in any of the content on | ||||
| 				Rockfic.com, and you have not been recognized as the copyright owner, | ||||
| 				please contact us and your case will be investigated; | ||||
| 			</li> | ||||
| 			<li> | ||||
| 				While we investigate reports of inappropriate content or copyright | ||||
| 				issues, we may temporarily remove the content in question; if we agree | ||||
| 				that you are the copyright owner or that the content is inappropriate, | ||||
| 				we will remove the relevant content permanently. | ||||
| 				While we investigate reports of inappropriate content or copyright issues, we may temporarily remove the content in question; if we agree that you are | ||||
| 				the copyright owner or that the content is inappropriate, we will remove the relevant content permanently. | ||||
| 			</li> | ||||
| 		</ol> | ||||
| 
 | ||||
| 		<h3>Conduct</h3> | ||||
| 		<ol> | ||||
| 			<li> | ||||
| 				Rockfic.com operates a strict anti-bullying and anti-harassment policy; | ||||
| 				if you have an issue to report, contact us; | ||||
| 			</li> | ||||
| 			<li>Rockfic.com operates a strict anti-bullying and anti-harassment policy; if you have an issue to report, contact us;</li> | ||||
| 			<li> | ||||
| 				Rockfic.com will permanently ban users who: | ||||
| 				<ol style="list-style-type: lower-alpha"> | ||||
| 					<li> | ||||
| 						break the law, for example by saying something libellous, or by | ||||
| 						posting something which results in a criminal offence; | ||||
| 					</li> | ||||
| 					<li>break the law, for example by saying something libellous, or by posting something which results in a criminal offence;</li> | ||||
| 					<li>share the personal details of users without their permission;</li> | ||||
| 					<li>impersonate another user;</li> | ||||
| 					<li> | ||||
| 						collect or use any information from Rockfic.com with the intent to | ||||
| 						harm, discredit or harass any other user; or | ||||
| 					</li> | ||||
| 					<li>collect or use any information from Rockfic.com with the intent to harm, discredit or harass any other user; or</li> | ||||
| 					<li>do anything which impacts the performance of the site.</li> | ||||
| 				</ol> | ||||
| 			</li> | ||||
| 		</ol> | ||||
| 		<h3>Copyright</h3> | ||||
| 		All content on this site is copyright of its respective author. You may not, | ||||
| 		except with our express written permission, distribute or commercially | ||||
| 		exploit the content. Nor may you transmit it or store it in any other | ||||
| 		website or other form of electronic retrieval system. | ||||
| 		All content on this site is copyright of its respective author. You may not, except with our express written permission, distribute or commercially exploit | ||||
| 		the content. Nor may you transmit it or store it in any other website or other form of electronic retrieval system. | ||||
| 	</div> | ||||
| </template> | ||||
|  | ||||
| @ -60,8 +60,7 @@ export const fancy = { | ||||
| 			items: "h1 h2 h3 h4 h5 h6", | ||||
| 		}, | ||||
| 	}, | ||||
| 	toolbar: | ||||
| 		"undo redo | paste | bold italic underline | hr image link | forecolor styles | heading alignment | code", | ||||
| 	toolbar: "undo redo | paste | bold italic underline | hr image link | forecolor styles | heading alignment | code", | ||||
| 	contextmenu: "bold italic underline | hr | link | image | paste", | ||||
| 	external_plugins: { | ||||
| 		mentions: "/plugins/mentions/plugin.min.js", | ||||
| @ -122,11 +121,7 @@ export const story = { | ||||
| 		`advlist autolink lists link image charmap preview anchor searchreplace visualblocks code fullscreen insertdatetime media table advcode help wordcount save`.split( | ||||
| 			" ", | ||||
| 		), | ||||
| 	toolbar: | ||||
| 		"undo redo | paste |" + | ||||
| 		"bold italic underline | hr | alignleft aligncenter " + | ||||
| 		"alignright alignjustify | " + | ||||
| 		"| code", | ||||
| 	toolbar: "undo redo | paste |" + "bold italic underline | hr | alignleft aligncenter " + "alignright alignjustify | " + "| code", | ||||
| 	contextmenu: "bold italic underline | hr | paste | link", | ||||
| }; | ||||
| export const bare = { | ||||
|  | ||||
| @ -3,12 +3,7 @@ import { FavPayload, HidePayload, SubPayload } from "./types/form/favSub"; | ||||
| 
 | ||||
| const base = `/user/me`; | ||||
| 
 | ||||
| export const favourites = ( | ||||
| 	values: (any & { _id: number })[], | ||||
| 	id: number, | ||||
| 	remove: boolean, | ||||
| 	type: "story" | "author", | ||||
| ) => { | ||||
| export const favourites = (values: (any & { _id: number })[], id: number, remove: boolean, type: "story" | "author") => { | ||||
| 	values?.splice( | ||||
| 		values!.findIndex((a) => a._id == id), | ||||
| 		1, | ||||
| @ -26,12 +21,7 @@ export const favourites = ( | ||||
| 	}); | ||||
| }; | ||||
| 
 | ||||
| export const subscriptions = ( | ||||
| 	values: (any & { _id: number })[], | ||||
| 	id: number, | ||||
| 	action: "hide" | "subscribe" | "unsubscribe", | ||||
| 	type: "bands" | "authors", | ||||
| ) => { | ||||
| export const subscriptions = (values: (any & { _id: number })[], id: number, action: "hide" | "subscribe" | "unsubscribe", type: "bands" | "authors") => { | ||||
| 	values?.splice( | ||||
| 		values!.findIndex((a) => a._id == id), | ||||
| 		1, | ||||
|  | ||||
| @ -3,15 +3,16 @@ import { IChapter } from "@models/stories/chapter"; | ||||
| import { IStory } from "@models/stories"; | ||||
| import { messages } from "@server/constants"; | ||||
| import { IUser } from "@models/user"; | ||||
| import { IDraft } from "@models/stories/draft"; | ||||
| 
 | ||||
| const show404 = () => showError({ statusCode: 404, message: messages[404] }); | ||||
| 
 | ||||
| export const storyMiddleware = defineNuxtRouteMiddleware(async (to, from) => { | ||||
| 	const { getSession } = useAuth(); | ||||
| 	await getSession({ force: true }); | ||||
| 	const { data } = useAuth(); | ||||
| 	console.log("to n from", to, from, data); | ||||
| 	const { data: story, error } = await useApiFetch<SingleChapterResult>( | ||||
| 		to.path, | ||||
| 	); | ||||
| 	const { data: story, error } = await useApiFetch<SingleChapterResult>(to.path); | ||||
| 	if (error.value) { | ||||
| 		return showError(error.value); | ||||
| 	} else if (!story.value) { | ||||
| @ -23,24 +24,27 @@ export const storyMiddleware = defineNuxtRouteMiddleware(async (to, from) => { | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| export const storyEditMiddleware = defineNuxtRouteMiddleware( | ||||
| 	async (to, from) => { | ||||
| 		const { data: curU } = useAuth(); | ||||
| 		const rtr = useRoute(); | ||||
| 		const { data: storyInfo } = await useApiFetch< | ||||
| 			({ chapters: (IChapter & { text: string })[] } & IStory) | null | ||||
| 		>(`/story/${rtr.params.id}/full`); | ||||
| 		if (!storyInfo.value) { | ||||
| 			return showError({ statusCode: 404, message: messages[404] }); | ||||
| 		} | ||||
| 		if ( | ||||
| 			curU.value?.user?._id !== (storyInfo.value?.author as IUser)._id && | ||||
| 			curU.value?.user?._id !== (storyInfo.value?.coAuthor as IUser)?._id | ||||
| 		) { | ||||
| 			return showError({ | ||||
| 				statusCode: 403, | ||||
| 				message: messages[403], | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
| ); | ||||
| export const storyEditMiddleware = defineNuxtRouteMiddleware(async (to, from) => { | ||||
| 	const { data: curU } = useAuth(); | ||||
| 	const rtr = useRoute(); | ||||
| 	const { data: storyInfo } = await useApiFetch<({ chapters: (IChapter & { text: string })[] } & IStory) | null>(`/story/${rtr.params.id}/full`); | ||||
| 	if (!storyInfo.value) show404(); | ||||
| 	if (curU.value?.user?._id !== (storyInfo.value?.author as IUser)._id && curU.value?.user?._id !== (storyInfo.value?.coAuthor as IUser)?._id) { | ||||
| 		return showError({ | ||||
| 			statusCode: 403, | ||||
| 			message: messages[403], | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
| export const draftEditMiddleware = defineNuxtRouteMiddleware(async (to, from) => { | ||||
| 	const { data: curU } = useAuth(); | ||||
| 	const rtr = useRoute(); | ||||
| 	const { data: storyInfo } = await useApiFetch<IDraft | null>(`/draft/${rtr.params.id}`); | ||||
| 	if (!storyInfo.value) show404(); | ||||
| 	if (curU.value?.user?._id !== (storyInfo.value?.author as IUser)._id && curU.value?.user?._id !== (storyInfo.value?.coAuthor as IUser)?._id) { | ||||
| 		return showError({ | ||||
| 			statusCode: 403, | ||||
| 			message: messages[403], | ||||
| 		}); | ||||
| 	} | ||||
| }); | ||||
|  | ||||
							
								
								
									
										3
									
								
								lib/client/types/form/draftInfo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								lib/client/types/form/draftInfo.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| export interface DraftInfo { | ||||
| 	id: number; | ||||
| } | ||||
| @ -1,4 +1,6 @@ | ||||
| import { V4Options, v4 } from "uuid"; | ||||
| import { v4 } from "uuid"; | ||||
| import { IChapter } from "@models/stories/chapter"; | ||||
| import { IBand } from "@models/band"; | ||||
| 
 | ||||
| export interface FormChapter { | ||||
| 	id?: number; | ||||
| @ -51,3 +53,21 @@ export const defaultStory: FormStory = { | ||||
| 	challenge: null, | ||||
| 	completed: false, | ||||
| }; | ||||
| 
 | ||||
| export function toFormChapter(chap: IChapter & { text: string }): FormChapter { | ||||
| 	return { | ||||
| 		chapterTitle: chap.title, | ||||
| 		index: 1, | ||||
| 		summary: chap.summary, | ||||
| 		notes: chap.notes, | ||||
| 		genre: chap.genre, | ||||
| 		bands: (chap.bands as IBand[]).map((a) => a._id), | ||||
| 		characters: chap.characters, | ||||
| 		relationships: chap.relationships, | ||||
| 		nsfw: chap.nsfw, | ||||
| 		loggedInOnly: chap.loggedInOnly, | ||||
| 		hidden: chap.hidden, | ||||
| 		content: chap.text, | ||||
| 		uuidKey: v4(), | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| @ -27,11 +27,7 @@ export const autoSave = async (values: any) => { | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| export const autoEdit = ( | ||||
| 	values: any, | ||||
| 	endpoint: string, | ||||
| 	method: "put" | "post", | ||||
| ) => { | ||||
| export const autoEdit = (values: any, endpoint: string, method: "put" | "post") => { | ||||
| 	const [messageApi, contextHolder] = message.useMessage(); | ||||
| 	useApiFetch<{ success: boolean; data: IStory }>(endpoint, { | ||||
| 		method, | ||||
|  | ||||
| @ -1,10 +1,8 @@ | ||||
| import turndown from "turndown"; | ||||
| export const ContentFilenameRegex = /\.(doc|docx|md|markdown)$/i; | ||||
| 
 | ||||
| export const emailRegex: RegExp = | ||||
| 	/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; | ||||
| export const usernameRegex: (uname: string) => RegExp = (uname: string) => | ||||
| 	new RegExp("^" + uname.trim().replace(/\*/g, "\\*") + "$", "i"); | ||||
| export const emailRegex: RegExp = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; | ||||
| export const usernameRegex: (uname: string) => RegExp = (uname: string) => new RegExp("^" + uname.trim().replace(/\*/g, "\\*") + "$", "i"); | ||||
| export const mammothTemplate = (doc, defaults, content) => { | ||||
| 	return content.replace(/\n|\r\n|\r/gm, ""); | ||||
| }; | ||||
| @ -90,17 +88,7 @@ export const sanitizeConf = { | ||||
| 		img: ["src"], | ||||
| 	}, | ||||
| 	// Lots of these won't come up by default because we don't allow them
 | ||||
| 	selfClosing: [ | ||||
| 		"img", | ||||
| 		"br", | ||||
| 		"hr", | ||||
| 		"area", | ||||
| 		"base", | ||||
| 		"basefont", | ||||
| 		"input", | ||||
| 		"link", | ||||
| 		"meta", | ||||
| 	], | ||||
| 	selfClosing: ["img", "br", "hr", "area", "base", "basefont", "input", "link", "meta"], | ||||
| 	// URL schemes we permit
 | ||||
| 	allowedSchemes: ["http", "https", "ftp", "mailto", "tel"], | ||||
| 	allowedSchemesAppliedToAttributes: ["href", "src", "cite"], | ||||
|  | ||||
							
								
								
									
										20
									
								
								lib/server/dbHelpers/draftHydrator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								lib/server/dbHelpers/draftHydrator.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| import { Document } from "mongoose"; | ||||
| import { IDraft } from "@models/stories/draft"; | ||||
| import { EventHandlerRequest, H3Event } from "h3"; | ||||
| import { IChapter } from "@models/stories/chapter"; | ||||
| import getDraftBucket from "@server/storyHelpers/getDraftBucket"; | ||||
| import { norm, stringifyStream } from "@functions"; | ||||
| 
 | ||||
| export interface HydratedDraft extends Omit<IDraft, "chapters"> { | ||||
| 	chapters: (IChapter & { text: string })[]; | ||||
| } | ||||
| 
 | ||||
| export default async function (draft: Document<number, {}, IDraft> & IDraft, event: H3Event<EventHandlerRequest>): Promise<HydratedDraft> { | ||||
| 	const finObj = draft.toObject() as HydratedDraft; | ||||
| 	const bucket = getDraftBucket(); | ||||
| 	for (let chap of finObj.chapters) { | ||||
| 		let dstream = bucket.openDownloadStreamByName(`/drafts/${chap.id}.txt`); | ||||
| 		chap.text = norm(await stringifyStream(dstream)); | ||||
| 	} | ||||
| 	return finObj; | ||||
| } | ||||
| @ -20,7 +20,6 @@ export default async function (ev: H3Event<EventHandlerRequest>) { | ||||
| 		}) | ||||
| 		.populate({ path: "challenge", model: Challenge }) | ||||
| 		.exec(); | ||||
| 	if (story == null) | ||||
| 		throw createError({ statusCode: 404, message: "Not found." }); | ||||
| 	if (story == null) throw createError({ statusCode: 404, message: "Not found." }); | ||||
| 	return story; | ||||
| } | ||||
|  | ||||
| @ -1,8 +1,4 @@ | ||||
| export const submissionsOpen = () => | ||||
| 	new Date() < new Date(Date.parse(`Dec 24 ${new Date().getFullYear()}`)) && | ||||
| 	Date.now() > Date.parse(`Nov 1 ${new Date().getFullYear()}`); | ||||
| export const ficsHidden = (d: number) => | ||||
| 	d < Date.parse(`Dec 25 ${new Date().getFullYear()}`); | ||||
| export const status = () => | ||||
| 	Date.now() > Date.parse(`Oct 1 ${new Date().getFullYear()}`) && | ||||
| 	Date.now() < Date.parse(`Nov 30 ${new Date().getFullYear()}`); | ||||
| 	new Date() < new Date(Date.parse(`Dec 24 ${new Date().getFullYear()}`)) && Date.now() > Date.parse(`Nov 1 ${new Date().getFullYear()}`); | ||||
| export const ficsHidden = (d: number) => d < Date.parse(`Dec 25 ${new Date().getFullYear()}`); | ||||
| export const status = () => Date.now() > Date.parse(`Oct 1 ${new Date().getFullYear()}`) && Date.now() < Date.parse(`Nov 30 ${new Date().getFullYear()}`); | ||||
|  | ||||
| @ -5,25 +5,13 @@ import { IDraft } from "@models/stories/draft"; | ||||
| import { IUser } from "@models/user"; | ||||
| export function canDelete(event: H3Event<EventHandlerRequest>, story: IStory) { | ||||
| 	isLoggedIn(event); | ||||
| 	return ( | ||||
| 		event.context.currentUser?.profile.isAdmin || | ||||
| 		(story.author as IUser)._id === event.context.currentUser?._id | ||||
| 	); | ||||
| 	return event.context.currentUser?.profile.isAdmin || (story.author as IUser)._id === event.context.currentUser?._id; | ||||
| } | ||||
| export function canDeleteDraft( | ||||
| 	event: H3Event<EventHandlerRequest>, | ||||
| 	story: IDraft, | ||||
| ) { | ||||
| export function canDeleteDraft(event: H3Event<EventHandlerRequest>, story: IDraft) { | ||||
| 	isLoggedIn(event); | ||||
| 	return story.author === event.context.currentUser?._id; | ||||
| } | ||||
| export function canModify( | ||||
| 	event: H3Event<EventHandlerRequest>, | ||||
| 	story: IStory | IDraft, | ||||
| ) { | ||||
| export function canModify(event: H3Event<EventHandlerRequest>, story: IStory | IDraft) { | ||||
| 	isLoggedIn(event); | ||||
| 	return ( | ||||
| 		event.context.currentUser?._id === (story.author as IUser)._id || | ||||
| 		(story.coAuthor as IUser)?._id === event.context.currentUser?._id | ||||
| 	); | ||||
| 	return event.context.currentUser?._id === (story.author as IUser)._id || (story.coAuthor as IUser)?._id === event.context.currentUser?._id; | ||||
| } | ||||
|  | ||||
| @ -14,17 +14,9 @@ export default async function (bodyObj: FormChapter): Promise<string> { | ||||
| 		str = bodyObj.content; | ||||
| 	} else if (bodyObj.file) { | ||||
| 		let ext = extname(bodyObj.file).toLowerCase(); | ||||
| 		if (ext === ".md" || ext === ".markdown") | ||||
| 			str = marked.parse( | ||||
| 				readFileSync(resolve(`tmp/${bodyObj.file}`)).toString(), | ||||
| 			); | ||||
| 		if (ext === ".md" || ext === ".markdown") str = marked.parse(readFileSync(resolve(`tmp/${bodyObj.file}`)).toString()); | ||||
| 		else if (ext === ".doc" || ext === ".docx") | ||||
| 			str = ( | ||||
| 				await mammoth.convertToHtml( | ||||
| 					{ path: resolve(`tmp/${bodyObj.file}`) }, | ||||
| 					{ styleMap: ["b => b", "i => i", "u => u"] }, | ||||
| 				) | ||||
| 			).value; | ||||
| 			str = (await mammoth.convertToHtml({ path: resolve(`tmp/${bodyObj.file}`) }, { styleMap: ["b => b", "i => i", "u => u"] })).value; | ||||
| 		else | ||||
| 			throw createError({ | ||||
| 				statusCode: 400, | ||||
|  | ||||
| @ -1,9 +1,6 @@ | ||||
| import getBucket from "./getBucket"; | ||||
| import { Readable } from "stream"; | ||||
| export default async function replaceGridFS( | ||||
| 	chapterID: number | undefined, | ||||
| 	content: string, | ||||
| ) { | ||||
| export default async function replaceGridFS(chapterID: number | undefined, content: string) { | ||||
| 	let filename = `/stories/${chapterID}.txt`; | ||||
| 	const bucket = getBucket(); | ||||
| 	if (chapterID) { | ||||
|  | ||||
| @ -4,9 +4,7 @@ | ||||
| 	import { subscriptions, bp } from "@client/listActions"; | ||||
| 	import { IUser } from "@models/user"; | ||||
| 
 | ||||
| 	const { data: bands } = (await useApiFetch<NonNullable<IBand[]>>( | ||||
| 		"/band/all", | ||||
| 	)) as unknown as { data: Ref<IBand[]> }; | ||||
| 	const { data: bands } = (await useApiFetch<NonNullable<IBand[]>>("/band/all")) as unknown as { data: Ref<IBand[]> }; | ||||
| 
 | ||||
| 	const { data: rd }: { data: any } = useAuth(); | ||||
| 	const data = rd as { user: IUser }; | ||||
| @ -29,16 +27,10 @@ | ||||
| 					</a-col> | ||||
| 					<!-- subscribe... --> | ||||
| 					<a-col v-if="data && data.user?._id" style="margin-left: auto"> | ||||
| 						<a | ||||
| 							v-if="!data?.user.subscriptions.bands.includes(item._id)" | ||||
| 							@click="(e) => hider(bands, item._id, 'subscribe', 'bands')" | ||||
| 						> | ||||
| 						<a v-if="!data?.user.subscriptions.bands.includes(item._id)" @click="(e) => hider(bands, item._id, 'subscribe', 'bands')"> | ||||
| 							<icon :istyle="'regular'" name="paper-plane" :size="12" /> | ||||
| 						</a> | ||||
| 						<a | ||||
| 							v-else | ||||
| 							@click="(e) => hider(bands, item._id, 'unsubscribe', 'bands')" | ||||
| 						> | ||||
| 						<a v-else @click="(e) => hider(bands, item._id, 'unsubscribe', 'bands')"> | ||||
| 							<icon :istyle="'regular'" name="x" :size="12" /> | ||||
| 						</a> | ||||
| 					</a-col> | ||||
|  | ||||
| @ -1,8 +1,5 @@ | ||||
| <template> | ||||
| 	<div style="width: 100%; height: 90vh"> | ||||
| 		<iframe | ||||
| 			style="width: 100%; height: 100%" | ||||
| 			src="https://www.rockfic.com/forum/" | ||||
| 		/> | ||||
| 		<iframe style="width: 100%; height: 100%" src="https://www.rockfic.com/forum/" /> | ||||
| 	</div> | ||||
| </template> | ||||
|  | ||||
| @ -10,10 +10,5 @@ | ||||
| </script> | ||||
| <template> | ||||
| 	<a-typography-title> Post a new Story </a-typography-title> | ||||
| 	<story-form | ||||
| 		endpoint-method="post" | ||||
| 		:can-draft="true" | ||||
| 		:data="defaultStory" | ||||
| 		endpoint="/story/new" | ||||
| 	/> | ||||
| 	<story-form endpoint-method="post" :can-draft="true" :data="defaultStory" endpoint="/story/new" /> | ||||
| </template> | ||||
|  | ||||
| @ -12,9 +12,7 @@ | ||||
| 		middleware: [storyMiddleware], | ||||
| 	}); | ||||
| 	const rtr = useRoute(); | ||||
| 	const { data: story, error } = await useApiFetch<SingleChapterResult>( | ||||
| 		`/story/${rtr.params.id}/${rtr.params.cidx}`, | ||||
| 	); | ||||
| 	const { data: story, error } = await useApiFetch<SingleChapterResult>(`/story/${rtr.params.id}/${rtr.params.cidx}`); | ||||
| 	provide<SingleChapterResult | null>("story", story.value); | ||||
| 	console.log("storyyy", story.value?.currentChapter); | ||||
| 	console.log(rtr); | ||||
| @ -52,55 +50,24 @@ | ||||
| 		<div v-html="story?.currentChapter.text"></div> | ||||
| 		<a-divider style="background-color: #fff" /> | ||||
| 		<a-button-group size="large" v-if="story.totalChapters > 1"> | ||||
| 			<a-button | ||||
| 				v-if="parseInt(rtr.params.cidx as string) > 1" | ||||
| 				@click="() => navigateTo(`/story/${rtr.params.id}/1`)" | ||||
| 			> | ||||
| 				First | ||||
| 			</a-button> | ||||
| 			<a-button | ||||
| 				v-if="parseInt(rtr.params.cidx as string) > 1" | ||||
| 				@click=" | ||||
| 					() => | ||||
| 						navigateTo( | ||||
| 							`/story/${rtr.params.id}/${ | ||||
| 								parseInt(rtr.params.cidx as string) - 1 | ||||
| 							}`, | ||||
| 						) | ||||
| 				" | ||||
| 			> | ||||
| 			<a-button v-if="parseInt(rtr.params.cidx as string) > 1" @click="() => navigateTo(`/story/${rtr.params.id}/1`)"> First </a-button> | ||||
| 			<a-button v-if="parseInt(rtr.params.cidx as string) > 1" @click="() => navigateTo(`/story/${rtr.params.id}/${parseInt(rtr.params.cidx as string) - 1}`)"> | ||||
| 				Previous | ||||
| 			</a-button> | ||||
| 			<a-button | ||||
| 				v-if=" | ||||
| 					parseInt(rtr.params.cidx as string) < story.chapterNames.length - 1 | ||||
| 				" | ||||
| 				@click=" | ||||
| 					() => | ||||
| 						navigateTo( | ||||
| 							`/story/${rtr.params.id}/${ | ||||
| 								parseInt(rtr.params.cidx as string) + 1 | ||||
| 							}`, | ||||
| 						) | ||||
| 				" | ||||
| 				v-if="parseInt(rtr.params.cidx as string) < story.chapterNames.length - 1" | ||||
| 				@click="() => navigateTo(`/story/${rtr.params.id}/${parseInt(rtr.params.cidx as string) + 1}`)" | ||||
| 			> | ||||
| 				Next | ||||
| 			</a-button> | ||||
| 			<a-button | ||||
| 				@click=" | ||||
| 					() => | ||||
| 						navigateTo(`/story/${rtr.params.id}/${story.chapterNames.length}`) | ||||
| 				" | ||||
| 				v-if=" | ||||
| 					parseInt(rtr.params.cidx as string) < story.chapterNames.length - 1 | ||||
| 				" | ||||
| 				@click="() => navigateTo(`/story/${rtr.params.id}/${story.chapterNames.length}`)" | ||||
| 				v-if="parseInt(rtr.params.cidx as string) < story.chapterNames.length - 1" | ||||
| 			> | ||||
| 				Last | ||||
| 			</a-button> | ||||
| 		</a-button-group> | ||||
| 		<a-typography-title style="text-align: center" :level="2"> | ||||
| 			Reviews | ||||
| 		</a-typography-title> | ||||
| 		<a-typography-title style="text-align: center" :level="2"> Reviews </a-typography-title> | ||||
| 		<for-chapter :endpoint="`/story/${rtr.params.id}/${rtr.params.cidx}`" /> | ||||
| 	</div> | ||||
| </template> | ||||
|  | ||||
| @ -6,9 +6,7 @@ | ||||
| 		middleware: [storyMiddleware], | ||||
| 	}); | ||||
| 	const rtr = useRoute(); | ||||
| 	const { data: story, error } = await useApiFetch<IStory>( | ||||
| 		`/story/${rtr.params.id}`, | ||||
| 	); | ||||
| 	const { data: story, error } = await useApiFetch<IStory>(`/story/${rtr.params.id}`); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|  | ||||
| @ -15,10 +15,7 @@ export default eventHandler(async (event) => { | ||||
| 	await captcha(event); | ||||
| 	console.log("fields exist"); | ||||
| 	const user = await User.findOne({ | ||||
| 		$or: [ | ||||
| 			{ username: usernameRegex(body.username) }, | ||||
| 			{ email: (body.email as string).toLowerCase() }, | ||||
| 		], | ||||
| 		$or: [{ username: usernameRegex(body.username) }, { email: (body.email as string).toLowerCase() }], | ||||
| 	}); | ||||
| 	console.log("after f0", user); | ||||
| 	if (user) | ||||
| @ -27,7 +24,7 @@ export default eventHandler(async (event) => { | ||||
| 			message: "A user with that username or email already exists.", | ||||
| 		}); | ||||
| 	let nuser = new User({ | ||||
| 		email: body.email.toLowerCase(), | ||||
| 		email: body.email.toLowerCase().trim(), | ||||
| 		username: w2nc(body.username.trim()), | ||||
| 		password: User.generateHash(body.password), | ||||
| 		auth: { | ||||
|  | ||||
| @ -14,10 +14,7 @@ let authorSingleton: { | ||||
| const threshold = 60 * 60 * 1000; | ||||
| export default cachedEventHandler( | ||||
| 	async (ev) => { | ||||
| 		if ( | ||||
| 			Date.now() - authorSingleton.lastRefreshed >= threshold || | ||||
| 			authorSingleton.data.length < 1 | ||||
| 		) | ||||
| 		if (Date.now() - authorSingleton.lastRefreshed >= threshold || authorSingleton.data.length < 1) | ||||
| 			authorSingleton.data = await User.aggregate([ | ||||
| 				{ $project: { username: true, _id: true } }, | ||||
| 				{ | ||||
| @ -38,10 +35,8 @@ export default cachedEventHandler( | ||||
| 				}, | ||||
| 			]); | ||||
| 		authorSingleton.data.sort((a, b) => { | ||||
| 			if (a.username.toLocaleUpperCase() > b.username.toLocaleUpperCase()) | ||||
| 				return 1; | ||||
| 			else if (a.username.toLocaleUpperCase() < b.username.toLocaleUpperCase()) | ||||
| 				return -1; | ||||
| 			if (a.username.toLocaleUpperCase() > b.username.toLocaleUpperCase()) return 1; | ||||
| 			else if (a.username.toLocaleUpperCase() < b.username.toLocaleUpperCase()) return -1; | ||||
| 			return 0; | ||||
| 		}); | ||||
| 		return authorSingleton.data; | ||||
|  | ||||
| @ -19,10 +19,7 @@ export default eventHandler(async (ev) => { | ||||
| 			statusCode: 400, | ||||
| 			message: "bad parameter", | ||||
| 		}); | ||||
| 	if ( | ||||
| 		ev.context.currentUser!._id != s2v?.author && | ||||
| 		ev.context.currentUser!._id != c2d._id | ||||
| 	) | ||||
| 	if (ev.context.currentUser!._id != s2v?.author && ev.context.currentUser!._id != c2d._id) | ||||
| 		throw createError({ | ||||
| 			statusCode: 403, | ||||
| 			message: messages[403], | ||||
|  | ||||
| @ -4,9 +4,7 @@ import { isIdNan } from "@server/middlewareButNotReally"; | ||||
| 
 | ||||
| export default eventHandler(async (ev) => { | ||||
| 	const revid = isIdNan(ev); | ||||
| 	const r = await Review.findById(revid) | ||||
| 		.populate("author", "username _id") | ||||
| 		.exec(); | ||||
| 	const r = await Review.findById(revid).populate("author", "username _id").exec(); | ||||
| 	if (!r) { | ||||
| 		throw createError({ | ||||
| 			statusCode: 404, | ||||
|  | ||||
| @ -32,8 +32,6 @@ export default eventHandler(async (ev) => { | ||||
| 	}); | ||||
| 	return { | ||||
| 		success: true, | ||||
| 		data: await Review.findById(revid) | ||||
| 			.populate("author", "username profile _id") | ||||
| 			.exec(), | ||||
| 		data: await Review.findById(revid).populate("author", "username profile _id").exec(), | ||||
| 	}; | ||||
| }); | ||||
|  | ||||
| @ -20,9 +20,7 @@ export default eventHandler(async (ev) => { | ||||
| 		}); | ||||
| 	} | ||||
| 	if ( | ||||
| 		(replyingTo?.author as IUser).blocked.includes( | ||||
| 			ev.context.currentUser!._id, | ||||
| 		) || | ||||
| 		(replyingTo?.author as IUser).blocked.includes(ev.context.currentUser!._id) || | ||||
| 		ev.context.currentUser!.blocked.includes((replyingTo?.author as IUser)._id) | ||||
| 	) { | ||||
| 		throw createError({ | ||||
| @ -40,9 +38,7 @@ export default eventHandler(async (ev) => { | ||||
| 		datePosted: new Date(), | ||||
| 	}); | ||||
| 	const { _id } = await newReply.save(); | ||||
| 	const nrs = (await Review.findOne({ _id }) | ||||
| 		.populate("author", "username _id blocked") | ||||
| 		.exec())!; | ||||
| 	const nrs = (await Review.findOne({ _id }).populate("author", "username _id blocked").exec())!; | ||||
| 	replyingTo.replies.push(nrs._id); | ||||
| 	await replyingTo.save(); | ||||
| 	const story = await Story.findById(replyingTo.leftOn); | ||||
| @ -52,9 +48,7 @@ export default eventHandler(async (ev) => { | ||||
| 		}); | ||||
| 	} | ||||
| 	return { | ||||
| 		back: `/story/${replyingTo.leftOn}/${ | ||||
| 			story!.chapters.findIndex((x) => x.id === nrs.whichChapter) + 1 | ||||
| 		}`,
 | ||||
| 		back: `/story/${replyingTo.leftOn}/${story!.chapters.findIndex((x) => x.id === nrs.whichChapter) + 1}`, | ||||
| 		data: nrs.toObject(), | ||||
| 		success: true, | ||||
| 	}; | ||||
|  | ||||
| @ -8,11 +8,7 @@ export default eventHandler(async (ev) => { | ||||
| 	isLoggedIn(ev); | ||||
| 	const s = await storyQuerier(ev); | ||||
| 	const hidden = s.chapters.some((a) => a.hidden); | ||||
| 	if ( | ||||
| 		hidden && | ||||
| 		ev.context.currentUser?._id !== (s.author as IUser)._id && | ||||
| 		!ev.context.currentUser?.profile.isAdmin | ||||
| 	) { | ||||
| 	if (hidden && ev.context.currentUser?._id !== (s.author as IUser)._id && !ev.context.currentUser?.profile.isAdmin) { | ||||
| 		throw createError({ | ||||
| 			statusCode: 403, | ||||
| 			message: messages[403], | ||||
|  | ||||
| @ -20,10 +20,7 @@ export default cachedEventHandler( | ||||
| 	async (event) => { | ||||
| 		let aa = mongoose.connection.db.collection("z_index_totAuthors"); | ||||
| 		let totalStories = await Story.countDocuments({ "chapters.hidden": false }); | ||||
| 		if ( | ||||
| 			!authorSingleton.data.length || | ||||
| 			Date.now() - authorSingleton.lastRefreshed >= threshold | ||||
| 		) { | ||||
| 		if (!authorSingleton.data.length || Date.now() - authorSingleton.lastRefreshed >= threshold) { | ||||
| 			authorSingleton.data = await User.aggregate([ | ||||
| 				{ $project: { username: true, _id: true } }, | ||||
| 				{ | ||||
|  | ||||
| @ -16,9 +16,5 @@ export default eventHandler(async (ev) => { | ||||
| 	}) | ||||
| 		.populate("story") | ||||
| 		.exec(); | ||||
| 	return ar | ||||
| 		.map((a) => a.toObject()) | ||||
| 		.sort( | ||||
| 			(a, b) => b.datePosted.getMilliseconds() - a.datePosted.getMilliseconds(), | ||||
| 		); | ||||
| 	return ar.map((a) => a.toObject()).sort((a, b) => b.datePosted.getMilliseconds() - a.datePosted.getMilliseconds()); | ||||
| }); | ||||
|  | ||||
| @ -1,10 +1,7 @@ | ||||
| export default eventHandler(async (ev) => { | ||||
| 	if (ev.context.currentUser) { | ||||
| 		let log = ev.context.currentUser.ipLog; | ||||
| 		if ( | ||||
| 			ev.context.clientAddress !== undefined && | ||||
| 			!/127\.0\.0\.1|localhost|::1/.test(ev.context.clientAddress) | ||||
| 		) { | ||||
| 		if (ev.context.clientAddress !== undefined && !/127\.0\.0\.1|localhost|::1/.test(ev.context.clientAddress)) { | ||||
| 			let found = log.findIndex((a) => a.ip === ev.context.clientAddress); | ||||
| 			if (found !== -1) { | ||||
| 				ev.context.currentUser.ipLog[found].lastAccess = new Date(); | ||||
|  | ||||
| @ -4,10 +4,7 @@ export default eventHandler(async (event) => { | ||||
| 	let y = new Date().getFullYear(); | ||||
| 	let fmfilt: any = {}; | ||||
| 
 | ||||
| 	if ( | ||||
| 		!!process.env.JulyFicmas && | ||||
| 		new Date() < new Date(Date.parse("Aug 1 " + y)) | ||||
| 	) { | ||||
| 	if (!!process.env.JulyFicmas && new Date() < new Date(Date.parse("Aug 1 " + y))) { | ||||
| 		fmfilt.isAnniversary = true; | ||||
| 		fmfilt.year = y; | ||||
| 	} else if (new Date() < new Date(Date.parse("Dec 25 " + y))) { | ||||
|  | ||||
| @ -5,9 +5,7 @@ export default eventHandler(async (ev) => { | ||||
| 	ev.node.res.on("close", () => { | ||||
| 		p.done({ | ||||
| 			label: "http/request", | ||||
| 			message: `{${ | ||||
| 				ev.context.currentUser?.username || "guest" | ||||
| 			}} | ${ev.method.toLocaleUpperCase()} @ ${ev._path}`,
 | ||||
| 			message: `{${ev.context.currentUser?.username || "guest"}} | ${ev.method.toLocaleUpperCase()} @ ${ev._path}`, | ||||
| 		}); | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
							
								
								
									
										29
									
								
								typings/auth.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										29
									
								
								typings/auth.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -1,12 +1,5 @@ | ||||
| import { IUser } from "@models/user"; | ||||
| import { | ||||
| 	GetSessionFunc, | ||||
| 	SecondarySignInOptions, | ||||
| 	SessionLastRefreshedAt, | ||||
| 	SessionStatus, | ||||
| 	SignInFunc, | ||||
| 	SignOutFunc, | ||||
| } from "@sidebase/nuxt-auth/dist/runtime/types"; | ||||
| import { GetSessionFunc, SecondarySignInOptions, SessionLastRefreshedAt, SessionStatus, SignInFunc, SignOutFunc } from "@sidebase/nuxt-auth/dist/runtime/types"; | ||||
| import { ComputedRef, Ref } from "vue"; | ||||
| 
 | ||||
| declare module "#auth" { | ||||
| @ -24,18 +17,10 @@ declare module "@sidebase/nuxt-auth/dist/runtime/types" { | ||||
| 	declare const signIn: SignInFunc<Credentials, any>; | ||||
| 	declare const signOut: SignOutFunc; | ||||
| 	declare const getSession: GetSessionFunc<SessionData | null | void>; | ||||
| 	declare const signUp: ( | ||||
| 		credentials: Credentials, | ||||
| 		signInOptions?: SecondarySignInOptions, | ||||
| 	) => Promise<any>; | ||||
| 	declare const signUp: (credentials: Credentials, signInOptions?: SecondarySignInOptions) => Promise<any>; | ||||
| 
 | ||||
| 	type WrappedSessionData<SessionData> = Ref<SessionData | null | undefined>; | ||||
| 	export interface CommonUseAuthReturn< | ||||
| 		SignIn, | ||||
| 		SignOut, | ||||
| 		GetSession, | ||||
| 		SessionData, | ||||
| 	> { | ||||
| 	export interface CommonUseAuthReturn<SignIn, SignOut, GetSession, SessionData> { | ||||
| 		data: Readonly<WrappedSessionData<SessionData>>; | ||||
| 		lastRefreshedAt: Readonly<Ref<SessionLastRefreshedAt>>; | ||||
| 		status: ComputedRef<SessionStatus>; | ||||
| @ -43,13 +28,7 @@ declare module "@sidebase/nuxt-auth/dist/runtime/types" { | ||||
| 		signOut: SignOut; | ||||
| 		getSession: GetSession; | ||||
| 	} | ||||
| 	interface UseAuthReturn | ||||
| 		extends CommonUseAuthReturn< | ||||
| 			typeof signIn, | ||||
| 			typeof signOut, | ||||
| 			typeof getSession, | ||||
| 			SessionData | ||||
| 		> { | ||||
| 	interface UseAuthReturn extends CommonUseAuthReturn<typeof signIn, typeof signOut, typeof getSession, SessionData> { | ||||
| 		signUp: typeof signUp; | ||||
| 		token: Readonly<Ref<string | null>>; | ||||
| 	} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user