style(*): edit indents

This commit is contained in:
parent c8e84c909e
commit 8f45dbe563
Signed by: tablet
GPG Key ID: 924A5F6AF051E87C
41 changed files with 198 additions and 491 deletions

@ -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 && ",&nbsp;") ||
""
}}
{{ (index < story!.currentChapter?.bands.length - 1 && ",&nbsp;") || "" }}
</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 && ",&nbsp;") ||
""
}}
{{ (index < story!.currentChapter?.genre.length - 1 && ",&nbsp;") || "" }}
</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 &&
",&nbsp;") ||
""
}}
{{ (index < story!.currentChapter?.relationships.length - 1 && ",&nbsp;") || "" }}
</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 &&
",&nbsp;") ||
""
}}
{{ item }}{{ (index < story!.currentChapter?.characters.length - 1 && ",&nbsp;") || "" }}
</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],
});
}
});

@ -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"],

@ -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

@ -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>>;
}