feat: new table and crud operations

This commit is contained in:
Lorenzo Migliorero
2024-07-25 15:19:03 +02:00
parent 083bb7b87d
commit f866250c25
12 changed files with 733 additions and 0 deletions

View File

@@ -23,6 +23,7 @@ import { redisRouter } from "./routers/redis";
import { registryRouter } from "./routers/registry";
import { securityRouter } from "./routers/security";
import { settingsRouter } from "./routers/settings";
import { sshRouter } from "./routers/ssh-key";
import { userRouter } from "./routers/user";
/**
@@ -56,6 +57,7 @@ export const appRouter = createTRPCRouter({
registry: registryRouter,
cluster: clusterRouter,
notification: notificationRouter,
sshKey: sshRouter,
});
// export type definition of API

View File

@@ -0,0 +1,63 @@
import {
adminProcedure,
createTRPCRouter,
protectedProcedure,
} from "@/server/api/trpc";
import { db } from "@/server/db";
import {
apiCreateSshKey,
apiFindOneSshKey,
apiRemoveSshKey,
apiUpdateSshKey,
} from "@/server/db/schema";
import { TRPCError } from "@trpc/server";
import {
createSshKey,
findSSHKeyById,
removeSSHKeyById,
updateSSHKeyById,
} from "../services/ssh-key";
export const sshRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateSshKey)
.mutation(async ({ input }) => {
try {
await createSshKey(input);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the ssh key",
cause: error,
});
}
}),
remove: adminProcedure.input(apiRemoveSshKey).mutation(async ({ input }) => {
try {
return await removeSSHKeyById(input.sshKeyId);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to delete this ssh key",
});
}
}),
one: protectedProcedure.input(apiFindOneSshKey).query(async ({ input }) => {
const sshKey = await findSSHKeyById(input.sshKeyId);
return sshKey;
}),
all: adminProcedure.query(async () => {
return await db.query.sshKeys.findMany({});
}),
update: adminProcedure.input(apiUpdateSshKey).mutation(async ({ input }) => {
try {
return await updateSSHKeyById(input);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to update this ssh key",
cause: error,
});
}
}),
});

View File

@@ -0,0 +1,70 @@
import { db } from "@/server/db";
import {
type apiCreateSshKey,
type apiFindOneSshKey,
type apiRemoveSshKey,
type apiUpdateSshKey,
sshKeys,
} from "@/server/db/schema";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
export const createSshKey = async ({
privateKey,
...input
}: typeof apiCreateSshKey._type) => {
await db.transaction(async (tx) => {
const sshKey = await tx
.insert(sshKeys)
.values(input)
.returning()
.then((response) => response[0])
.catch((e) => console.error(e));
if (!sshKey) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the ssh key",
});
}
return sshKey;
});
};
export const removeSSHKeyById = async (
sshKeyId: (typeof apiRemoveSshKey._type)["sshKeyId"],
) => {
const result = await db
.delete(sshKeys)
.where(eq(sshKeys.sshKeyId, sshKeyId))
.returning();
return result[0];
};
export const updateSSHKeyById = async ({
sshKeyId,
...input
}: typeof apiUpdateSshKey._type) => {
const result = await db
.update(sshKeys)
.set(input)
.where(eq(sshKeys.sshKeyId, sshKeyId))
.returning();
return result[0];
};
export const findSSHKeyById = async (
sshKeyId: (typeof apiFindOneSshKey._type)["sshKeyId"],
) => {
const sshKey = await db.query.sshKeys.findFirst({
where: eq(sshKeys.sshKeyId, sshKeyId),
});
if (!sshKey) {
throw new TRPCError({
code: "NOT_FOUND",
message: "SSH Key not found",
});
}
return sshKey;
};

View File

@@ -22,3 +22,4 @@ export * from "./shared";
export * from "./compose";
export * from "./registry";
export * from "./notification";
export * from "./ssh-key";

View File

@@ -0,0 +1,57 @@
import { sshKeyCreate } from "@/server/db/validations";
import { pgTable, text, time } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
export const sshKeys = pgTable("ssh-key", {
sshKeyId: text("sshKeyId")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
publicKey: text("publicKey").notNull(),
name: text("name").notNull(),
description: text("description"),
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
lastUsedAt: text("lastUsedAt"),
});
const createSchema = createInsertSchema(
sshKeys,
/* Private key is not stored in the DB */
sshKeyCreate.omit({ privateKey: true }).shape,
);
export const apiCreateSshKey = createSchema
.pick({
name: true,
description: true,
publicKey: true,
})
.merge(sshKeyCreate.pick({ privateKey: true }));
export const apiFindOneSshKey = createSchema
.pick({
sshKeyId: true,
})
.required();
export const apiRemoveSshKey = createSchema
.pick({
sshKeyId: true,
})
.required();
export const apiUpdateSshKey = createSchema
.pick({
name: true,
description: true,
})
.merge(
createSchema
.pick({
sshKeyId: true,
})
.required(),
);

View File

@@ -0,0 +1,22 @@
import { z } from "zod";
export const sshKeyCreate = z.object({
name: z.string().min(1),
description: z.string().optional(),
publicKey: z.string().regex(/^ssh-rsa\s+([A-Za-z0-9+/=]+)\s*(.*)?$/, {
message: "Invalid format",
}),
privateKey: z
.string()
.regex(
/^-----BEGIN RSA PRIVATE KEY-----\n([A-Za-z0-9+/=\n]+)-----END RSA PRIVATE KEY-----$/,
{
message: "Invalid format",
},
),
});
export const sshKeyUpdate = sshKeyCreate.pick({
name: true,
description: true,
});