validation
This commit is contained in:
@@ -47,7 +47,7 @@ export default function PostForm(props: PostFormProps) {
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
const [error, setError] = createSignal("");
|
||||
const [showAutoSaveMessage, setShowAutoSaveMessage] = createSignal(false);
|
||||
const [isInitialLoad, setIsInitialLoad] = createSignal(props.mode === "edit");
|
||||
const [isInitialLoad, setIsInitialLoad] = createSignal(true);
|
||||
const [initialBody, setInitialBody] = createSignal<string | undefined>(
|
||||
props.initialData?.body
|
||||
);
|
||||
@@ -57,9 +57,10 @@ export default function PostForm(props: PostFormProps) {
|
||||
);
|
||||
|
||||
// Mark initial load as complete after data is loaded (for edit mode)
|
||||
// Use setTimeout to ensure this runs after all signals are initialized
|
||||
createEffect(() => {
|
||||
if (props.mode === "edit" && props.initialData) {
|
||||
setIsInitialLoad(false);
|
||||
setTimeout(() => setIsInitialLoad(false), 0);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import { env } from "~/env/server";
|
||||
import { ConnectionFactory, hashPassword, checkPassword } from "~/server/utils";
|
||||
import { SignJWT, jwtVerify } from "jose";
|
||||
import { setCookie, getCookie } from "vinxi/http";
|
||||
import type { User } from "~/types/user";
|
||||
import type { User } from "~/db/types";
|
||||
import {
|
||||
fetchWithTimeout,
|
||||
checkResponse,
|
||||
@@ -15,6 +15,12 @@ import {
|
||||
TimeoutError,
|
||||
APIError
|
||||
} from "~/server/fetch-utils";
|
||||
import {
|
||||
registerUserSchema,
|
||||
loginUserSchema,
|
||||
resetPasswordSchema,
|
||||
requestPasswordResetSchema
|
||||
} from "../schemas/user";
|
||||
|
||||
async function createJWT(
|
||||
userId: string,
|
||||
@@ -501,16 +507,11 @@ export const authRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
emailRegistration: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(8),
|
||||
passwordConfirmation: z.string().min(8)
|
||||
})
|
||||
)
|
||||
.input(registerUserSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { email, password, passwordConfirmation } = input;
|
||||
|
||||
// Schema already validates password match, but double check
|
||||
if (password !== passwordConfirmation) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
@@ -549,13 +550,7 @@ export const authRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
emailPasswordLogin: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string(),
|
||||
rememberMe: z.boolean().optional()
|
||||
})
|
||||
)
|
||||
.input(loginUserSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { email, password, rememberMe } = input;
|
||||
|
||||
@@ -746,7 +741,7 @@ export const authRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
requestPasswordReset: publicProcedure
|
||||
.input(z.object({ email: z.string().email() }))
|
||||
.input(requestPasswordResetSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { email } = input;
|
||||
|
||||
@@ -862,16 +857,11 @@ export const authRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
resetPassword: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
token: z.string(),
|
||||
newPassword: z.string().min(8),
|
||||
newPasswordConfirmation: z.string().min(8)
|
||||
})
|
||||
)
|
||||
.input(resetPasswordSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { token, newPassword, newPasswordConfirmation } = input;
|
||||
|
||||
// Schema already validates password match, but double check
|
||||
if (newPassword !== newPasswordConfirmation) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
@@ -945,7 +935,7 @@ export const authRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
resendEmailVerification: publicProcedure
|
||||
.input(z.object({ email: z.string().email() }))
|
||||
.input(requestPasswordResetSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { email } = input;
|
||||
|
||||
|
||||
@@ -8,12 +8,35 @@ import { ConnectionFactory } from "~/server/utils";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { env } from "~/env/server";
|
||||
import { cache, withCacheAndStale } from "~/server/cache";
|
||||
import type {
|
||||
Comment,
|
||||
CommentReaction,
|
||||
Post,
|
||||
PostLike,
|
||||
User,
|
||||
Tag
|
||||
} from "~/db/types";
|
||||
import {
|
||||
getCommentReactionsQuerySchema,
|
||||
toggleCommentReactionMutationSchema,
|
||||
deleteCommentWithTypeSchema,
|
||||
getCommentsByPostIdSchema,
|
||||
getPostByIdSchema,
|
||||
getPostByTitleSchema,
|
||||
createPostSchema,
|
||||
updatePostSchema,
|
||||
idSchema,
|
||||
togglePostLikeMutationSchema,
|
||||
getUserByIdSchema,
|
||||
updateUserImageSchema,
|
||||
updateUserEmailSchema
|
||||
} from "../schemas/database";
|
||||
|
||||
const BLOG_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
||||
|
||||
export const databaseRouter = createTRPCRouter({
|
||||
getCommentReactions: publicProcedure
|
||||
.input(z.object({ commentID: z.string() }))
|
||||
.input(getCommentReactionsQuerySchema)
|
||||
.query(async ({ input }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
@@ -22,7 +45,9 @@ export const databaseRouter = createTRPCRouter({
|
||||
sql: query,
|
||||
args: [input.commentID]
|
||||
});
|
||||
return { commentReactions: results.rows };
|
||||
return {
|
||||
commentReactions: results.rows as unknown as CommentReaction[]
|
||||
};
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
@@ -32,13 +57,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
addCommentReaction: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
type: z.string(),
|
||||
comment_id: z.string(),
|
||||
user_id: z.string()
|
||||
})
|
||||
)
|
||||
.input(toggleCommentReactionMutationSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
@@ -57,7 +76,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
args: [input.comment_id]
|
||||
});
|
||||
|
||||
return { commentReactions: res.rows };
|
||||
return { commentReactions: res.rows as unknown as CommentReaction[] };
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
@@ -67,13 +86,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
removeCommentReaction: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
type: z.string(),
|
||||
comment_id: z.string(),
|
||||
user_id: z.string()
|
||||
})
|
||||
)
|
||||
.input(toggleCommentReactionMutationSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
@@ -92,7 +105,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
args: [input.comment_id]
|
||||
});
|
||||
|
||||
return { commentReactions: res.rows };
|
||||
return { commentReactions: res.rows as unknown as CommentReaction[] };
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
@@ -121,13 +134,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
deleteComment: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
commentID: z.number(),
|
||||
commenterID: z.string(),
|
||||
deletionType: z.enum(["user", "admin", "database"])
|
||||
})
|
||||
)
|
||||
.input(deleteCommentWithTypeSchema)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
@@ -238,7 +245,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
getCommentsByPostId: publicProcedure
|
||||
.input(z.object({ post_id: z.string() }))
|
||||
.input(getCommentsByPostIdSchema)
|
||||
.query(async ({ input }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
@@ -253,7 +260,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
sql: query,
|
||||
args: [input.post_id]
|
||||
});
|
||||
return { comments: res.rows };
|
||||
return { comments: res.rows as unknown as Comment[] };
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
@@ -263,12 +270,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
getPostById: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
category: z.literal("blog"),
|
||||
id: z.number()
|
||||
})
|
||||
)
|
||||
.input(getPostByIdSchema)
|
||||
.query(async ({ input }) => {
|
||||
return withCacheAndStale(
|
||||
`blog-post-id-${input.id}`,
|
||||
@@ -311,12 +313,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
getPostByTitle: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
category: z.literal("blog"),
|
||||
title: z.string()
|
||||
})
|
||||
)
|
||||
.input(getPostByTitleSchema)
|
||||
.query(async ({ input, ctx }) => {
|
||||
return withCacheAndStale(
|
||||
`blog-post-title-${input.title}`,
|
||||
@@ -510,55 +507,48 @@ export const databaseRouter = createTRPCRouter({
|
||||
}
|
||||
}),
|
||||
|
||||
deletePost: publicProcedure
|
||||
.input(z.object({ id: z.number() }))
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
deletePost: publicProcedure.input(idSchema).mutation(async ({ input }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
|
||||
await conn.execute({
|
||||
sql: "DELETE FROM Tag WHERE post_id = ?",
|
||||
args: [input.id.toString()]
|
||||
});
|
||||
await conn.execute({
|
||||
sql: "DELETE FROM Tag WHERE post_id = ?",
|
||||
args: [input.id.toString()]
|
||||
});
|
||||
|
||||
await conn.execute({
|
||||
sql: "DELETE FROM PostLike WHERE post_id = ?",
|
||||
args: [input.id.toString()]
|
||||
});
|
||||
await conn.execute({
|
||||
sql: "DELETE FROM PostLike WHERE post_id = ?",
|
||||
args: [input.id.toString()]
|
||||
});
|
||||
|
||||
await conn.execute({
|
||||
sql: "DELETE FROM Comment WHERE post_id = ?",
|
||||
args: [input.id]
|
||||
});
|
||||
await conn.execute({
|
||||
sql: "DELETE FROM Comment WHERE post_id = ?",
|
||||
args: [input.id]
|
||||
});
|
||||
|
||||
await conn.execute({
|
||||
sql: "DELETE FROM Post WHERE id = ?",
|
||||
args: [input.id]
|
||||
});
|
||||
await conn.execute({
|
||||
sql: "DELETE FROM Post WHERE id = ?",
|
||||
args: [input.id]
|
||||
});
|
||||
|
||||
cache.deleteByPrefix("blog-");
|
||||
cache.deleteByPrefix("blog-");
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to delete post"
|
||||
});
|
||||
}
|
||||
}),
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
message: "Failed to delete post"
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
// ============================================================
|
||||
// Post Likes Routes
|
||||
// ============================================================
|
||||
|
||||
addPostLike: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
user_id: z.string(),
|
||||
post_id: z.string()
|
||||
})
|
||||
)
|
||||
.input(togglePostLikeMutationSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
@@ -574,7 +564,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
args: [input.post_id]
|
||||
});
|
||||
|
||||
return { newLikes: res.rows };
|
||||
return { newLikes: res.rows as unknown as PostLike[] };
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
@@ -584,12 +574,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
removePostLike: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
user_id: z.string(),
|
||||
post_id: z.string()
|
||||
})
|
||||
)
|
||||
.input(togglePostLikeMutationSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
@@ -608,7 +593,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
args: [input.post_id]
|
||||
});
|
||||
|
||||
return { newLikes: res.rows };
|
||||
return { newLikes: res.rows as unknown as PostLike[] };
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "INTERNAL_SERVER_ERROR",
|
||||
@@ -622,7 +607,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
// ============================================================
|
||||
|
||||
getUserById: publicProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.input(getUserByIdSchema)
|
||||
.query(async ({ input }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
@@ -654,7 +639,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
getUserPublicData: publicProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.input(getUserByIdSchema)
|
||||
.query(async ({ input }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
@@ -683,7 +668,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
getUserImage: publicProcedure
|
||||
.input(z.object({ id: z.string() }))
|
||||
.input(getUserByIdSchema)
|
||||
.query(async ({ input }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
@@ -702,12 +687,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
updateUserImage: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
imageURL: z.string()
|
||||
})
|
||||
)
|
||||
.input(updateUserImageSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
@@ -729,13 +709,7 @@ export const databaseRouter = createTRPCRouter({
|
||||
}),
|
||||
|
||||
updateUserEmail: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
newEmail: z.string().email(),
|
||||
oldEmail: z.string().email()
|
||||
})
|
||||
)
|
||||
.input(updateUserEmailSchema)
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
const conn = ConnectionFactory();
|
||||
|
||||
@@ -277,6 +277,85 @@ export const paginationSchema = z.object({
|
||||
offset: z.number().min(0).default(0)
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Additional Database Router Schemas
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get post by ID or title
|
||||
*/
|
||||
export const getPostByIdSchema = z.object({
|
||||
category: z.literal("blog"),
|
||||
id: z.number()
|
||||
});
|
||||
|
||||
export const getPostByTitleSchema = z.object({
|
||||
category: z.literal("blog"),
|
||||
title: z.string()
|
||||
});
|
||||
|
||||
/**
|
||||
* Get comments by post ID
|
||||
*/
|
||||
export const getCommentsByPostIdSchema = z.object({
|
||||
post_id: z.number()
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggle post like (add/remove)
|
||||
*/
|
||||
export const togglePostLikeMutationSchema = z.object({
|
||||
user_id: z.string(),
|
||||
post_id: z.number()
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggle comment reaction (add/remove)
|
||||
*/
|
||||
export const toggleCommentReactionMutationSchema = z.object({
|
||||
type: reactionTypeSchema,
|
||||
comment_id: z.number(),
|
||||
user_id: z.string()
|
||||
});
|
||||
|
||||
/**
|
||||
* Get comment reactions
|
||||
*/
|
||||
export const getCommentReactionsQuerySchema = z.object({
|
||||
commentID: z.number()
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete comment with deletion type
|
||||
*/
|
||||
export const deleteCommentWithTypeSchema = z.object({
|
||||
commentID: z.number(),
|
||||
commenterID: z.string(),
|
||||
deletionType: z.enum(["user", "admin", "database"])
|
||||
});
|
||||
|
||||
/**
|
||||
* User query schemas
|
||||
*/
|
||||
export const getUserByIdSchema = z.object({
|
||||
id: z.string()
|
||||
});
|
||||
|
||||
export const getUserPublicDataSchema = z.object({
|
||||
id: z.string()
|
||||
});
|
||||
|
||||
export const updateUserImageSchema = z.object({
|
||||
id: z.string(),
|
||||
imageURL: z.string()
|
||||
});
|
||||
|
||||
export const updateUserEmailSchema = z.object({
|
||||
id: z.string(),
|
||||
newEmail: z.string().email(),
|
||||
oldEmail: z.string().email()
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// Type Exports
|
||||
// ============================================================================
|
||||
@@ -293,3 +372,23 @@ export type CreatePostLikeInput = z.infer<typeof createPostLikeSchema>;
|
||||
export type CreateTagInput = z.infer<typeof createTagSchema>;
|
||||
export type CreateConnectionInput = z.infer<typeof createConnectionSchema>;
|
||||
export type PaginationInput = z.infer<typeof paginationSchema>;
|
||||
export type GetPostByIdInput = z.infer<typeof getPostByIdSchema>;
|
||||
export type GetPostByTitleInput = z.infer<typeof getPostByTitleSchema>;
|
||||
export type GetCommentsByPostIdInput = z.infer<
|
||||
typeof getCommentsByPostIdSchema
|
||||
>;
|
||||
export type TogglePostLikeMutationInput = z.infer<
|
||||
typeof togglePostLikeMutationSchema
|
||||
>;
|
||||
export type ToggleCommentReactionMutationInput = z.infer<
|
||||
typeof toggleCommentReactionMutationSchema
|
||||
>;
|
||||
export type GetCommentReactionsQueryInput = z.infer<
|
||||
typeof getCommentReactionsQuerySchema
|
||||
>;
|
||||
export type DeleteCommentWithTypeInput = z.infer<
|
||||
typeof deleteCommentWithTypeSchema
|
||||
>;
|
||||
export type GetUserByIdInput = z.infer<typeof getUserByIdSchema>;
|
||||
export type UpdateUserImageInput = z.infer<typeof updateUserImageSchema>;
|
||||
export type UpdateUserEmailInput = z.infer<typeof updateUserEmailSchema>;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// lineage User
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string | null;
|
||||
|
||||
Reference in New Issue
Block a user