Schedule posts to Bluesky with Cloudflare workers. skyscheduler.work
cf tool bsky-tool cloudflare bluesky schedule bsky service social-media cloudflare-workers

More cleanup

fix minor bugs and project layout

+79 -72
+5 -5
README.md
··· 102 102 103 103 ### Minimization 104 104 105 - The application by default is configured to use the minified versions of the scripts in `assets/js`. You can either turn off this behavior in `src/limits.ts` or run `npm run minify` whenever those files change. 105 + The application by default is configured to use the minified versions of the scripts in `assets/js`. By default these rebuild whenever any typescript file is changed or the application is deployed/ran. 106 106 107 107 ## Project Structure 108 108 ··· 132 132 133 133 #### Server 134 134 135 - - Zod - data validation 136 - - BetterAuth - login/authentication 137 - - BetterAuthCloudflare - helper adaption 135 + - BetterAuth - site login/authentication 136 + - BetterAuthCloudflare - helper for BetterAuth on CF 138 137 - hono - request routing/processing 139 138 - uuid - id generation 139 + - zod - data validation 140 140 - image-dimensions - image data validation 141 141 - date-fns - date processing helpers 142 142 - drizzle - database orm/schemas ··· 148 148 - tribute - client autocomplete library 149 149 - toastify - client notifications 150 150 - dropzone - file upload negotiation 151 - - pico - styling 151 + - pico - styling, tabs, modals 152 152 - countable - dynamic input counter 153 153 154 154 ## Contributing
+2 -1
src/endpoints/account.tsx
··· 233 233 // There has to be a better method for this tbh. 234 234 const canMessageUser = await checkIfCanDMUser(c.env, bskyUserId); 235 235 if (canMessageUser === false) { 236 - return c.json({ok: false, message: `Could not send a direct message to your bsky account.\nPlease check to see if you are following @${c.env.RESET_BOT_USERNAME} and your DM permissions`}, 401); 236 + return c.json({ok: false, message: 237 + `Could not send a direct message to your bsky account.\nPlease check to see if you are following @${c.env.RESET_BOT_USERNAME} and your DM permissions`}, 401); 237 238 } 238 239 239 240 const { data, error } = await auth.api.requestPasswordReset({
+6 -7
src/endpoints/post.tsx
··· 13 13 EmbedDataType, LooseObj, Post 14 14 } from "../types.d"; 15 15 import { makePost } from "../utils/bskyApi"; 16 - import { getUsernameForUser } from "../utils/db/userinfo"; 17 16 import { 18 - createPost, createRepost, deletePost, getPostById, getPostByIdWithReposts, 17 + createPost, createRepost, 18 + deletePost, getPostById, 19 + getPostByIdWithReposts, 19 20 updatePostForUser 20 21 } from "../utils/dbQuery"; 21 22 import { enqueuePost, shouldPostNowQueue } from "../utils/queuePublisher"; ··· 50 51 51 52 const { content } = validation.data; 52 53 // delete item from r2 53 - deleteFromR2(c, content); 54 + c.executionCtx.waitUntil(deleteFromR2(c, content, false)); 54 55 return c.json({"success": true}, 200); 55 56 }); 56 57 ··· 184 185 185 186 if (await updatePostForUser(c, id, payload)) { 186 187 originalPost.text = content; 187 - const username = await getUsernameForUser(c); 188 188 c.header("HX-Trigger-After-Settle", `{"scrollListToPost": "${id}"}`); 189 189 c.header("HX-Trigger-After-Swap", "postUpdatedNotice, timeSidebar, scrollTop"); 190 - return c.html(<ScheduledPost post={originalPost} user={username} dynamic={true} />); 190 + return c.html(<ScheduledPost post={originalPost} dynamic={true} />); 191 191 } 192 192 193 193 c.header("HX-Trigger-After-Settle", swapErrEvents); ··· 203 203 // Get the original post to replace with 204 204 if (postInfo !== null) { 205 205 c.header("HX-Trigger-After-Swap", "timeSidebar, scrollListTop, scrollTop"); 206 - const username = await getUsernameForUser(c); 207 - return c.html(<ScheduledPost post={postInfo} user={username} dynamic={true} />); 206 + return c.html(<ScheduledPost post={postInfo} dynamic={true} />); 208 207 } 209 208 210 209 // Refresh sidebar otherwise
src/layout/contentLabelOptions.tsx src/layout/options/contentLabelOptions.tsx
+1 -1
src/layout/depTags.tsx src/layout/helpers/includesTags.tsx
··· 1 - import { PreloadRules } from "../types.d"; 1 + import { PreloadRules } from "../../types"; 2 2 3 3 type DepTagsType = { 4 4 scripts?: PreloadRules[]
+2 -2
src/layout/editPost.tsx
··· 48 48 return (<PostContentObject text={post.text} posted={true} repost={false} />); 49 49 } 50 50 51 - const editSpinner:string = `editSpinner${post.postid}`; 52 - const editResponse:string = `editResponse${post.postid}`; 51 + const editSpinner: string = `editSpinner${post.postid}`; 52 + const editResponse: string = `editResponse${post.postid}`; 53 53 return ( 54 54 <form id={`editPost${post.postid}`} hx-ext="form-json" hx-post={`/post/edit/${post.postid}`} hx-target={`#${editResponse}`} 55 55 hx-swap="innerHTML swap:0.2s" hx-indicator={`#${editSpinner}`}>
+4 -3
src/layout/footer.tsx src/layout/helpers/footer.tsx
··· 1 - import { PROGRESS_MADE, PROGRESS_TOTAL } from "../progress"; 1 + import { PROGRESS_MADE, PROGRESS_TOTAL } from "../../progress"; 2 2 3 3 // Helper footer for various pages 4 4 type FooterCopyrightProps = { ··· 9 9 10 10 export default function FooterCopyright(props: FooterCopyrightProps) { 11 11 const newWinAttr = props.inNewWindow ? {"target": '_blank'} : {}; 12 - const projectURL = (<a class="secondary" target="_blank" title="Project source on GitHub" href="https://github.com/SocksTheWolf/SkyScheduler">SkyScheduler</a>); 12 + const projectURL = (<a class="secondary" target="_blank" title="Project source on GitHub" 13 + href="https://github.com/SocksTheWolf/SkyScheduler">SkyScheduler</a>); 13 14 const homepageURL = (<a class="secondary" title="Homepage" href="/">SkyScheduler</a>); 14 15 const progressBarTooltip = `$${PROGRESS_MADE}/$${PROGRESS_TOTAL} for this month`; 15 16 return ( ··· 21 22 <a rel="author" target="_blank" title="Project author" href="https://socksthewolf.com">SocksTheWolf</a><br /> 22 23 <small> 23 24 <a class="secondary" target="_blank" 24 - data-tooltip="Tips are not required, the service is free, but if you like this service, tips would help with the costs <3" 25 + data-tooltip="Tips are not required, the service is free, but if you like this service they are appreciated <3" 25 26 title="Tip the dev" href="/tip">Tip</a> - 26 27 <a class="secondary" {...newWinAttr} href="/tos" title="Terms of Service">Terms</a> - 27 28 <a class="secondary" {...newWinAttr} href="/privacy" title="Privacy Policy">Privacy</a>
+3 -3
src/layout/main.tsx
··· 1 1 import { html } from 'hono/html'; 2 2 import { Child } from 'hono/jsx'; 3 - import { HtmlEscapedString } from 'hono/utils/html'; 4 3 import { PreloadRules } from '../types.d'; 5 4 import { mainScriptStr } from '../utils/appScripts'; 6 - import { PreloadDependencyTags } from './depTags'; 7 - import MetaTags from './metaTags'; 5 + import { PreloadDependencyTags } from './helpers/includesTags'; 6 + import MetaTags from './helpers/metaTags'; 8 7 9 8 type BaseLayoutProps = { 10 9 children: Child; ··· 45 44 </container> 46 45 </body> 47 46 </html>); 47 + // inject the doctype so we're not in quirks mode 48 48 return html`<!DOCTYPE html> 49 49 ${layout}`; 50 50 }
+4 -4
src/layout/makePost.tsx
··· 11 11 } from "../limits"; 12 12 import { PreloadRules } from "../types.d"; 13 13 import { ConstScriptPreload } from "../utils/constScriptGen"; 14 - import { ContentLabelOptions } from "./contentLabelOptions"; 15 - import { IncludeDependencyTags } from "./depTags"; 16 - import { RetweetOptions } from "./retweetOptions"; 17 - import { ScheduleOptions } from "./scheduleOptions"; 14 + import { ContentLabelOptions } from "./options/contentLabelOptions"; 15 + import { IncludeDependencyTags } from "./helpers/includesTags"; 16 + import { RetweetOptions } from "./options/retweetOptions"; 17 + import { ScheduleOptions } from "./options/scheduleOptions"; 18 18 19 19 export const PreloadPostCreation: PreloadRules[] = [ 20 20 ...ConstScriptPreload,
+5 -3
src/layout/makeRetweet.tsx
··· 1 - import { RetweetOptions } from "./retweetOptions"; 2 - import { ScheduleOptions } from "./scheduleOptions"; 1 + import { RetweetOptions } from "./options/retweetOptions"; 2 + import { ScheduleOptions } from "./options/scheduleOptions"; 3 3 4 4 export function MakeRetweet() { 5 5 return ( ··· 11 11 <article> 12 12 <header>Post To Retweet</header> 13 13 <input type="text" id="repostRecordURL" placeholder="https://" /> 14 - <small>This must be a post, it cannot be anything else. It must also exist, and be reachable (i.e. a post that's not deleted, nor are you forbidden from seeing it)</small> 14 + <small>This must be a post, it cannot be anything else. 15 + It must also exist, and be reachable 16 + (i.e. a post that's not deleted, nor are you forbidden from seeing it)</small> 15 17 </article> 16 18 <ScheduleOptions timeID="repostTime" allowNow={false} type="retweet" header="Retweet At" /> 17 19 <article>
+1
src/layout/metaTags.tsx src/layout/helpers/metaTags.tsx
··· 1 1 import { raw } from "hono/html"; 2 2 3 3 export default function MetaTags() { 4 + /* Modify these to change meta information on all pages. */ 4 5 const Title: string = "SkyScheduler"; 5 6 const URL: string = "https://skyscheduler.work"; 6 7 const Description: string = "Schedule and automatically repost on Bluesky! Boost engagement and reach more people no matter what time of day!";
src/layout/navTags.tsx src/layout/helpers/navTags.tsx
-1
src/layout/passwordFields.tsx
··· 1 1 import { html } from "hono/html"; 2 2 import { BSKY_MAX_APP_PASSWORD_LENGTH, MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS } from "../limits"; 3 3 import { PWAutoCompleteSettings } from "../types.d"; 4 - import { appPasswordRegex } from "../validation/regexCases"; 5 4 6 5 type PasswordFieldSettings = { 7 6 required?: boolean
+2 -6
src/layout/postList.tsx
··· 2 2 import { html, raw } from "hono/html"; 3 3 import isEmpty from "just-is-empty"; 4 4 import { Post } from "../types.d"; 5 - import { getUsernameForUser } from "../utils/db/userinfo"; 6 5 import { getPostsForUser } from "../utils/dbQuery"; 7 6 import { MAX_POSTED_LENGTH } from "../limits"; 8 7 ··· 19 18 20 19 type ScheduledPostOptions = { 21 20 post: Post; 22 - user: string|null; 23 21 // if the object should be dynamically replaced. 24 22 // usually in edit/cancel edit settings. 25 23 dynamic?: boolean; ··· 27 25 28 26 export function ScheduledPost(props: ScheduledPostOptions) { 29 27 const content: Post = props.post; 30 - const username: string|null = props.user; 31 28 const oobSwapStr = (props.dynamic) ? `hx-swap-oob="#postBase${content.postid}"` : ""; 32 - const hasBeenPosted: boolean = (username !== null && content.posted === true && content.uri !== undefined); 29 + const hasBeenPosted: boolean = (content.posted === true && content.uri !== undefined); 33 30 const postURIID: string|null = content.uri ? content.uri.replace("at://","").replace("app.bsky.feed.","") : null; 34 31 35 32 const postType = content.isRepost ? "repost" : "post"; ··· 111 108 export const ScheduledPostList = async ({ctx}: ScheduledPostListProps) => { 112 109 if (ctx !== undefined) { 113 110 const response: Post[]|null = await getPostsForUser(ctx); 114 - const username = await getUsernameForUser(ctx); 115 111 if (!isEmpty(response)) { 116 112 return ( 117 113 <> 118 114 <a hidden tabindex={-1} class="invalidateTab hidden"></a> 119 115 {response!.map((data: Post) => { 120 - return <ScheduledPost post={data} user={username} />; 116 + return <ScheduledPost post={data} />; 121 117 })} 122 118 </> 123 119 );
+1 -1
src/layout/retweetOptions.tsx src/layout/options/retweetOptions.tsx
··· 1 1 import isEmpty from "just-is-empty"; 2 - import { MAX_REPOST_IN_HOURS, MAX_REPOST_INTERVAL_LIMIT } from "../limits"; 2 + import { MAX_REPOST_IN_HOURS, MAX_REPOST_INTERVAL_LIMIT } from "../../limits"; 3 3 4 4 type RetweetOptionsProps = { 5 5 id: string;
src/layout/scheduleOptions.tsx src/layout/options/scheduleOptions.tsx
+10 -5
src/layout/settings.tsx
··· 9 9 }; 10 10 11 11 export function Settings(props: SettingsTypeProps) { 12 - const placeholderPDS = props.pds? props.pds : "https://bsky.social"; 12 + // attempt to pull the user's current PDS information. 13 + const placeholderPDS = props.pds ? props.pds : "https://bsky.social"; 13 14 return ( 14 15 <> 15 16 <dialog id="changeInfo"> ··· 25 26 <form id="settingsData" name="settingsData" hx-post="/account/update" hx-target="#accountResponse" 26 27 hx-swap="innerHTML swap:1s" hx-indicator="#spinner" hx-disabled-elt="#settingsButtons button, find input" novalidate> 27 28 28 - <UsernameField required={false} title="BlueSky Handle:" hintText="Only change this if you have recently changed your Bluesky handle" /> 29 + <UsernameField required={false} title="BlueSky Handle:" 30 + hintText="Only change this if you have recently changed your Bluesky handle" /> 29 31 30 32 <label> 31 33 Dashboard Pass: ··· 35 37 <label> 36 38 BSky App Password: 37 39 <BSkyAppPasswordField /> 38 - <small>If you need to change your bsky application password, you can <a href="https://bsky.app/settings/app-passwords" target="_blank">get a new one here</a>.</small> 40 + <small>If you need to change your bsky application password, you can 41 + <a href="https://bsky.app/settings/app-passwords" target="_blank">get a new one here</a>.</small> 39 42 </label> 40 43 <label> 41 44 BSky PDS: ··· 62 65 63 66 <center><strong>NOTE</strong>: THIS ACTION IS <u>PERMANENT</u>.</center> 64 67 </p> 65 - <form id="delAccountForm" name="delAccountForm" hx-post="/account/delete" hx-target="#accountDeleteResponse" hx-disabled-elt="#accountDeleteButtons button, find input" 68 + <form id="delAccountForm" name="delAccountForm" hx-post="/account/delete" 69 + hx-target="#accountDeleteResponse" hx-disabled-elt="#accountDeleteButtons button, find input" 66 70 hx-swap="innerHTML swap:1s" hx-indicator="#delSpinner" novalidate> 67 71 <label> 68 - Dashboard Pass: <input id="deleteAccountPass" type="password" name="password" minlength={MIN_DASHBOARD_PASS} maxlength={MAX_DASHBOARD_PASS} /> 72 + Dashboard Pass: <input id="deleteAccountPass" type="password" name="password" 73 + minlength={MIN_DASHBOARD_PASS} maxlength={MAX_DASHBOARD_PASS} /> 69 74 <small>The password to access the SkyScheduler Dashboard</small> 70 75 </label> 71 76 </form>
+1 -4
src/layout/turnstile.tsx src/layout/helpers/turnstile.tsx
··· 1 1 import { Context } from "hono"; 2 - 3 - export function UseCFTurnstile(ctx: Context): boolean { 4 - return ctx.env.SIGNUP_SETTINGS.use_captcha && ctx.env.IN_DEV === false; 5 - } 2 + import { UseCFTurnstile } from "../../utils/helpers"; 6 3 7 4 export function TurnstileCaptchaPreloads(ctx: Context) { 8 5 if (UseCFTurnstile(ctx)) {
+3 -2
src/layout/usernameField.tsx
··· 10 10 export function UsernameField(props?: UsernameFieldProps) { 11 11 const hintText = props?.hintText ? raw(props.hintText) : 12 12 (<span>This is your Bluesky username/handle, in the format of a custom domain or <code>USERNAME.bsky.social</code>. 13 - <br />Profile/post links will attempt to be converted to the correct format. 13 + <br />Profile/post links will attempt to be converted into the correct format. 14 14 </span>); 15 15 // default required true. 16 16 const inputRequired = (props) ? (props?.required || false) : true; 17 17 return ( 18 18 <label> 19 19 {props?.title || "Bluesky Handle"} 20 - <input type="text" id="username" name="username" autocomplete="username" minlength={BSKY_MIN_USERNAME_LENGTH} required={inputRequired} /> 20 + <input type="text" id="username" name="username" autocomplete="username" 21 + minlength={BSKY_MIN_USERNAME_LENGTH} required={inputRequired} /> 21 22 <small>{hintText}</small> 22 23 </label> 23 24 );
-2
src/layout/violationsBar.tsx
··· 1 1 import { Context } from "hono"; 2 - import isEmpty from "just-is-empty"; 3 - import { Violation } from "../types.d"; 4 2 import { getViolationsForCurrentUser } from "../utils/db/violations"; 5 3 6 4 export async function ViolationNoticeBar(props: any) {
+1 -1
src/middleware/turnstile.ts
··· 1 1 import { Context } from "hono"; 2 - import { UseCFTurnstile } from "../layout/turnstile"; 2 + import { UseCFTurnstile } from "../utils/helpers"; 3 3 4 4 // Middleware that handles turnstile verification. 5 5 export async function verifyTurnstile(c: Context, next: any) {
+2 -2
src/pages/dashboard.tsx
··· 1 1 import { Context } from "hono"; 2 2 import { AltTextDialog } from "../layout/altTextModal"; 3 - import { IncludeDependencyTags, ScriptTags } from "../layout/depTags"; 4 - import FooterCopyright from "../layout/footer"; 3 + import { IncludeDependencyTags, ScriptTags } from "../layout/helpers/includesTags"; 4 + import FooterCopyright from "../layout/helpers/footer"; 5 5 import { BaseLayout } from "../layout/main"; 6 6 import { PostCreation, PreloadPostCreation } from "../layout/makePost"; 7 7 import { MakeRetweet } from "../layout/makeRetweet";
+3 -3
src/pages/forgot.tsx
··· 1 1 import { Context } from "hono"; 2 2 import AccountHandler from "../layout/account"; 3 - import FooterCopyright from "../layout/footer"; 3 + import FooterCopyright from "../layout/helpers/footer"; 4 4 import { BaseLayout } from "../layout/main"; 5 - import NavTags from "../layout/navTags"; 6 - import { TurnstileCaptcha, TurnstileCaptchaPreloads } from "../layout/turnstile"; 5 + import NavTags from "../layout/helpers/navTags"; 6 + import { TurnstileCaptcha, TurnstileCaptchaPreloads } from "../layout/helpers/turnstile"; 7 7 import { UsernameField } from "../layout/usernameField"; 8 8 9 9 export default function ForgotPassword(props:any) {
+8 -5
src/pages/homepage.tsx
··· 1 - import FooterCopyright from "../layout/footer"; 1 + import FooterCopyright from "../layout/helpers/footer"; 2 2 import { BaseLayout } from "../layout/main"; 3 - import NavTags from "../layout/navTags"; 3 + import NavTags from "../layout/helpers/navTags"; 4 4 import { 5 5 MAX_POSTS_PER_THREAD, MAX_REPOST_DAYS, MAX_REPOST_IN_HOURS, 6 6 MAX_REPOST_INTERVAL, R2_FILE_SIZE_LIMIT_IN_MB ··· 14 14 <article> 15 15 <noscript><header>Javascript is required to use this website</header></noscript> 16 16 <p> 17 - <strong>SkyScheduler</strong> is a free, <a href="https://github.com/socksthewolf/skyscheduler" rel="nofollow" target="_blank">open source</a> service that 17 + <strong>SkyScheduler</strong> is a free, 18 + <a href="https://github.com/socksthewolf/skyscheduler" rel="nofollow" target="_blank">open source</a> service that 18 19 lets you schedule and automatically repost your content on Bluesky!<br /> 19 20 Boost engagement and reach more people no matter what time of day!<br /> 20 21 <center> ··· 36 37 <li>Handles multiple users/accounts easily, supports most PDS instances</li> 37 38 <li>Schedule your posts any time in the future (to the nearest hour)</li> 38 39 <li>Supports embeds, quote posts, links, tagging, mentions</li> 39 - <li>Post <span data-tooltip={`images and video (up to ${R2_FILE_SIZE_LIMIT_IN_MB} MB)`}>media</span> with content labels and full support for alt text</li> 40 + <li>Post <span data-tooltip={`images and video (up to ${R2_FILE_SIZE_LIMIT_IN_MB} MB)`}>media</span> 41 + with content labels and full support for alt text</li> 40 42 <li>Schedule entire threads with support of up to {MAX_POSTS_PER_THREAD} posts per thread!</li> 41 - <li>Automatically retweet your content at an interval of your choosing, up to {MAX_REPOST_INTERVAL} times every {MAX_REPOST_IN_HOURS-1} hours (or {MAX_REPOST_DAYS} days)</li> 43 + <li>Automatically retweet your content at an interval of your choosing, up to {MAX_REPOST_INTERVAL} times every 44 + {MAX_REPOST_IN_HOURS-1} hours (or {MAX_REPOST_DAYS} days)</li> 42 45 <li>Edit the content of posts and alt text before they are posted</li> 43 46 </ul> 44 47 </p>
+1 -1
src/pages/login.tsx
··· 1 1 import AccountHandler from "../layout/account"; 2 2 import { BaseLayout } from "../layout/main"; 3 - import NavTags from "../layout/navTags"; 3 + import NavTags from "../layout/helpers/navTags"; 4 4 import { DashboardPasswordField } from "../layout/passwordFields"; 5 5 import { UsernameField } from "../layout/usernameField"; 6 6 import { PWAutoCompleteSettings } from "../types.d";
+2 -2
src/pages/privacy.tsx
··· 1 - import FooterCopyright from "../layout/footer"; 1 + import FooterCopyright from "../layout/helpers/footer"; 2 2 import { BaseLayout } from "../layout/main"; 3 - import NavTags from "../layout/navTags"; 3 + import NavTags from "../layout/helpers/navTags"; 4 4 5 5 export default function PrivacyPolicy() { 6 6 return (
+1 -1
src/pages/reset.tsx
··· 1 1 import AccountHandler from "../layout/account"; 2 2 import { BaseLayout } from "../layout/main"; 3 - import NavTags from "../layout/navTags"; 3 + import NavTags from "../layout/helpers/navTags"; 4 4 import { MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS } from "../limits"; 5 5 6 6 export default function ResetPassword() {
+3 -3
src/pages/signup.tsx
··· 1 1 import { Context } from "hono"; 2 2 import AccountHandler from "../layout/account"; 3 - import FooterCopyright from "../layout/footer"; 3 + import FooterCopyright from "../layout/helpers/footer"; 4 4 import { BaseLayout } from "../layout/main"; 5 - import NavTags from "../layout/navTags"; 5 + import NavTags from "../layout/helpers/navTags"; 6 6 import { BSkyAppPasswordField, DashboardPasswordField } from "../layout/passwordFields"; 7 - import { TurnstileCaptcha, TurnstileCaptchaPreloads } from "../layout/turnstile"; 7 + import { TurnstileCaptcha, TurnstileCaptchaPreloads } from "../layout/helpers/turnstile"; 8 8 import { UsernameField } from "../layout/usernameField"; 9 9 import { MAX_DASHBOARD_PASS, MIN_DASHBOARD_PASS } from "../limits"; 10 10 import { PWAutoCompleteSettings } from "../types.d";
+2 -2
src/pages/tos.tsx
··· 1 - import FooterCopyright from "../layout/footer"; 1 + import FooterCopyright from "../layout/helpers/footer"; 2 2 import { BaseLayout } from "../layout/main"; 3 - import NavTags from "../layout/navTags"; 3 + import NavTags from "../layout/helpers/navTags"; 4 4 5 5 export default function TermsOfService() { 6 6 return (
-1
src/utils/db/maintain.ts
··· 2 2 import { BatchItem } from "drizzle-orm/batch"; 3 3 import { drizzle, DrizzleD1Database } from "drizzle-orm/d1"; 4 4 import flatten from "just-flatten-it"; 5 - import truncate from "just-truncate"; 6 5 import { mediaFiles, posts, repostCounts, reposts } from "../../db/app.schema"; 7 6 import { users } from "../../db/auth.schema"; 8 7 import { MAX_POSTED_LENGTH } from "../../limits";
+1 -1
src/utils/dbQuery.ts
··· 430 430 return { ok: success, msg: success ? "success" : "fail", postId: postUUID }; 431 431 }; 432 432 433 - export const updatePostForUser = async (c: Context, id: string, newData: Object) => { 433 + export const updatePostForUser = async (c: Context, id: string, newData: Object): Promise<boolean> => { 434 434 const userId = c.get("userId"); 435 435 return await updatePostForGivenUser(c.env, userId, id, newData); 436 436 };
+5
src/utils/helpers.ts
··· 2 2 import has from "just-has"; 3 3 import isEmpty from "just-is-empty"; 4 4 import { Bindings, BskyAPILoginCreds, Post, Repost, RepostInfo } from "../types.d"; 5 + import { Context } from "hono"; 5 6 6 7 export function createPostObject(data: any) { 7 8 const postData: Post = (new Object() as Post); ··· 104 105 105 106 export function daysAgo(days: number) { 106 107 return subDays(new Date(), days); 108 + } 109 + 110 + export function UseCFTurnstile(ctx: Context): boolean { 111 + return ctx.env.SIGNUP_SETTINGS.use_captcha && ctx.env.IN_DEV === false; 107 112 }