feat: initial commit

This commit is contained in:
Mauricio Siu
2024-04-28 23:57:52 -06:00
parent 8857a20344
commit be56ba046c
412 changed files with 60777 additions and 1 deletions

166
server/api/routers/admin.ts Normal file
View File

@@ -0,0 +1,166 @@
import {
apiAssignPermissions,
apiCreateUserInvitation,
apiFindOneToken,
apiGetBranches,
apiRemoveUser,
users,
} from "@/server/db/schema";
import {
createInvitation,
findAdmin,
getUserByToken,
removeUserByAuthId,
updateAdmin,
} from "../services/admin";
import {
adminProcedure,
createTRPCRouter,
protectedProcedure,
publicProcedure,
} from "../trpc";
import { db } from "@/server/db";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { Octokit } from "octokit";
import { createAppAuth } from "@octokit/auth-app";
import { haveGithubRequirements } from "@/server/utils/providers/github";
export const adminRouter = createTRPCRouter({
one: adminProcedure.query(async () => {
const { sshPrivateKey, ...rest } = await findAdmin();
return {
haveSSH: !!sshPrivateKey,
...rest,
};
}),
createUserInvitation: adminProcedure
.input(apiCreateUserInvitation)
.mutation(async ({ input }) => {
try {
await createInvitation(input);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message:
"Error to create this user\ncheck if the email is not registered",
cause: error,
});
}
}),
removeUser: adminProcedure
.input(apiRemoveUser)
.mutation(async ({ input }) => {
try {
return await removeUserByAuthId(input.authId);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to delete this user",
cause: error,
});
}
}),
getUserByToken: publicProcedure
.input(apiFindOneToken)
.query(async ({ input }) => {
return await getUserByToken(input.token);
}),
assignPermissions: adminProcedure
.input(apiAssignPermissions)
.mutation(async ({ input }) => {
try {
await db
.update(users)
.set({
...input,
})
.where(eq(users.userId, input.userId));
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to assign permissions",
});
}
}),
cleanGithubApp: adminProcedure.mutation(async ({ ctx }) => {
try {
return await updateAdmin(ctx.user.authId, {
githubAppName: "",
githubClientId: "",
githubClientSecret: "",
githubInstallationId: "",
});
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to delete this github app",
cause: error,
});
}
}),
getRepositories: protectedProcedure.query(async () => {
const admin = await findAdmin();
const completeRequirements = haveGithubRequirements(admin);
if (!completeRequirements) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Admin need to setup correctly github account",
});
}
const octokit = new Octokit({
authStrategy: createAppAuth,
auth: {
appId: admin.githubAppId,
privateKey: admin.githubPrivateKey,
installationId: admin.githubInstallationId,
},
});
const repositories = (await octokit.paginate(
octokit.rest.apps.listReposAccessibleToInstallation,
)) as unknown as Awaited<
ReturnType<typeof octokit.rest.apps.listReposAccessibleToInstallation>
>["data"]["repositories"];
return repositories;
}),
getBranches: protectedProcedure
.input(apiGetBranches)
.query(async ({ input }) => {
const admin = await findAdmin();
const completeRequirements = haveGithubRequirements(admin);
if (!completeRequirements) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Admin need to setup correctly github account",
});
}
const octokit = new Octokit({
authStrategy: createAppAuth,
auth: {
appId: admin.githubAppId,
privateKey: admin.githubPrivateKey,
installationId: admin.githubInstallationId,
},
});
const branches = await octokit.rest.repos.listBranches({
owner: input.owner,
repo: input.repo,
});
return branches.data;
}),
haveGithubConfigured: protectedProcedure.query(async () => {
const adminResponse = await findAdmin();
return haveGithubRequirements(adminResponse);
}),
});

View File

@@ -0,0 +1,339 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { db } from "@/server/db";
import {
apiCreateApplication,
apiFindMonitoringStats,
apiFindOneApplication,
apiReloadApplication,
apiSaveBuildType,
apiSaveDockerProvider,
apiSaveEnviromentVariables,
apiSaveGitProvider,
apiSaveGithubProvider,
apiUpdateApplication,
applications,
} from "@/server/db/schema/application";
import {
cleanQueuesByApplication,
type DeploymentJob,
} from "@/server/queues/deployments-queue";
import { myQueue } from "@/server/queues/queueSetup";
import {
removeService,
startService,
stopService,
} from "@/server/utils/docker/utils";
import {
removeDirectoryCode,
removeMonitoringDirectory,
} from "@/server/utils/filesystem/directory";
import {
generateSSHKey,
readRSAFile,
removeRSAFiles,
} from "@/server/utils/filesystem/ssh";
import {
readConfig,
removeTraefikConfig,
writeConfig,
} from "@/server/utils/traefik/application";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import {
createApplication,
findApplicationById,
getApplicationStats,
updateApplication,
updateApplicationStatus,
} from "../services/application";
import { removeDeployments } from "../services/deployment";
import { deleteAllMiddlewares } from "@/server/utils/traefik/middleware";
import { z } from "zod";
import { nanoid } from "nanoid";
import { addNewService, checkServiceAccess } from "../services/user";
export const applicationRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateApplication)
.mutation(async ({ input, ctx }) => {
try {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
}
const newApplication = await createApplication(input);
if (ctx.user.rol === "user") {
await addNewService(ctx.user.authId, newApplication.applicationId);
}
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the application",
cause: error,
});
}
}),
one: protectedProcedure
.input(apiFindOneApplication)
.query(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(
ctx.user.authId,
input.applicationId,
"access",
);
}
return await findApplicationById(input.applicationId);
}),
reload: protectedProcedure
.input(apiReloadApplication)
.mutation(async ({ input }) => {
await stopService(input.appName);
await updateApplicationStatus(input.applicationId, "idle");
await startService(input.appName);
await updateApplicationStatus(input.applicationId, "done");
return true;
}),
delete: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(
ctx.user.authId,
input.applicationId,
"delete",
);
}
const application = await findApplicationById(input.applicationId);
const result = await db
.delete(applications)
.where(eq(applications.applicationId, input.applicationId))
.returning();
const cleanupOperations = [
async () => deleteAllMiddlewares(application),
async () => await removeDeployments(application),
async () => await removeDirectoryCode(application?.appName),
async () => await removeMonitoringDirectory(application?.appName),
async () => await removeTraefikConfig(application?.appName),
async () => await removeService(application?.appName),
async () => await removeRSAFiles(application?.appName),
];
for (const operation of cleanupOperations) {
try {
await operation();
} catch (error) {}
}
return result[0];
}),
stop: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input }) => {
const service = await findApplicationById(input.applicationId);
await stopService(service.appName);
await updateApplicationStatus(input.applicationId, "idle");
return service;
}),
start: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input }) => {
const service = await findApplicationById(input.applicationId);
await startService(service.appName);
await updateApplicationStatus(input.applicationId, "done");
return service;
}),
redeploy: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input }) => {
const jobData: DeploymentJob = {
applicationId: input.applicationId,
titleLog: "Rebuild deployment",
type: "redeploy",
};
await myQueue.add(
"deployments",
{ ...jobData },
{
removeOnComplete: true,
removeOnFail: true,
},
);
}),
saveEnviroment: protectedProcedure
.input(apiSaveEnviromentVariables)
.mutation(async ({ input }) => {
await updateApplication(input.applicationId, {
env: input.env,
});
return true;
}),
saveBuildType: protectedProcedure
.input(apiSaveBuildType)
.mutation(async ({ input }) => {
await updateApplication(input.applicationId, {
buildType: input.buildType,
dockerfile: input.dockerfile,
});
return true;
}),
saveGithubProvider: protectedProcedure
.input(apiSaveGithubProvider)
.mutation(async ({ input }) => {
await updateApplication(input.applicationId, {
repository: input.repository,
branch: input.branch,
sourceType: "github",
owner: input.owner,
buildPath: input.buildPath,
applicationStatus: "idle",
});
return true;
}),
saveDockerProvider: protectedProcedure
.input(apiSaveDockerProvider)
.mutation(async ({ input }) => {
await updateApplication(input.applicationId, {
dockerImage: input.dockerImage,
username: input.username,
password: input.password,
sourceType: "docker",
applicationStatus: "idle",
});
return true;
}),
saveGitProdiver: protectedProcedure
.input(apiSaveGitProvider)
.mutation(async ({ input }) => {
await updateApplication(input.applicationId, {
customGitBranch: input.customGitBranch,
customGitBuildPath: input.customGitBuildPath,
customGitUrl: input.customGitUrl,
sourceType: "git",
applicationStatus: "idle",
});
return true;
}),
generateSSHKey: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input }) => {
const application = await findApplicationById(input.applicationId);
try {
await generateSSHKey(application.appName);
const file = await readRSAFile(application.appName);
await updateApplication(input.applicationId, {
customGitSSHKey: file,
});
} catch (error) {}
return true;
}),
removeSSHKey: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input }) => {
const application = await findApplicationById(input.applicationId);
await removeRSAFiles(application.appName);
await updateApplication(input.applicationId, {
customGitSSHKey: null,
});
return true;
}),
markRunning: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input }) => {
await updateApplicationStatus(input.applicationId, "running");
}),
update: protectedProcedure
.input(apiUpdateApplication)
.mutation(async ({ input }) => {
const { applicationId, ...rest } = input;
const application = await updateApplication(applicationId, {
...rest,
});
if (!application) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Update: Error to update application",
});
}
return true;
}),
refreshToken: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input }) => {
await updateApplication(input.applicationId, {
refreshToken: nanoid(),
});
return true;
}),
deploy: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input }) => {
const jobData: DeploymentJob = {
applicationId: input.applicationId,
titleLog: "Manual deployment",
type: "deploy",
};
await myQueue.add(
"deployments",
{ ...jobData },
{
removeOnComplete: true,
removeOnFail: true,
},
);
}),
cleanQueues: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input }) => {
await cleanQueuesByApplication(input.applicationId);
}),
readTraefikConfig: protectedProcedure
.input(apiFindOneApplication)
.query(async ({ input }) => {
const application = await findApplicationById(input.applicationId);
const traefikConfig = readConfig(application.appName);
return traefikConfig;
}),
updateTraefikConfig: protectedProcedure
.input(z.object({ applicationId: z.string(), traefikConfig: z.string() }))
.mutation(async ({ input }) => {
const application = await findApplicationById(input.applicationId);
writeConfig(application.appName, input.traefikConfig);
return true;
}),
readAppMonitoring: protectedProcedure
.input(apiFindMonitoringStats)
.query(async ({ input }) => {
const stats = await getApplicationStats(input.appName);
return stats;
}),
});
// Paketo Buildpacks: paketobuildpacks/builder-jammy-full Ubuntu 22.04 Jammy Jellyfish full image with buildpacks for Apache HTTPD, Go, Java, Java Native Image, .NET, NGINX, Node.js, PHP, Procfile, Python, and Ruby
// Heroku: heroku/builder:22 Heroku-22 (Ubuntu 22.04) base image with buildpacks for Go, Java, Node.js, PHP, Python, Ruby & Scala.
// pack build imageName --path ./ --builder paketobuildpacks/builder-jammy-full
// pack build prueba-pack --path ./ --builder heroku/builder:22

199
server/api/routers/auth.ts Normal file
View File

@@ -0,0 +1,199 @@
import { TRPCError } from "@trpc/server";
import * as bcrypt from "bcrypt";
import {
adminProcedure,
createTRPCRouter,
protectedProcedure,
publicProcedure,
} from "../trpc";
import { lucia, validateRequest } from "@/server/auth/auth";
import {
apiCreateAdmin,
apiCreateUser,
apiFindOneAuth,
apiUpdateAuthByAdmin,
apiLogin,
apiUpdateAuth,
apiVerify2FA,
apiVerifyLogin2FA,
} from "@/server/db/schema";
import {
createAdmin,
createUser,
findAuthByEmail,
findAuthById,
generate2FASecret,
updateAuthById,
verify2FA,
} from "../services/auth";
export const authRouter = createTRPCRouter({
createAdmin: publicProcedure
.input(apiCreateAdmin)
.mutation(async ({ ctx, input }) => {
try {
const newAdmin = await createAdmin(input);
const session = await lucia.createSession(newAdmin.id || "", {});
ctx.res.appendHeader(
"Set-Cookie",
lucia.createSessionCookie(session.id).serialize(),
);
return true;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the main admin",
cause: error,
});
}
}),
createUser: publicProcedure
.input(apiCreateUser)
.mutation(async ({ ctx, input }) => {
try {
const newUser = await createUser(input);
const session = await lucia.createSession(newUser?.authId || "", {});
ctx.res.appendHeader(
"Set-Cookie",
lucia.createSessionCookie(session.id).serialize(),
);
return true;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the user",
cause: error,
});
}
}),
login: publicProcedure.input(apiLogin).mutation(async ({ ctx, input }) => {
try {
const auth = await findAuthByEmail(input.email);
const correctPassword = bcrypt.compareSync(
input.password,
auth?.password || "",
);
if (!correctPassword) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Credentials do not match",
});
}
if (auth?.is2FAEnabled) {
return {
is2FAEnabled: true,
authId: auth.id,
};
}
const session = await lucia.createSession(auth?.id || "", {});
ctx.res.appendHeader(
"Set-Cookie",
lucia.createSessionCookie(session.id).serialize(),
);
return {
is2FAEnabled: false,
authId: auth?.id,
};
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Credentials do not match",
cause: error,
});
}
}),
get: protectedProcedure.query(async ({ ctx }) => {
const auth = await findAuthById(ctx.user.authId);
return auth;
}),
logout: protectedProcedure.mutation(async ({ ctx }) => {
const { req, res } = ctx;
const { session } = await validateRequest(req, res);
if (!session) return false;
await lucia.invalidateSession(session.id);
res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
return true;
}),
update: protectedProcedure
.input(apiUpdateAuth)
.mutation(async ({ ctx, input }) => {
const auth = await updateAuthById(ctx.user.authId, {
...(input.email && { email: input.email }),
...(input.password && {
password: bcrypt.hashSync(input.password, 10),
}),
...(input.image && { image: input.image }),
});
return auth;
}),
one: adminProcedure.input(apiFindOneAuth).query(async ({ input }) => {
const auth = await findAuthById(input.id);
return auth;
}),
updateByAdmin: protectedProcedure
.input(apiUpdateAuthByAdmin)
.mutation(async ({ input }) => {
const auth = await updateAuthById(input.id, {
...(input.email && { email: input.email }),
...(input.password && {
password: bcrypt.hashSync(input.password, 10),
}),
...(input.image && { image: input.image }),
});
return auth;
}),
generate2FASecret: protectedProcedure.query(async ({ ctx }) => {
return await generate2FASecret(ctx.user.authId);
}),
verify2FASetup: protectedProcedure
.input(apiVerify2FA)
.mutation(async ({ ctx, input }) => {
const auth = await findAuthById(ctx.user.authId);
await verify2FA(auth, input.secret, input.pin);
await updateAuthById(auth.id, {
is2FAEnabled: true,
secret: input.secret,
});
return auth;
}),
verifyLogin2FA: publicProcedure
.input(apiVerifyLogin2FA)
.mutation(async ({ ctx, input }) => {
const auth = await findAuthById(input.id);
await verify2FA(auth, auth.secret || "", input.pin);
const session = await lucia.createSession(auth.id, {});
ctx.res.appendHeader(
"Set-Cookie",
lucia.createSessionCookie(session.id).serialize(),
);
return auth;
}),
disable2FA: protectedProcedure.mutation(async ({ ctx }) => {
const auth = await findAuthById(ctx.user.authId);
await updateAuthById(auth.id, {
is2FAEnabled: false,
secret: null,
});
return auth;
}),
});

View File

@@ -0,0 +1,152 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
apiCreateBackup,
apiFindOneBackup,
apiRemoveBackup,
apiUpdateBackup,
} from "@/server/db/schema";
import { runMariadbBackup } from "@/server/utils/backups/mariadb";
import { runMongoBackup } from "@/server/utils/backups/mongo";
import { runMySqlBackup } from "@/server/utils/backups/mysql";
import { runPostgresBackup } from "@/server/utils/backups/postgres";
import { TRPCError } from "@trpc/server";
import {
createBackup,
findBackupById,
removeBackupById,
updateBackupById,
} from "../services/backup";
import { findMariadbByBackupId } from "../services/mariadb";
import { findMongoByBackupId } from "../services/mongo";
import { findMySqlByBackupId } from "../services/mysql";
import { findPostgresByBackupId } from "../services/postgres";
import {
removeScheduleBackup,
scheduleBackup,
} from "@/server/utils/backups/utils";
export const backupRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateBackup)
.mutation(async ({ input }) => {
try {
const newBackup = await createBackup(input);
const backup = await findBackupById(newBackup.backupId);
if (backup.enabled) {
scheduleBackup(backup);
}
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the Backup",
cause: error,
});
}
}),
one: protectedProcedure.input(apiFindOneBackup).query(async ({ input }) => {
const backup = await findBackupById(input.backupId);
return backup;
}),
update: protectedProcedure
.input(apiUpdateBackup)
.mutation(async ({ input }) => {
try {
await updateBackupById(input.backupId, input);
const backup = await findBackupById(input.backupId);
if (backup.enabled) {
scheduleBackup(backup);
} else {
removeScheduleBackup(input.backupId);
}
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to update this Backup",
});
}
}),
remove: protectedProcedure
.input(apiRemoveBackup)
.mutation(async ({ input }) => {
try {
const value = await removeBackupById(input.backupId);
removeScheduleBackup(input.backupId);
return value;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to delete this Backup",
cause: error,
});
}
}),
manualBackupPostgres: protectedProcedure
.input(apiFindOneBackup)
.mutation(async ({ input }) => {
try {
const backup = await findBackupById(input.backupId);
const postgres = await findPostgresByBackupId(backup.backupId);
await runPostgresBackup(postgres, backup);
return true;
} catch (error) {
console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to run manual postgres backup ",
cause: error,
});
}
}),
manualBackupMySql: protectedProcedure
.input(apiFindOneBackup)
.mutation(async ({ input }) => {
try {
const backup = await findBackupById(input.backupId);
const mysql = await findMySqlByBackupId(backup.backupId);
await runMySqlBackup(mysql, backup);
return true;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to run manual mysql backup ",
cause: error,
});
}
}),
manualBackupMariadb: protectedProcedure
.input(apiFindOneBackup)
.mutation(async ({ input }) => {
try {
const backup = await findBackupById(input.backupId);
const mariadb = await findMariadbByBackupId(backup.backupId);
await runMariadbBackup(mariadb, backup);
return true;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to run manual mariadb backup ",
cause: error,
});
}
}),
manualBackupMongo: protectedProcedure
.input(apiFindOneBackup)
.mutation(async ({ input }) => {
try {
const backup = await findBackupById(input.backupId);
const mongo = await findMongoByBackupId(backup.backupId);
await runMongoBackup(mongo, backup);
return true;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to run manual mongo backup ",
cause: error,
});
}
}),
});

View File

@@ -0,0 +1,29 @@
import { adminProcedure, createTRPCRouter } from "@/server/api/trpc";
import { apiCreateCertificate, apiFindCertificate } from "@/server/db/schema";
import {
createCertificate,
findCertificates,
findCertificateById,
removeCertificateById,
} from "../services/certificate";
export const certificateRouter = createTRPCRouter({
create: adminProcedure
.input(apiCreateCertificate)
.mutation(async ({ input }) => {
return await createCertificate(input);
}),
one: adminProcedure.input(apiFindCertificate).query(async ({ input }) => {
return await findCertificateById(input.certificateId);
}),
remove: adminProcedure
.input(apiFindCertificate)
.mutation(async ({ input }) => {
await removeCertificateById(input.certificateId);
return true;
}),
all: adminProcedure.query(async () => {
return findCertificates();
}),
});

View File

@@ -0,0 +1,11 @@
import { apiFindAllByApplication } from "@/server/db/schema";
import { findAllDeploymentsByApplicationId } from "../services/deployment";
import { createTRPCRouter, protectedProcedure } from "../trpc";
export const deploymentRouter = createTRPCRouter({
all: protectedProcedure
.input(apiFindAllByApplication)
.query(async ({ input }) => {
return await findAllDeploymentsByApplicationId(input.applicationId);
}),
});

View File

@@ -0,0 +1,99 @@
import {
adminProcedure,
createTRPCRouter,
protectedProcedure,
} from "@/server/api/trpc";
import { db } from "@/server/db";
import {
apiCreateDestination,
apiFindOneDestination,
apiRemoveDestination,
apiUpdateDestination,
} from "@/server/db/schema";
import { HeadBucketCommand, S3Client } from "@aws-sdk/client-s3";
import { TRPCError } from "@trpc/server";
import {
createDestintation,
findDestinationById,
removeDestinationById,
updateDestinationById,
} from "../services/destination";
import { findAdmin } from "../services/admin";
export const destinationRouter = createTRPCRouter({
create: adminProcedure
.input(apiCreateDestination)
.mutation(async ({ input }) => {
try {
await createDestintation(input);
return await findAdmin();
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the destination",
cause: error,
});
}
}),
testConnection: adminProcedure
.input(apiCreateDestination)
.mutation(async ({ input }) => {
const { secretAccessKey, bucket, region, endpoint, accessKey } = input;
const s3Client = new S3Client({
region: region,
...(endpoint && {
endpoint: endpoint,
}),
credentials: {
accessKeyId: accessKey,
secretAccessKey: secretAccessKey,
},
forcePathStyle: true,
});
const headBucketCommand = new HeadBucketCommand({ Bucket: bucket });
try {
await s3Client.send(headBucketCommand);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to connect to bucket",
cause: error,
});
}
}),
one: protectedProcedure
.input(apiFindOneDestination)
.query(async ({ input }) => {
const destination = await findDestinationById(input.destinationId);
return destination;
}),
all: adminProcedure.query(async () => {
return await db.query.destinations.findMany({});
}),
remove: adminProcedure
.input(apiRemoveDestination)
.mutation(async ({ input }) => {
try {
return await removeDestinationById(input.destinationId);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to delete this destination",
});
}
}),
update: adminProcedure
.input(apiUpdateDestination)
.mutation(async ({ input }) => {
try {
return await updateDestinationById(input.destinationId, input);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to update this destination",
cause: error,
});
}
}),
});

View File

@@ -0,0 +1,44 @@
import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "../trpc";
import {
getConfig,
getContainersByAppLabel,
getContainers,
getContainersByAppNameMatch,
} from "../services/docker";
export const dockerRouter = createTRPCRouter({
getContainers: protectedProcedure.query(async () => {
return await getContainers();
}),
getConfig: protectedProcedure
.input(
z.object({
containerId: z.string().min(1),
}),
)
.query(async ({ input }) => {
return await getConfig(input.containerId);
}),
getContainersByAppNameMatch: protectedProcedure
.input(
z.object({
appName: z.string().min(1),
}),
)
.query(async ({ input }) => {
return await getContainersByAppNameMatch(input.appName);
}),
getContainersByAppLabel: protectedProcedure
.input(
z.object({
appName: z.string().min(1),
}),
)
.query(async ({ input }) => {
return await getContainersByAppLabel(input.appName);
}),
});

View File

@@ -0,0 +1,59 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
apiCreateDomain,
apiFindDomain,
apiFindDomainByApplication,
apiUpdateDomain,
} from "@/server/db/schema";
import { manageDomain, removeDomain } from "@/server/utils/traefik/domain";
import { TRPCError } from "@trpc/server";
import { findApplicationById } from "../services/application";
import {
createDomain,
findDomainById,
findDomainsByApplicationId,
removeDomainById,
updateDomainById,
} from "../services/domain";
export const domainRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateDomain)
.mutation(async ({ input }) => {
try {
await createDomain(input);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the domain",
cause: error,
});
}
}),
byApplicationId: protectedProcedure
.input(apiFindDomainByApplication)
.query(async ({ input }) => {
return await findDomainsByApplicationId(input.applicationId);
}),
update: protectedProcedure
.input(apiUpdateDomain)
.mutation(async ({ input }) => {
const result = await updateDomainById(input.domainId, input);
const domain = await findDomainById(input.domainId);
const application = await findApplicationById(domain.applicationId);
await manageDomain(application, domain);
return result;
}),
one: protectedProcedure.input(apiFindDomain).query(async ({ input }) => {
return await findDomainById(input.domainId);
}),
delete: protectedProcedure
.input(apiFindDomain)
.mutation(async ({ input }) => {
const domain = await findDomainById(input.domainId);
const result = await removeDomainById(input.domainId);
removeDomain(domain.application.appName, domain.uniqueConfigKey);
return result;
}),
});

View File

@@ -0,0 +1,183 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
apiChangeMariaDBStatus,
apiCreateMariaDB,
apiDeployMariaDB,
apiFindOneMariaDB,
apiResetMariadb,
apiSaveEnviromentVariablesMariaDB,
apiSaveExternalPortMariaDB,
apiUpdateMariaDB,
} from "@/server/db/schema/mariadb";
import {
removeService,
startService,
stopService,
} from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import {
createMariadb,
deployMariadb,
findMariadbById,
removeMariadbById,
updateMariadbById,
} from "../services/mariadb";
import { addNewService, checkServiceAccess } from "../services/user";
import { createMount } from "../services/mount";
export const mariadbRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateMariaDB)
.mutation(async ({ input, ctx }) => {
try {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
}
const newMariadb = await createMariadb(input);
if (ctx.user.rol === "user") {
await addNewService(ctx.user.authId, newMariadb.mariadbId);
}
await createMount({
serviceId: newMariadb.mariadbId,
serviceType: "mariadb",
volumeName: `${newMariadb.appName}-data`,
mountPath: "/var/lib/mysql",
type: "volume",
});
return true;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting mariadb database",
cause: error,
});
}
}),
one: protectedProcedure
.input(apiFindOneMariaDB)
.query(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.mariadbId, "access");
}
return await findMariadbById(input.mariadbId);
}),
start: protectedProcedure
.input(apiFindOneMariaDB)
.mutation(async ({ input }) => {
const service = await findMariadbById(input.mariadbId);
await startService(service.appName);
await updateMariadbById(input.mariadbId, {
applicationStatus: "done",
});
return service;
}),
stop: protectedProcedure
.input(apiFindOneMariaDB)
.mutation(async ({ input }) => {
const mongo = await findMariadbById(input.mariadbId);
await stopService(mongo.appName);
await updateMariadbById(input.mariadbId, {
applicationStatus: "idle",
});
return mongo;
}),
saveExternalPort: protectedProcedure
.input(apiSaveExternalPortMariaDB)
.mutation(async ({ input }) => {
const mongo = await findMariadbById(input.mariadbId);
await updateMariadbById(input.mariadbId, {
externalPort: input.externalPort,
});
await deployMariadb(input.mariadbId);
return mongo;
}),
deploy: protectedProcedure
.input(apiDeployMariaDB)
.mutation(async ({ input }) => {
return deployMariadb(input.mariadbId);
}),
changeStatus: protectedProcedure
.input(apiChangeMariaDBStatus)
.mutation(async ({ input }) => {
const mongo = await findMariadbById(input.mariadbId);
await updateMariadbById(input.mariadbId, {
applicationStatus: input.applicationStatus,
});
return mongo;
}),
remove: protectedProcedure
.input(apiFindOneMariaDB)
.mutation(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.mariadbId, "delete");
}
const mongo = await findMariadbById(input.mariadbId);
const cleanupOperations = [
async () => await removeService(mongo?.appName),
async () => await removeMariadbById(input.mariadbId),
];
for (const operation of cleanupOperations) {
try {
await operation();
} catch (error) {}
}
return mongo;
}),
saveEnviroment: protectedProcedure
.input(apiSaveEnviromentVariablesMariaDB)
.mutation(async ({ input }) => {
const service = await updateMariadbById(input.mariadbId, {
env: input.env,
});
if (!service) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Update: Error to add enviroment variables",
});
}
return true;
}),
reload: protectedProcedure
.input(apiResetMariadb)
.mutation(async ({ input }) => {
await stopService(input.appName);
await updateMariadbById(input.mariadbId, {
applicationStatus: "idle",
});
await startService(input.appName);
await updateMariadbById(input.mariadbId, {
applicationStatus: "done",
});
return true;
}),
update: protectedProcedure
.input(apiUpdateMariaDB)
.mutation(async ({ input }) => {
const { mariadbId, ...rest } = input;
const service = await updateMariadbById(mariadbId, {
...rest,
});
if (!service) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Update: Error to update mariadb",
});
}
return true;
}),
});

184
server/api/routers/mongo.ts Normal file
View File

@@ -0,0 +1,184 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
apiChangeMongoStatus,
apiCreateMongo,
apiDeployMongo,
apiFindOneMongo,
apiResetMongo,
apiSaveEnviromentVariablesMongo,
apiSaveExternalPortMongo,
apiUpdateMongo,
} from "@/server/db/schema/mongo";
import {
removeService,
startService,
stopService,
} from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import {
createMongo,
deployMongo,
findMongoById,
removeMongoById,
updateMongoById,
} from "../services/mongo";
import { addNewService, checkServiceAccess } from "../services/user";
import { createMount } from "../services/mount";
export const mongoRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateMongo)
.mutation(async ({ input, ctx }) => {
try {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
}
const newMongo = await createMongo(input);
if (ctx.user.rol === "user") {
await addNewService(ctx.user.authId, newMongo.mongoId);
}
await createMount({
serviceId: newMongo.mongoId,
serviceType: "mongo",
volumeName: `${newMongo.appName}-data`,
mountPath: "/data/db",
type: "volume",
});
return true;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting mongo database",
cause: error,
});
}
}),
one: protectedProcedure
.input(apiFindOneMongo)
.query(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.mongoId, "access");
}
return await findMongoById(input.mongoId);
}),
start: protectedProcedure
.input(apiFindOneMongo)
.mutation(async ({ input }) => {
const service = await findMongoById(input.mongoId);
await startService(service.appName);
await updateMongoById(input.mongoId, {
applicationStatus: "done",
});
return service;
}),
stop: protectedProcedure
.input(apiFindOneMongo)
.mutation(async ({ input }) => {
const mongo = await findMongoById(input.mongoId);
await stopService(mongo.appName);
await updateMongoById(input.mongoId, {
applicationStatus: "idle",
});
return mongo;
}),
saveExternalPort: protectedProcedure
.input(apiSaveExternalPortMongo)
.mutation(async ({ input }) => {
const mongo = await findMongoById(input.mongoId);
await updateMongoById(input.mongoId, {
externalPort: input.externalPort,
});
await deployMongo(input.mongoId);
return mongo;
}),
deploy: protectedProcedure
.input(apiDeployMongo)
.mutation(async ({ input }) => {
return deployMongo(input.mongoId);
}),
changeStatus: protectedProcedure
.input(apiChangeMongoStatus)
.mutation(async ({ input }) => {
const mongo = await findMongoById(input.mongoId);
await updateMongoById(input.mongoId, {
applicationStatus: input.applicationStatus,
});
return mongo;
}),
reload: protectedProcedure
.input(apiResetMongo)
.mutation(async ({ input }) => {
await stopService(input.appName);
await updateMongoById(input.mongoId, {
applicationStatus: "idle",
});
await startService(input.appName);
await updateMongoById(input.mongoId, {
applicationStatus: "done",
});
return true;
}),
remove: protectedProcedure
.input(apiFindOneMongo)
.mutation(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.mongoId, "delete");
}
const mongo = await findMongoById(input.mongoId);
const cleanupOperations = [
async () => await removeService(mongo?.appName),
async () => await removeMongoById(input.mongoId),
];
for (const operation of cleanupOperations) {
try {
await operation();
} catch (error) {}
}
return mongo;
}),
saveEnviroment: protectedProcedure
.input(apiSaveEnviromentVariablesMongo)
.mutation(async ({ input }) => {
const service = await updateMongoById(input.mongoId, {
env: input.env,
});
if (!service) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Update: Error to add enviroment variables",
});
}
return true;
}),
update: protectedProcedure
.input(apiUpdateMongo)
.mutation(async ({ input }) => {
const { mongoId, ...rest } = input;
const service = await updateMongoById(mongoId, {
...rest,
});
if (!service) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Update: Error to update mongo",
});
}
return true;
}),
});

View File

@@ -0,0 +1,17 @@
import { apiCreateMount, apiRemoveMount } from "@/server/db/schema";
import { createMount, deleteMount } from "../services/mount";
import { createTRPCRouter, protectedProcedure } from "../trpc";
export const mountRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateMount)
.mutation(async ({ input }) => {
await createMount(input);
return true;
}),
remove: protectedProcedure
.input(apiRemoveMount)
.mutation(async ({ input }) => {
return await deleteMount(input.mountId);
}),
});

183
server/api/routers/mysql.ts Normal file
View File

@@ -0,0 +1,183 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
apiChangeMySqlStatus,
apiCreateMySql,
apiDeployMySql,
apiFindOneMySql,
apiResetMysql,
apiSaveEnviromentVariablesMySql,
apiSaveExternalPortMySql,
apiUpdateMySql,
} from "@/server/db/schema/mysql";
import {
removeService,
startService,
stopService,
} from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import {
createMysql,
deployMySql,
findMySqlById,
removeMySqlById,
updateMySqlById,
} from "../services/mysql";
import { addNewService, checkServiceAccess } from "../services/user";
import { createMount } from "../services/mount";
import { z } from "zod";
export const mysqlRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateMySql)
.mutation(async ({ input, ctx }) => {
try {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
}
const newMysql = await createMysql(input);
if (ctx.user.rol === "user") {
await addNewService(ctx.user.authId, newMysql.mysqlId);
}
await createMount({
serviceId: newMysql.mysqlId,
serviceType: "mysql",
volumeName: `${newMysql.appName}-data`,
mountPath: "/var/lib/mysql",
type: "volume",
});
return true;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting mysql database",
cause: error,
});
}
}),
one: protectedProcedure
.input(apiFindOneMySql)
.query(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.mysqlId, "access");
}
return await findMySqlById(input.mysqlId);
}),
start: protectedProcedure
.input(apiFindOneMySql)
.mutation(async ({ input }) => {
const service = await findMySqlById(input.mysqlId);
await startService(service.appName);
await updateMySqlById(input.mysqlId, {
applicationStatus: "done",
});
return service;
}),
stop: protectedProcedure
.input(apiFindOneMySql)
.mutation(async ({ input }) => {
const mongo = await findMySqlById(input.mysqlId);
await stopService(mongo.appName);
await updateMySqlById(input.mysqlId, {
applicationStatus: "idle",
});
return mongo;
}),
saveExternalPort: protectedProcedure
.input(apiSaveExternalPortMySql)
.mutation(async ({ input }) => {
const mongo = await findMySqlById(input.mysqlId);
await updateMySqlById(input.mysqlId, {
externalPort: input.externalPort,
});
await deployMySql(input.mysqlId);
return mongo;
}),
deploy: protectedProcedure
.input(apiDeployMySql)
.mutation(async ({ input }) => {
return deployMySql(input.mysqlId);
}),
changeStatus: protectedProcedure
.input(apiChangeMySqlStatus)
.mutation(async ({ input }) => {
const mongo = await findMySqlById(input.mysqlId);
await updateMySqlById(input.mysqlId, {
applicationStatus: input.applicationStatus,
});
return mongo;
}),
reload: protectedProcedure
.input(apiResetMysql)
.mutation(async ({ input }) => {
await stopService(input.appName);
await updateMySqlById(input.mysqlId, {
applicationStatus: "idle",
});
await startService(input.appName);
await updateMySqlById(input.mysqlId, {
applicationStatus: "done",
});
return true;
}),
remove: protectedProcedure
.input(apiFindOneMySql)
.mutation(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.mysqlId, "delete");
}
const mongo = await findMySqlById(input.mysqlId);
const cleanupOperations = [
async () => await removeService(mongo?.appName),
async () => await removeMySqlById(input.mysqlId),
];
for (const operation of cleanupOperations) {
try {
await operation();
} catch (error) {}
}
return mongo;
}),
saveEnviroment: protectedProcedure
.input(apiSaveEnviromentVariablesMySql)
.mutation(async ({ input }) => {
const service = await updateMySqlById(input.mysqlId, {
env: input.env,
});
if (!service) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Update: Error to add enviroment variables",
});
}
return true;
}),
update: protectedProcedure
.input(apiUpdateMySql)
.mutation(async ({ input }) => {
const { mysqlId, ...rest } = input;
const service = await updateMySqlById(mysqlId, {
...rest,
});
if (!service) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Update: Error to update mysql",
});
}
return true;
}),
});

View File

@@ -0,0 +1,65 @@
import { TRPCError } from "@trpc/server";
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
apiCreatePort,
apiFindOnePort,
apiUpdatePort,
} from "@/server/db/schema/port";
import {
createPort,
finPortById,
removePortById,
updatePortById,
} from "../services/port";
export const portRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreatePort)
.mutation(async ({ input }) => {
try {
await createPort(input);
return true;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting port",
cause: error,
});
}
}),
one: protectedProcedure.input(apiFindOnePort).query(async ({ input }) => {
try {
return await finPortById(input.portId);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Port not found",
cause: error,
});
}
}),
delete: protectedProcedure
.input(apiFindOnePort)
.mutation(async ({ input }) => {
try {
return removePortById(input.portId);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Deleting port",
});
}
}),
update: protectedProcedure
.input(apiUpdatePort)
.mutation(async ({ input }) => {
try {
return updatePortById(input.portId, input);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to updating port",
});
}
}),
});

View File

@@ -0,0 +1,179 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
apiChangePostgresStatus,
apiCreatePostgres,
apiDeployPostgres,
apiFindOnePostgres,
apiResetPostgres,
apiSaveEnviromentVariablesPostgres,
apiSaveExternalPortPostgres,
apiUpdatePostgres,
} from "@/server/db/schema/postgres";
import {
removeService,
startService,
stopService,
} from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import {
createPostgres,
deployPostgres,
findPostgresById,
removePostgresById,
updatePostgresById,
} from "../services/postgres";
import { addNewService, checkServiceAccess } from "../services/user";
import { createMount } from "../services/mount";
export const postgresRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreatePostgres)
.mutation(async ({ input, ctx }) => {
try {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
}
const newPostgres = await createPostgres(input);
if (ctx.user.rol === "user") {
await addNewService(ctx.user.authId, newPostgres.postgresId);
}
await createMount({
serviceId: newPostgres.postgresId,
serviceType: "postgres",
volumeName: `${newPostgres.appName}-data`,
mountPath: "/var/lib/postgresql/data",
type: "volume",
});
return true;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting postgresql database",
cause: error,
});
}
}),
one: protectedProcedure
.input(apiFindOnePostgres)
.query(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.postgresId, "access");
}
return await findPostgresById(input.postgresId);
}),
start: protectedProcedure
.input(apiFindOnePostgres)
.mutation(async ({ input }) => {
const service = await findPostgresById(input.postgresId);
await startService(service.appName);
await updatePostgresById(input.postgresId, {
applicationStatus: "done",
});
return service;
}),
stop: protectedProcedure
.input(apiFindOnePostgres)
.mutation(async ({ input }) => {
const postgres = await findPostgresById(input.postgresId);
await stopService(postgres.appName);
await updatePostgresById(input.postgresId, {
applicationStatus: "idle",
});
return postgres;
}),
saveExternalPort: protectedProcedure
.input(apiSaveExternalPortPostgres)
.mutation(async ({ input }) => {
const postgres = await findPostgresById(input.postgresId);
await updatePostgresById(input.postgresId, {
externalPort: input.externalPort,
});
await deployPostgres(input.postgresId);
return postgres;
}),
deploy: protectedProcedure
.input(apiDeployPostgres)
.mutation(async ({ input }) => {
return deployPostgres(input.postgresId);
}),
changeStatus: protectedProcedure
.input(apiChangePostgresStatus)
.mutation(async ({ input }) => {
const postgres = await findPostgresById(input.postgresId);
await updatePostgresById(input.postgresId, {
applicationStatus: input.applicationStatus,
});
return postgres;
}),
remove: protectedProcedure
.input(apiFindOnePostgres)
.mutation(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.postgresId, "delete");
}
const postgres = await findPostgresById(input.postgresId);
const cleanupOperations = [
removeService(postgres.appName),
removePostgresById(input.postgresId),
];
await Promise.allSettled(cleanupOperations);
return postgres;
}),
saveEnviroment: protectedProcedure
.input(apiSaveEnviromentVariablesPostgres)
.mutation(async ({ input }) => {
const service = await updatePostgresById(input.postgresId, {
env: input.env,
});
if (!service) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Update: Error to add enviroment variables",
});
}
return true;
}),
reload: protectedProcedure
.input(apiResetPostgres)
.mutation(async ({ input }) => {
await stopService(input.appName);
await updatePostgresById(input.postgresId, {
applicationStatus: "idle",
});
await startService(input.appName);
await updatePostgresById(input.postgresId, {
applicationStatus: "done",
});
return true;
}),
update: protectedProcedure
.input(apiUpdatePostgres)
.mutation(async ({ input }) => {
const { postgresId, ...rest } = input;
const service = await updatePostgresById(postgresId, {
...rest,
});
if (!service) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Update: Error to update postgres",
});
}
return true;
}),
});

View File

@@ -0,0 +1,197 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { db } from "@/server/db";
import {
apiCreateProject,
apiFindOneProject,
apiRemoveProject,
apiUpdateProject,
projects,
} from "@/server/db/schema/project";
import { TRPCError } from "@trpc/server";
import { desc, eq, sql } from "drizzle-orm";
import {
createProject,
deleteProject,
findProjectById,
updateProjectById,
} from "../services/project";
import {
addNewProject,
checkProjectAccess,
findUserByAuthId,
} from "../services/user";
import {
applications,
mariadb,
mongo,
mysql,
postgres,
redis,
} from "@/server/db/schema";
import type { AnyPgColumn } from "drizzle-orm/pg-core";
export const projectRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateProject)
.mutation(async ({ ctx, input }) => {
try {
if (ctx.user.rol === "user") {
await checkProjectAccess(ctx.user.authId, "create");
}
const project = await createProject(input);
if (ctx.user.rol === "user") {
await addNewProject(ctx.user.authId, project.projectId);
}
} catch (error) {
console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to create the project",
cause: error,
});
}
}),
one: protectedProcedure
.input(apiFindOneProject)
.query(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
const { accesedServices } = await findUserByAuthId(ctx.user.authId);
await checkProjectAccess(ctx.user.authId, "access", input.projectId);
const service = await db.query.projects.findFirst({
where: eq(projects.projectId, input.projectId),
with: {
applications: {
where: buildServiceFilter(
applications.applicationId,
accesedServices,
),
},
mariadb: {
where: buildServiceFilter(mariadb.mariadbId, accesedServices),
},
mongo: {
where: buildServiceFilter(mongo.mongoId, accesedServices),
},
mysql: {
where: buildServiceFilter(mysql.mysqlId, accesedServices),
},
postgres: {
where: buildServiceFilter(postgres.postgresId, accesedServices),
},
redis: {
where: buildServiceFilter(redis.redisId, accesedServices),
},
},
});
if (!service) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Project not found",
});
}
return service;
}
const project = await findProjectById(input.projectId);
return project;
}),
all: protectedProcedure.query(async ({ ctx }) => {
if (ctx.user.rol === "user") {
const { accesedProjects, accesedServices } = await findUserByAuthId(
ctx.user.authId,
);
if (accesedProjects.length === 0) {
return [];
}
const query = await db.query.projects.findMany({
where: sql`${projects.projectId} IN (${sql.join(
accesedProjects.map((projectId) => sql`${projectId}`),
sql`, `,
)})`,
with: {
applications: {
where: buildServiceFilter(
applications.applicationId,
accesedServices,
),
},
mariadb: {
where: buildServiceFilter(mariadb.mariadbId, accesedServices),
},
mongo: {
where: buildServiceFilter(mongo.mongoId, accesedServices),
},
mysql: {
where: buildServiceFilter(mysql.mysqlId, accesedServices),
},
postgres: {
where: buildServiceFilter(postgres.postgresId, accesedServices),
},
redis: {
where: buildServiceFilter(redis.redisId, accesedServices),
},
},
orderBy: desc(projects.createdAt),
});
return query;
}
return await db.query.projects.findMany({
with: {
applications: true,
mariadb: true,
mongo: true,
mysql: true,
postgres: true,
redis: true,
},
orderBy: desc(projects.createdAt),
});
}),
remove: protectedProcedure
.input(apiRemoveProject)
.mutation(async ({ input, ctx }) => {
try {
if (ctx.user.rol === "user") {
await checkProjectAccess(ctx.user.authId, "delete");
}
const project = await deleteProject(input.projectId);
return project;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to delete this project",
cause: error,
});
}
}),
update: protectedProcedure
.input(apiUpdateProject)
.mutation(async ({ input }) => {
try {
const project = updateProjectById(input.projectId, {
...input,
});
return project;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error to update this project",
cause: error,
});
}
}),
});
function buildServiceFilter(fieldName: AnyPgColumn, accesedServices: string[]) {
return accesedServices.length > 0
? sql`${fieldName} IN (${sql.join(
accesedServices.map((serviceId) => sql`${serviceId}`),
sql`, `,
)})`
: sql`1 = 0`; // Always false condition
}

View File

@@ -0,0 +1,33 @@
import { createTRPCRouter, protectedProcedure } from "../trpc";
import {
apiCreateRedirect,
apiFindOneRedirect,
apiUpdateRedirect,
} from "@/server/db/schema";
import {
createRedirect,
findRedirectById,
removeRedirectById,
updateRedirectById,
} from "../services/redirect";
export const redirectsRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateRedirect)
.mutation(async ({ input }) => {
return await createRedirect(input);
}),
one: protectedProcedure.input(apiFindOneRedirect).query(async ({ input }) => {
return findRedirectById(input.redirectId);
}),
delete: protectedProcedure
.input(apiFindOneRedirect)
.mutation(async ({ input }) => {
return removeRedirectById(input.redirectId);
}),
update: protectedProcedure
.input(apiUpdateRedirect)
.mutation(async ({ input }) => {
return updateRedirectById(input.redirectId, input);
}),
});

183
server/api/routers/redis.ts Normal file
View File

@@ -0,0 +1,183 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
apiChangeRedisStatus,
apiCreateRedis,
apiDeployRedis,
apiFindOneRedis,
apiResetRedis,
apiSaveEnviromentVariablesRedis,
apiSaveExternalPortRedis,
apiUpdateRedis,
} from "@/server/db/schema/redis";
import {
removeService,
startService,
stopService,
} from "@/server/utils/docker/utils";
import { TRPCError } from "@trpc/server";
import {
createRedis,
deployRedis,
findRedisById,
removeRedisById,
updateRedisById,
} from "../services/redis";
import { addNewService, checkServiceAccess } from "../services/user";
import { createMount } from "../services/mount";
export const redisRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateRedis)
.mutation(async ({ input, ctx }) => {
try {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.projectId, "create");
}
const newRedis = await createRedis(input);
if (ctx.user.rol === "user") {
await addNewService(ctx.user.authId, newRedis.redisId);
}
await createMount({
serviceId: newRedis.redisId,
serviceType: "redis",
volumeName: `${newRedis.appName}-data`,
mountPath: "/data",
type: "volume",
});
return true;
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Error input: Inserting redis database",
cause: error,
});
}
}),
one: protectedProcedure
.input(apiFindOneRedis)
.query(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.redisId, "access");
}
return await findRedisById(input.redisId);
}),
start: protectedProcedure
.input(apiFindOneRedis)
.mutation(async ({ input }) => {
const redis = await findRedisById(input.redisId);
await startService(redis.appName);
await updateRedisById(input.redisId, {
applicationStatus: "done",
});
return redis;
}),
reload: protectedProcedure
.input(apiResetRedis)
.mutation(async ({ input }) => {
await stopService(input.appName);
await updateRedisById(input.redisId, {
applicationStatus: "idle",
});
await startService(input.appName);
await updateRedisById(input.redisId, {
applicationStatus: "done",
});
return true;
}),
stop: protectedProcedure
.input(apiFindOneRedis)
.mutation(async ({ input }) => {
const mongo = await findRedisById(input.redisId);
await stopService(mongo.appName);
await updateRedisById(input.redisId, {
applicationStatus: "idle",
});
return mongo;
}),
saveExternalPort: protectedProcedure
.input(apiSaveExternalPortRedis)
.mutation(async ({ input }) => {
const mongo = await findRedisById(input.redisId);
await updateRedisById(input.redisId, {
externalPort: input.externalPort,
});
await deployRedis(input.redisId);
return mongo;
}),
deploy: protectedProcedure
.input(apiDeployRedis)
.mutation(async ({ input }) => {
return deployRedis(input.redisId);
}),
changeStatus: protectedProcedure
.input(apiChangeRedisStatus)
.mutation(async ({ input }) => {
const mongo = await findRedisById(input.redisId);
await updateRedisById(input.redisId, {
applicationStatus: input.applicationStatus,
});
return mongo;
}),
remove: protectedProcedure
.input(apiFindOneRedis)
.mutation(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
await checkServiceAccess(ctx.user.authId, input.redisId, "delete");
}
const redis = await findRedisById(input.redisId);
const cleanupOperations = [
async () => await removeService(redis?.appName),
async () => await removeRedisById(input.redisId),
];
for (const operation of cleanupOperations) {
try {
await operation();
} catch (error) {}
}
return redis;
}),
saveEnviroment: protectedProcedure
.input(apiSaveEnviromentVariablesRedis)
.mutation(async ({ input }) => {
const redis = await updateRedisById(input.redisId, {
env: input.env,
});
if (!redis) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Update: Error to add enviroment variables",
});
}
return true;
}),
update: protectedProcedure
.input(apiUpdateRedis)
.mutation(async ({ input }) => {
const { redisId, ...rest } = input;
const redis = await updateRedisById(redisId, {
...rest,
});
if (!redis) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "Update: Error to update redis",
});
}
return true;
}),
});

View File

@@ -0,0 +1,33 @@
import { createTRPCRouter, protectedProcedure } from "../trpc";
import {
apiCreateSecurity,
apiFindOneSecurity,
apiUpdateSecurity,
} from "@/server/db/schema";
import {
createSecurity,
deleteSecurityById,
findSecurityById,
updateSecurityById,
} from "../services/security";
export const securityRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateSecurity)
.mutation(async ({ input }) => {
return await createSecurity(input);
}),
one: protectedProcedure.input(apiFindOneSecurity).query(async ({ input }) => {
return await findSecurityById(input.securityId);
}),
delete: protectedProcedure
.input(apiFindOneSecurity)
.mutation(async ({ input }) => {
return await deleteSecurityById(input.securityId);
}),
update: protectedProcedure
.input(apiUpdateSecurity)
.mutation(async ({ input }) => {
return await updateSecurityById(input.securityId, input);
}),
});

View File

@@ -0,0 +1,240 @@
import { docker, MAIN_TRAEFIK_PATH } from "@/server/constants";
import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
import {
cleanStoppedContainers,
cleanUpDockerBuilder,
cleanUpSystemPrune,
cleanUpUnusedImages,
cleanUpUnusedVolumes,
startService,
stopService,
} from "@/server/utils/docker/utils";
import {
apiAssignDomain,
apiModifyTraefikConfig,
apiReadTraefikConfig,
apiSaveSSHKey,
apiTraefikConfig,
apiUpdateDockerCleanup,
} from "@/server/db/schema";
import { scheduledJobs, scheduleJob } from "node-schedule";
import {
readMainConfig,
updateLetsEncryptEmail,
updateServerTraefik,
writeMainConfig,
} from "@/server/utils/traefik/web-server";
import {
readConfig,
readConfigInPath,
writeConfig,
writeTraefikConfigInPath,
} from "@/server/utils/traefik/application";
import { spawnAsync } from "@/server/utils/process/spawnAsync";
import { findAdmin, updateAdmin } from "../services/admin";
import { TRPCError } from "@trpc/server";
import {
getDokployVersion,
getDokployImage,
pullLatestRelease,
readDirectory,
} from "../services/settings";
import { canAccessToTraefikFiles } from "../services/user";
export const settingsRouter = createTRPCRouter({
reloadServer: adminProcedure.mutation(async () => {
await spawnAsync("docker", [
"service",
"update",
"--force",
"--image",
getDokployImage(),
"dokploy",
]);
return true;
}),
reloadTraefik: adminProcedure.mutation(async () => {
await stopService("dokploy-traefik");
await startService("dokploy-traefik");
return true;
}),
cleanUnusedImages: adminProcedure.mutation(async () => {
await cleanUpUnusedImages();
return true;
}),
cleanUnusedVolumes: adminProcedure.mutation(async () => {
await cleanUpUnusedVolumes();
return true;
}),
cleanStoppedContainers: adminProcedure.mutation(async () => {
await cleanStoppedContainers();
return true;
}),
cleanDockerBuilder: adminProcedure.mutation(async () => {
await cleanUpDockerBuilder();
}),
cleanDockerPrune: adminProcedure.mutation(async () => {
await cleanUpSystemPrune();
await cleanUpDockerBuilder();
return true;
}),
cleanAll: adminProcedure.mutation(async () => {
await cleanUpUnusedImages();
await cleanUpDockerBuilder();
await cleanUpSystemPrune();
return true;
}),
saveSSHPrivateKey: adminProcedure
.input(apiSaveSSHKey)
.mutation(async ({ input, ctx }) => {
await updateAdmin(ctx.user.authId, {
sshPrivateKey: input.sshPrivateKey,
});
return true;
}),
assignDomainServer: adminProcedure
.input(apiAssignDomain)
.mutation(async ({ ctx, input }) => {
const admin = await updateAdmin(ctx.user.authId, {
host: input.host,
letsEncryptEmail: input.letsEncryptEmail,
certificateType: input.certificateType,
});
if (!admin) {
throw new TRPCError({
code: "NOT_FOUND",
message: "Admin not found",
});
}
updateServerTraefik(admin, input.host);
updateLetsEncryptEmail(admin.letsEncryptEmail);
return admin;
}),
cleanSSHPrivateKey: adminProcedure.mutation(async ({ ctx }) => {
await updateAdmin(ctx.user.authId, {
sshPrivateKey: null,
});
return true;
}),
updateDockerCleanup: adminProcedure
.input(apiUpdateDockerCleanup)
.mutation(async ({ input, ctx }) => {
await updateAdmin(ctx.user.authId, {
enableDockerCleanup: input.enableDockerCleanup,
});
const admin = await findAdmin();
if (admin.enableDockerCleanup) {
scheduleJob("docker-cleanup", "0 0 * * *", async () => {
console.log(
`Docker Cleanup ${new Date().toLocaleString()}] Running...`,
);
await cleanUpUnusedImages();
await cleanUpDockerBuilder();
await cleanUpSystemPrune();
});
} else {
const currentJob = scheduledJobs["docker-cleanup"];
currentJob?.cancel();
}
return true;
}),
readTraefikConfig: adminProcedure.query(() => {
const traefikConfig = readMainConfig();
return traefikConfig;
}),
updateTraefikConfig: adminProcedure
.input(apiTraefikConfig)
.mutation(async ({ input }) => {
writeMainConfig(input.traefikConfig);
return true;
}),
readWebServerTraefikConfig: adminProcedure.query(() => {
const traefikConfig = readConfig("dokploy");
return traefikConfig;
}),
updateWebServerTraefikConfig: adminProcedure
.input(apiTraefikConfig)
.mutation(async ({ input }) => {
writeConfig("dokploy", input.traefikConfig);
return true;
}),
readMiddlewareTraefikConfig: adminProcedure.query(() => {
const traefikConfig = readConfig("middlewares");
return traefikConfig;
}),
updateMiddlewareTraefikConfig: adminProcedure
.input(apiTraefikConfig)
.mutation(async ({ input }) => {
writeConfig("middlewares", input.traefikConfig);
return true;
}),
checkAndUpdateImage: adminProcedure.query(async () => {
return await pullLatestRelease();
}),
updateServer: adminProcedure.mutation(async () => {
await spawnAsync("docker", [
"service",
"update",
"--force",
"--image",
getDokployImage(),
"dokploy",
]);
return true;
}),
getDokployVersion: adminProcedure.query(() => {
return getDokployVersion();
}),
readDirectories: protectedProcedure.query(async ({ ctx }) => {
if (ctx.user.rol === "user") {
const canAccess = await canAccessToTraefikFiles(ctx.user.authId);
if (!canAccess) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
const result = readDirectory(MAIN_TRAEFIK_PATH);
return result || [];
}),
updateTraefikFile: protectedProcedure
.input(apiModifyTraefikConfig)
.mutation(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
const canAccess = await canAccessToTraefikFiles(ctx.user.authId);
if (!canAccess) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
writeTraefikConfigInPath(input.path, input.traefikConfig);
return true;
}),
readTraefikFile: protectedProcedure
.input(apiReadTraefikConfig)
.query(async ({ input, ctx }) => {
if (ctx.user.rol === "user") {
const canAccess = await canAccessToTraefikFiles(ctx.user.authId);
if (!canAccess) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
}
return readConfigInPath(input.path);
}),
});

View File

@@ -0,0 +1,20 @@
import { apiFindOneUser, apiFindOneUserByAuth } from "@/server/db/schema";
import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
import { findUserByAuthId, findUserById, findUsers } from "../services/user";
export const userRouter = createTRPCRouter({
all: adminProcedure.query(async () => {
return await findUsers();
}),
byAuthId: protectedProcedure
.input(apiFindOneUserByAuth)
.query(async ({ input }) => {
return await findUserByAuthId(input.authId);
}),
byUserId: protectedProcedure
.input(apiFindOneUser)
.query(async ({ input }) => {
return await findUserById(input.userId);
}),
});