Skip to content

Commit

Permalink
feat: add valtown driver (#81)
Browse files Browse the repository at this point in the history
  • Loading branch information
invisal authored Apr 7, 2024
1 parent fbc6ed3 commit f5cd43f
Show file tree
Hide file tree
Showing 11 changed files with 294 additions and 113 deletions.
13 changes: 13 additions & 0 deletions public/valtown.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 3 additions & 2 deletions src/app/api/database/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ const databaseSchema = zod.object({
name: zod.string().min(3).max(50),
description: zod.string(),
label: zod.enum(["gray", "red", "yellow", "green", "blue"]),
driver: zod.enum(["turso", "rqlite"]),
driver: zod.enum(["turso", "rqlite", "valtown"]),
config: zod.object({
url: zod.string().min(5),
url: zod.string().optional(),
token: zod.string().optional(),
username: zod.string().optional(),
password: zod.string().optional(),
Expand Down Expand Up @@ -101,6 +101,7 @@ export const POST = withUser(async ({ user, req }) => {
success: true,
data: {
id: databaseId,
driver: data.driver,
name: data.name,
label: data.label,
description: data.description,
Expand Down
5 changes: 5 additions & 0 deletions src/app/api/ops/[database_id]/turso-edge-client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { database } from "@/db/schema";
import RqliteDriver from "@/drivers/rqlite-driver";
import TursoDriver from "@/drivers/turso-driver";
import ValtownDriver from "@/drivers/valtown-driver";
import { env } from "@/env";
import { decrypt } from "@/lib/encryption-edge";

Expand All @@ -13,6 +14,10 @@ export async function createTursoEdgeDriver(db: typeof database.$inferSelect) {
db.username ? await decrypt(env.ENCRYPTION_KEY, db.username) : "",
db.password ? await decrypt(env.ENCRYPTION_KEY, db.password) : ""
);
} else if (db.driver === "valtown") {
return new ValtownDriver(
db.token ? await decrypt(env.ENCRYPTION_KEY, db.token) : ""
);
}

const token = db.token
Expand Down
3 changes: 3 additions & 0 deletions src/app/client/[[...driver]]/page-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from "../../connect/saved-connection-storage";
import RqliteDriver from "@/drivers/rqlite-driver";
import { DatabaseDriverProvider } from "@/context/DatabaseDriverProvider";
import ValtownDriver from "@/drivers/valtown-driver";

export default function ClientPageBody() {
const driver = useMemo(() => {
Expand All @@ -20,6 +21,8 @@ export default function ClientPageBody() {

if (config.driver === "rqlite") {
return new RqliteDriver(config.url, config.username, config.password);
} else if (config.driver === "valtown") {
return new ValtownDriver(config.token);
}
return new TursoDriver(config.url, config.token as string, true);
}, []);
Expand Down
3 changes: 3 additions & 0 deletions src/app/client/s/[[...driver]]/page-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { DatabaseDriverProvider } from "@/context/DatabaseDriverProvider";
import { ConnectionConfigProvider } from "@/context/connection-config-provider";
import RqliteDriver from "@/drivers/rqlite-driver";
import TursoDriver from "@/drivers/turso-driver";
import ValtownDriver from "@/drivers/valtown-driver";
import { useSearchParams } from "next/navigation";
import { useMemo } from "react";

Expand All @@ -27,6 +28,8 @@ export default function ClientPageBody() {
conn.config.username,
conn.config.password
);
} else if (conn.driver === "valtown") {
return new ValtownDriver(conn.config.token);
}

return new TursoDriver(conn.config.url, conn.config.token, true);
Expand Down
95 changes: 38 additions & 57 deletions src/app/connect/connection-string-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,85 +20,66 @@ export default function ConnectionStringInput({
autoFocus?: boolean;
}>) {
const driverDetail = DRIVER_DETAIL[driver];
const authType = driver === "turso" ? "token" : "username";
const endpointError = driverDetail.invalidateEndpoint(value.url);

return (
<>
<div>
<div className="text-xs mb-2 font-semibold">URL (*)</div>
<Input
autoFocus={autoFocus}
placeholder={"URL"}
value={value.url}
onChange={(e) => {
onChange({ ...value, url: e.currentTarget.value });
}}
/>
{endpointError && (
<div className="text-xs mt-2 text-red-400">{endpointError}</div>
)}
<div className="text-xs mt-2">{driverDetail.endpointExample}</div>
</div>
{driverDetail.fields.map((field, idx) => {
let inputDom = <div></div>;
const error = field.invalidate
? field.invalidate(value[field.name] ?? "")
: null;

{authType === "token" && (
<div>
<div className="text-xs mb-2 font-semibold">Token</div>
<Textarea
placeholder={
showLockedCredential && !value.token ? "✱✱✱✱✱✱✱✱✱" : "Token"
}
className={
showLockedCredential && !value.token ? "bg-secondary" : ""
}
value={value.token}
onChange={(e) => {
onChange({ ...value, token: e.currentTarget.value });
}}
/>
</div>
)}

{authType === "username" && (
<>
<div>
<div className="text-xs mb-2 font-semibold">Username</div>
if (field.type === "text" || field.type === "password") {
inputDom = (
<Input
type="username"
type={field.type}
placeholder={
showLockedCredential && !value.username
showLockedCredential && !value[field.name]
? "✱✱✱✱✱✱✱✱✱"
: "Username"
: field.placeholder
}
className={
showLockedCredential && !value.username ? "bg-secondary" : ""
showLockedCredential && !value[field.name] ? "bg-secondary" : ""
}
value={value.username}
autoFocus={autoFocus && idx === 0}
value={value[field.name]}
onChange={(e) => {
onChange({ ...value, username: e.currentTarget.value });
onChange({ ...value, [field.name]: e.currentTarget.value });
}}
/>
</div>
<div>
<div className="text-xs mb-2 font-semibold">Password</div>
<Input
type="password"
);
} else if (field.type === "textarea") {
inputDom = (
<Textarea
placeholder={
showLockedCredential && !value.password
showLockedCredential && !value[field.name]
? "✱✱✱✱✱✱✱✱✱"
: "Password"
: "Token"
}
className={
showLockedCredential && !value.password ? "bg-secondary" : ""
showLockedCredential && !value[field.name] ? "bg-secondary" : ""
}
value={value.password}
value={value[field.name]}
onChange={(e) => {
onChange({ ...value, password: e.currentTarget.value });
onChange({ ...value, [field.name]: e.currentTarget.value });
}}
/>
);
}

return (
<div key={driverDetail.name}>
<div className="text-xs mb-2 font-semibold">
{field.title} {field.required && <span>(*)</span>}
</div>
{inputDom}
{error && <div className="text-xs mt-2 text-red-400">{error}</div>}
{field.description && (
<div className="text-xs mt-2">{field.description}</div>
)}
</div>
</>
)}
);
})}
</>
);
}
15 changes: 14 additions & 1 deletion src/app/connect/driver-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default function DriverDropdown({
}}
>
<div className="flex gap-4 px-2 items-center h-12">
<img src="/rqlite.png" alt="turso" className="w-9 h-9" />
<img src="/rqlite.png" alt="rqlite" className="w-9 h-9" />
<div>
<div className="font-bold">rqlite</div>
<div className="text-xs opacity-50">
Expand All @@ -47,6 +47,19 @@ export default function DriverDropdown({
</div>
</div>
</DropdownMenuItem>
<DropdownMenuItem
onClick={() => {
onSelect("valtown");
}}
>
<div className="flex gap-4 px-2 items-center h-12">
<img src="/valtown.svg" alt="valtown" className="w-9 h-9 rounded" />
<div>
<div className="font-bold">val.town</div>
<div className="text-xs opacity-50">Private SQLite database</div>
</div>
</div>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
Expand Down
18 changes: 10 additions & 8 deletions src/app/connect/quick-connect.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { Button } from "@/components/ui/button";
import ConnectionDialogContent from "./saved-connection-content";
import { useCallback, useState } from "react";
import { useCallback, useMemo, useState } from "react";
import useConnect from "@/hooks/use-connect";
import {
DRIVER_DETAIL,
SavedConnectionItemConfigConfig,
SupportedDriver,
prefillConnectionString,
validateConnectionString,
} from "./saved-connection-storage";
import { RqliteInstruction } from "./saved-connection";
import ConnectionStringInput from "./connection-string-input";
Expand All @@ -16,15 +18,15 @@ export default function QuickConnect({
}: Readonly<{ onClose: () => void; driver: SupportedDriver }>) {
const driverDetail = DRIVER_DETAIL[driver ?? "turso"];
const [connectionConfig, setConnectionConfig] =
useState<SavedConnectionItemConfigConfig>({
url: DRIVER_DETAIL[driver ?? "turso"].prefill,
token: "",
username: "",
password: "",
});
useState<SavedConnectionItemConfigConfig>(() =>
prefillConnectionString(driverDetail)
);

const connect = useConnect();
const valid = !driverDetail.invalidateEndpoint(connectionConfig.url);

const valid = useMemo(() => {
return validateConnectionString(driverDetail, connectionConfig);
}, [connectionConfig, driverDetail]);

const onConnect = useCallback(() => {
connect(driver, {
Expand Down
17 changes: 9 additions & 8 deletions src/app/connect/saved-connection-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import {
SavedConnectionItemConfigConfig,
SavedConnectionLabel,
SupportedDriver,
prefillConnectionString,
validateConnectionString,
} from "@/app/connect/saved-connection-storage";
import { cn } from "@/lib/utils";
import { useState } from "react";
import { useMemo, useState } from "react";
import { LucideLoader } from "lucide-react";
import ConnectionStringInput from "./connection-string-input";

Expand Down Expand Up @@ -56,12 +58,9 @@ export default function SavedConnectionConfig({
);

const [connectionString, setConnectionString] =
useState<SavedConnectionItemConfigConfig>({
url: initialData?.config?.url ?? driverDetail.prefill,
token: initialData?.config?.token ?? "",
username: initialData?.config?.username ?? "",
password: initialData?.config?.password ?? "",
});
useState<SavedConnectionItemConfigConfig>(() =>
prefillConnectionString(driverDetail, initialData?.config)
);

const onSaveClicked = () => {
onSave({
Expand All @@ -73,7 +72,9 @@ export default function SavedConnectionConfig({
});
};

const valid = !driverDetail.invalidateEndpoint(connectionString.url);
const valid = useMemo(() => {
return validateConnectionString(driverDetail, connectionString);
}, [connectionString, driverDetail]);

return (
<>
Expand Down
Loading

0 comments on commit f5cd43f

Please sign in to comment.