mirror of
https://github.com/akazwz/smail.git
synced 2026-05-06 22:01:09 +08:00
1. add indexes to optimise query performance
2. add password protected
This commit is contained in:
25
app/components/ui/input.tsx
Normal file
25
app/components/ui/input.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
Input.displayName = "Input";
|
||||
|
||||
export { Input };
|
||||
24
app/components/ui/label.tsx
Normal file
24
app/components/ui/label.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as React from "react";
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "~/lib/utils";
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
);
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Label.displayName = LabelPrimitive.Root.displayName;
|
||||
|
||||
export { Label };
|
||||
198
app/drizzle/meta/0001_snapshot.json
Normal file
198
app/drizzle/meta/0001_snapshot.json
Normal file
@@ -0,0 +1,198 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "7dee99c9-e3ff-4dc0-bbd8-060f5e974163",
|
||||
"prevId": "79e43276-dcf0-4e97-81da-cd758f0cf38a",
|
||||
"tables": {
|
||||
"emails": {
|
||||
"name": "emails",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"domain": {
|
||||
"name": "domain",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"message_from": {
|
||||
"name": "message_from",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"message_to": {
|
||||
"name": "message_to",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"headers": {
|
||||
"name": "headers",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"from": {
|
||||
"name": "from",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"sender": {
|
||||
"name": "sender",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"reply_to": {
|
||||
"name": "reply_to",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"delivered_to": {
|
||||
"name": "delivered_to",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"return_path": {
|
||||
"name": "return_path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"to": {
|
||||
"name": "to",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"cc": {
|
||||
"name": "cc",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"bcc": {
|
||||
"name": "bcc",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"subject": {
|
||||
"name": "subject",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"message_id": {
|
||||
"name": "message_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"in_reply_to": {
|
||||
"name": "in_reply_to",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"references": {
|
||||
"name": "references",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"date": {
|
||||
"name": "date",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"html": {
|
||||
"name": "html",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"text": {
|
||||
"name": "text",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"attachments": {
|
||||
"name": "attachments",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"message_to_idx": {
|
||||
"name": "message_to_idx",
|
||||
"columns": ["message_to"],
|
||||
"isUnique": false
|
||||
},
|
||||
"created_at_idx": {
|
||||
"name": "created_at_idx",
|
||||
"columns": ["created_at"],
|
||||
"isUnique": false
|
||||
}
|
||||
},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {}
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
72
app/routes/_h.$(lang).auth.tsx
Normal file
72
app/routes/_h.$(lang).auth.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import type {
|
||||
ActionFunctionArgs,
|
||||
LoaderFunctionArgs,
|
||||
} from "@remix-run/cloudflare";
|
||||
import {
|
||||
Form,
|
||||
redirect,
|
||||
useActionData,
|
||||
useLoaderData,
|
||||
useNavigation,
|
||||
} from "@remix-run/react";
|
||||
import { LockKeyholeIcon } from "lucide-react";
|
||||
import { sessionWrapper } from "~/.server/session";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Input } from "~/components/ui/input";
|
||||
import { Label } from "~/components/ui/label";
|
||||
import { getLocaleData } from "~/locales/locale";
|
||||
|
||||
export async function loader({ params }: LoaderFunctionArgs) {
|
||||
const lang = params.lang || "en";
|
||||
const locale = await getLocaleData(lang);
|
||||
return {
|
||||
locale,
|
||||
};
|
||||
}
|
||||
|
||||
export async function action({ request, context }: ActionFunctionArgs) {
|
||||
const password = (await request.formData()).get("password") as string;
|
||||
if (password === context.cloudflare.env.PASSWORD) {
|
||||
const { getSession, commitSession } = sessionWrapper(
|
||||
context.cloudflare.env,
|
||||
);
|
||||
const session = await getSession(request.headers.get("Cookie"));
|
||||
session.set("password", password);
|
||||
return redirect("/", {
|
||||
headers: {
|
||||
"Set-Cookie": await commitSession(session),
|
||||
},
|
||||
});
|
||||
}
|
||||
return {
|
||||
error: true,
|
||||
};
|
||||
}
|
||||
|
||||
export default function Auth() {
|
||||
const { locale } = useLoaderData<typeof loader>();
|
||||
const actionData = useActionData<typeof action>();
|
||||
|
||||
const navigation = useNavigation();
|
||||
|
||||
return (
|
||||
<div className="p-2">
|
||||
<Form
|
||||
method="POST"
|
||||
className="flex flex-col w-full max-w-md mx-auto gap-4"
|
||||
>
|
||||
<LockKeyholeIcon strokeWidth="1.5px" className="size-8" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label htmlFor="password">{locale.auth.title}</Label>
|
||||
<Input id="password" name="password" type="password" required />
|
||||
{actionData?.error && (
|
||||
<div className="text-destructive text-xs">{locale.auth.msg}</div>
|
||||
)}
|
||||
</div>
|
||||
<Button disabled={navigation.state === "submitting"}>
|
||||
{locale.auth.submit}
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user