mirror of
https://github.com/hex248/sprint.git
synced 2026-02-07 18:23:03 +00:00
verification emails and full email setup
This commit is contained in:
@@ -60,12 +60,21 @@ export const AuthResponseSchema = z.object({
|
||||
username: z.string(),
|
||||
avatarURL: z.string().nullable(),
|
||||
iconPreference: z.enum(["lucide", "pixel", "phosphor"]),
|
||||
emailVerified: z.boolean(),
|
||||
}),
|
||||
csrfToken: z.string(),
|
||||
});
|
||||
|
||||
export type AuthResponse = z.infer<typeof AuthResponseSchema>;
|
||||
|
||||
// email verification schemas
|
||||
|
||||
export const VerifyEmailRequestSchema = z.object({
|
||||
code: z.string().length(6, "Verification code must be 6 digits"),
|
||||
});
|
||||
|
||||
export type VerifyEmailRequest = z.infer<typeof VerifyEmailRequestSchema>;
|
||||
|
||||
// issue schemas
|
||||
|
||||
export const IssueCreateRequestSchema = z.object({
|
||||
|
||||
@@ -649,6 +649,30 @@ export const apiContract = c.router({
|
||||
500: ApiErrorSchema,
|
||||
},
|
||||
},
|
||||
|
||||
authVerifyEmail: {
|
||||
method: "POST",
|
||||
path: "/auth/verify-email",
|
||||
body: z.object({ code: z.string() }),
|
||||
responses: {
|
||||
200: SuccessResponseSchema,
|
||||
400: ApiErrorSchema,
|
||||
401: ApiErrorSchema,
|
||||
404: ApiErrorSchema,
|
||||
},
|
||||
headers: csrfHeaderSchema,
|
||||
},
|
||||
authResendVerification: {
|
||||
method: "POST",
|
||||
path: "/auth/resend-verification",
|
||||
body: emptyBodySchema,
|
||||
responses: {
|
||||
200: SuccessResponseSchema,
|
||||
400: ApiErrorSchema,
|
||||
401: ApiErrorSchema,
|
||||
},
|
||||
headers: csrfHeaderSchema,
|
||||
},
|
||||
});
|
||||
|
||||
export type ApiContract = typeof apiContract;
|
||||
|
||||
@@ -63,6 +63,7 @@ export type {
|
||||
UserByUsernameQuery,
|
||||
UserResponse,
|
||||
UserUpdateRequest,
|
||||
VerifyEmailRequest,
|
||||
} from "./api-schemas";
|
||||
// API schemas
|
||||
export {
|
||||
@@ -133,6 +134,7 @@ export {
|
||||
UserByUsernameQuerySchema,
|
||||
UserResponseSchema,
|
||||
UserUpdateRequestSchema,
|
||||
VerifyEmailRequestSchema,
|
||||
} from "./api-schemas";
|
||||
export {
|
||||
ISSUE_COMMENT_MAX_LENGTH,
|
||||
@@ -153,6 +155,10 @@ export {
|
||||
export type { ApiContract } from "./contract";
|
||||
export { apiContract } from "./contract";
|
||||
export type {
|
||||
EmailJobInsert,
|
||||
EmailJobRecord,
|
||||
EmailVerificationInsert,
|
||||
EmailVerificationRecord,
|
||||
IconStyle,
|
||||
IssueAssigneeInsert,
|
||||
IssueAssigneeRecord,
|
||||
@@ -191,6 +197,12 @@ export {
|
||||
DEFAULT_SPRINT_COLOUR,
|
||||
DEFAULT_STATUS_COLOUR,
|
||||
DEFAULT_STATUS_COLOURS,
|
||||
EmailJob,
|
||||
EmailJobInsertSchema,
|
||||
EmailJobSelectSchema,
|
||||
EmailVerification,
|
||||
EmailVerificationInsertSchema,
|
||||
EmailVerificationSelectSchema,
|
||||
Issue,
|
||||
IssueAssignee,
|
||||
IssueAssigneeInsertSchema,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { boolean, integer, json, pgTable, timestamp, uniqueIndex, varchar } from "drizzle-orm/pg-core";
|
||||
import { boolean, integer, json, pgTable, text, timestamp, uniqueIndex, varchar } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
|
||||
import type { z } from "zod";
|
||||
import {
|
||||
@@ -62,6 +62,8 @@ export const User = pgTable("User", {
|
||||
avatarURL: varchar({ length: 512 }),
|
||||
iconPreference: varchar({ length: 10 }).notNull().default("pixel").$type<IconStyle>(),
|
||||
plan: varchar({ length: 32 }).notNull().default("free"),
|
||||
emailVerified: boolean().notNull().default(false),
|
||||
emailVerifiedAt: timestamp({ withTimezone: false }),
|
||||
createdAt: timestamp({ withTimezone: false }).defaultNow(),
|
||||
updatedAt: timestamp({ withTimezone: false }).defaultNow(),
|
||||
});
|
||||
@@ -195,7 +197,6 @@ export const IssueComment = pgTable("IssueComment", {
|
||||
updatedAt: timestamp({ withTimezone: false }).defaultNow(),
|
||||
});
|
||||
|
||||
// Zod schemas
|
||||
export const UserSelectSchema = createSelectSchema(User);
|
||||
export const UserInsertSchema = createInsertSchema(User);
|
||||
|
||||
@@ -226,7 +227,6 @@ export const SessionInsertSchema = createInsertSchema(Session);
|
||||
export const TimedSessionSelectSchema = createSelectSchema(TimedSession);
|
||||
export const TimedSessionInsertSchema = createInsertSchema(TimedSession);
|
||||
|
||||
// Types
|
||||
export type UserRecord = z.infer<typeof UserSelectSchema>;
|
||||
export type UserInsert = z.infer<typeof UserInsertSchema>;
|
||||
|
||||
@@ -260,8 +260,6 @@ export type SessionInsert = z.infer<typeof SessionInsertSchema>;
|
||||
export type TimedSessionRecord = z.infer<typeof TimedSessionSelectSchema>;
|
||||
export type TimedSessionInsert = z.infer<typeof TimedSessionInsertSchema>;
|
||||
|
||||
// Responses
|
||||
|
||||
export type IssueResponse = {
|
||||
Issue: IssueRecord;
|
||||
Creator: UserRecord;
|
||||
@@ -299,7 +297,6 @@ export type TimerState = {
|
||||
endedAt: string | null;
|
||||
} | null;
|
||||
|
||||
// Subscription table - tracks user subscriptions
|
||||
export const Subscription = pgTable("Subscription", {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
userId: integer()
|
||||
@@ -319,7 +316,6 @@ export const Subscription = pgTable("Subscription", {
|
||||
updatedAt: timestamp({ withTimezone: false }).defaultNow(),
|
||||
});
|
||||
|
||||
// Payment history table
|
||||
export const Payment = pgTable("Payment", {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
subscriptionId: integer()
|
||||
@@ -332,16 +328,53 @@ export const Payment = pgTable("Payment", {
|
||||
createdAt: timestamp({ withTimezone: false }).defaultNow(),
|
||||
});
|
||||
|
||||
// Zod schemas for Subscription and Payment
|
||||
export const SubscriptionSelectSchema = createSelectSchema(Subscription);
|
||||
export const SubscriptionInsertSchema = createInsertSchema(Subscription);
|
||||
|
||||
export const PaymentSelectSchema = createSelectSchema(Payment);
|
||||
export const PaymentInsertSchema = createInsertSchema(Payment);
|
||||
|
||||
// Types for Subscription and Payment
|
||||
export type SubscriptionRecord = z.infer<typeof SubscriptionSelectSchema>;
|
||||
export type SubscriptionInsert = z.infer<typeof SubscriptionInsertSchema>;
|
||||
|
||||
export type PaymentRecord = z.infer<typeof PaymentSelectSchema>;
|
||||
export type PaymentInsert = z.infer<typeof PaymentInsertSchema>;
|
||||
|
||||
export const EmailVerification = pgTable("EmailVerification", {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
userId: integer()
|
||||
.notNull()
|
||||
.references(() => User.id, { onDelete: "cascade" }),
|
||||
code: varchar({ length: 6 }).notNull(),
|
||||
attempts: integer().notNull().default(0),
|
||||
maxAttempts: integer().notNull().default(5),
|
||||
expiresAt: timestamp({ withTimezone: false }).notNull(),
|
||||
verifiedAt: timestamp({ withTimezone: false }),
|
||||
createdAt: timestamp({ withTimezone: false }).defaultNow(),
|
||||
});
|
||||
|
||||
export const EmailVerificationSelectSchema = createSelectSchema(EmailVerification);
|
||||
export const EmailVerificationInsertSchema = createInsertSchema(EmailVerification);
|
||||
|
||||
export type EmailVerificationRecord = z.infer<typeof EmailVerificationSelectSchema>;
|
||||
export type EmailVerificationInsert = z.infer<typeof EmailVerificationInsertSchema>;
|
||||
|
||||
export const EmailJob = pgTable("EmailJob", {
|
||||
id: integer().primaryKey().generatedAlwaysAsIdentity(),
|
||||
userId: integer()
|
||||
.notNull()
|
||||
.references(() => User.id, { onDelete: "cascade" }),
|
||||
type: varchar({ length: 64 }).notNull(),
|
||||
scheduledFor: timestamp({ withTimezone: false }).notNull(),
|
||||
sentAt: timestamp({ withTimezone: false }),
|
||||
failedAt: timestamp({ withTimezone: false }),
|
||||
errorMessage: text(),
|
||||
metadata: json("metadata").$type<Record<string, unknown>>(),
|
||||
createdAt: timestamp({ withTimezone: false }).defaultNow(),
|
||||
});
|
||||
|
||||
export const EmailJobSelectSchema = createSelectSchema(EmailJob);
|
||||
export const EmailJobInsertSchema = createInsertSchema(EmailJob);
|
||||
|
||||
export type EmailJobRecord = z.infer<typeof EmailJobSelectSchema>;
|
||||
export type EmailJobInsert = z.infer<typeof EmailJobInsertSchema>;
|
||||
|
||||
Reference in New Issue
Block a user