mirror of
https://github.com/YuzuZensai/Minikura.git
synced 2026-01-06 04:32:37 +00:00
✨ feat: adds server management API
This commit is contained in:
@@ -37,65 +37,25 @@ const bootstrap = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const app = new Elysia()
|
const app = new Elysia()
|
||||||
.use(swagger())
|
.use(swagger({
|
||||||
.ws("/ws", {
|
path: '/swagger',
|
||||||
body: t.Object({
|
documentation: {
|
||||||
message: t.String(),
|
info: {
|
||||||
}),
|
title: 'Minikura API Documentation',
|
||||||
// query: t.Object({
|
version: '1.0.0'
|
||||||
// id: t.String(),
|
|
||||||
// }),
|
|
||||||
message(ws, { message }) {
|
|
||||||
const { id } = ws.data.query;
|
|
||||||
ws.send({
|
|
||||||
id,
|
|
||||||
message,
|
|
||||||
time: Date.now(),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.post(
|
|
||||||
"/login",
|
|
||||||
async ({ body, cookie: { session_token } }) => {
|
|
||||||
const user = await UserService.getUserByUsername(body.username);
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
// Fake hash to prevent timing attacks
|
|
||||||
const FAKE_HASH =
|
|
||||||
"$argon2id$v=19$m=65536,t=3,p=4$PMqZArEBmfkHPbPv7Z50KQ$miV6tIIXU6w2WWOGLbWrdan8TGMTUsS1A1hy9RrpS9Y";
|
|
||||||
await argon2.verify(FAKE_HASH, "fake");
|
|
||||||
|
|
||||||
return error("Unauthorized", {
|
|
||||||
success: false,
|
|
||||||
message: ReturnError.INVALID_USERNAME_OR_PASSWORD,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const valid = await argon2.verify(user.password, body.password);
|
|
||||||
if (!valid) {
|
|
||||||
return error("Unauthorized", {
|
|
||||||
success: false,
|
|
||||||
message: ReturnError.INVALID_USERNAME_OR_PASSWORD,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}))
|
||||||
const session = await SessionService.create(user.id);
|
.group('/api', app => app
|
||||||
|
.derive(async ({ headers, cookie: { session_token }, path }) => {
|
||||||
session_token.httpOnly = true;
|
// Skip token validation for login route
|
||||||
session_token.value = session.token;
|
if (path === '/api/login') {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
server: null,
|
||||||
|
session: null,
|
||||||
};
|
};
|
||||||
},
|
|
||||||
{
|
|
||||||
body: t.Object({
|
|
||||||
username: t.String({ minLength: 1 }),
|
|
||||||
password: t.String({ minLength: 1 }),
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
.derive(async ({ headers, cookie: { session_token } }) => {
|
|
||||||
const auth = session_token.value;
|
const auth = session_token.value;
|
||||||
const token = headers.authorization?.split(" ")[1];
|
const token = headers.authorization?.split(" ")[1];
|
||||||
|
|
||||||
@@ -161,6 +121,48 @@ const app = new Elysia()
|
|||||||
message: ReturnError.INVALID_TOKEN,
|
message: ReturnError.INVALID_TOKEN,
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
.post(
|
||||||
|
"/login",
|
||||||
|
async ({ body, cookie: { session_token } }) => {
|
||||||
|
const user = await UserService.getUserByUsername(body.username);
|
||||||
|
const valid = await argon2.verify(user?.password || "fake", body.password);
|
||||||
|
|
||||||
|
if (!user || !valid) {
|
||||||
|
return error("Unauthorized", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.INVALID_USERNAME_OR_PASSWORD,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const session = await SessionService.create(user.id);
|
||||||
|
|
||||||
|
session_token.httpOnly = true;
|
||||||
|
session_token.value = session.token;
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
username: t.String({ minLength: 1 }),
|
||||||
|
password: t.String({ minLength: 1 }),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.ws("/ws", {
|
||||||
|
body: t.Object({
|
||||||
|
message: t.String(),
|
||||||
|
}),
|
||||||
|
message(ws, { message }) {
|
||||||
|
const { id } = ws.data.query;
|
||||||
|
ws.send({
|
||||||
|
id,
|
||||||
|
message,
|
||||||
|
time: Date.now(),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
})
|
||||||
.post("/logout", async ({ session, cookie: { session_token } }) => {
|
.post("/logout", async ({ session, cookie: { session_token } }) => {
|
||||||
if (!session) return { success: true };
|
if (!session) return { success: true };
|
||||||
|
|
||||||
@@ -182,11 +184,11 @@ const app = new Elysia()
|
|||||||
"/servers",
|
"/servers",
|
||||||
async ({ body, error }) => {
|
async ({ body, error }) => {
|
||||||
// Must be a-z, A-Z, 0-9, and -_ only
|
// Must be a-z, A-Z, 0-9, and -_ only
|
||||||
if (!/^[a-zA-Z0-9-_]+$/.test(body.name)) {
|
if (!/^[a-zA-Z0-9-_]+$/.test(body.id)) {
|
||||||
return error("Bad Request", "Name must be a-z, A-Z, 0-9, and -_ only");
|
return error("Bad Request", "ID must be a-z, A-Z, 0-9, and -_ only");
|
||||||
}
|
}
|
||||||
|
|
||||||
const _server = await ServerService.getServerByName(body.name);
|
const _server = await ServerService.getServerById(body.id);
|
||||||
if (_server) {
|
if (_server) {
|
||||||
return error("Conflict", {
|
return error("Conflict", {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -195,12 +197,12 @@ const app = new Elysia()
|
|||||||
}
|
}
|
||||||
|
|
||||||
const server = await ServerService.createServer({
|
const server = await ServerService.createServer({
|
||||||
name: body.name,
|
id: body.id,
|
||||||
description: body.description,
|
description: body.description,
|
||||||
address: body.address,
|
listen_port: body.listen_port,
|
||||||
port: body.port,
|
|
||||||
type: body.type,
|
type: body.type,
|
||||||
join_priority: body.join_priority,
|
env_variables: body.env_variables,
|
||||||
|
memory: body.memory,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -212,12 +214,15 @@ const app = new Elysia()
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
name: t.String({ minLength: 1 }),
|
id: t.String({ minLength: 1 }),
|
||||||
description: t.Nullable(t.String({ minLength: 1 })),
|
description: t.Nullable(t.String({ minLength: 1 })),
|
||||||
address: t.String({ minLength: 1 }),
|
listen_port: t.Integer({ minimum: 1, maximum: 65535 }),
|
||||||
port: t.Integer({ minimum: 1, maximum: 65535 }),
|
|
||||||
type: t.Enum(ServerType),
|
type: t.Enum(ServerType),
|
||||||
join_priority: t.Nullable(t.Integer({ minimum: 0 })),
|
env_variables: t.Optional(t.Array(t.Object({
|
||||||
|
key: t.String({ minLength: 1 }),
|
||||||
|
value: t.String(),
|
||||||
|
}))),
|
||||||
|
memory: t.Optional(t.String({ minLength: 1 })),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -232,23 +237,13 @@ const app = new Elysia()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body.name) {
|
// Create update data with only fields that exist in the model
|
||||||
const _server = await ServerService.getServerByName(body.name);
|
const data: any = {};
|
||||||
if (_server && _server.id !== id) {
|
|
||||||
return error("Conflict", {
|
|
||||||
success: false,
|
|
||||||
message: ReturnError.SERVER_NAME_IN_USE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
if (body.description !== undefined) data.description = body.description;
|
||||||
name: body.name,
|
if (body.listen_port !== undefined) data.listen_port = body.listen_port;
|
||||||
description: body.description,
|
if (body.memory !== undefined) data.memory = body.memory;
|
||||||
address: body.address,
|
// Don't allow service_type to be updated through API
|
||||||
port: body.port,
|
|
||||||
join_priority: body.join_priority,
|
|
||||||
};
|
|
||||||
|
|
||||||
await prisma.server.update({
|
await prisma.server.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -266,11 +261,9 @@ const app = new Elysia()
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
name: t.Optional(t.String({ minLength: 1 })),
|
|
||||||
description: t.Optional(t.Nullable(t.String({ minLength: 1 }))),
|
description: t.Optional(t.Nullable(t.String({ minLength: 1 }))),
|
||||||
address: t.Optional(t.String({ minLength: 1 })),
|
listen_port: t.Optional(t.Integer({ minimum: 1, maximum: 65535 })),
|
||||||
port: t.Optional(t.Integer({ minimum: 1, maximum: 65535 })),
|
memory: t.Optional(t.String({ minLength: 1 })),
|
||||||
join_priority: t.Optional(t.Integer({ minimum: 0 })),
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -298,13 +291,11 @@ const app = new Elysia()
|
|||||||
"/reverse_proxy_servers",
|
"/reverse_proxy_servers",
|
||||||
async ({ body, error }) => {
|
async ({ body, error }) => {
|
||||||
// Must be a-z, A-Z, 0-9, and -_ only
|
// Must be a-z, A-Z, 0-9, and -_ only
|
||||||
if (!/^[a-zA-Z0-9-_]+$/.test(body.name)) {
|
if (!/^[a-zA-Z0-9-_]+$/.test(body.id)) {
|
||||||
return error("Bad Request", "Name must be a-z, A-Z, 0-9, and -_ only");
|
return error("Bad Request", "ID must be a-z, A-Z, 0-9, and -_ only");
|
||||||
}
|
}
|
||||||
|
|
||||||
const _server = await ServerService.getReverseProxyServerByName(
|
const _server = await ServerService.getReverseProxyServerById(body.id);
|
||||||
body.name
|
|
||||||
);
|
|
||||||
if (_server) {
|
if (_server) {
|
||||||
return error("Conflict", {
|
return error("Conflict", {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -313,10 +304,14 @@ const app = new Elysia()
|
|||||||
}
|
}
|
||||||
|
|
||||||
const server = await ServerService.createReverseProxyServer({
|
const server = await ServerService.createReverseProxyServer({
|
||||||
name: body.name,
|
id: body.id,
|
||||||
address: body.address,
|
|
||||||
port: body.port,
|
|
||||||
description: body.description,
|
description: body.description,
|
||||||
|
external_address: body.external_address,
|
||||||
|
external_port: body.external_port,
|
||||||
|
listen_port: body.listen_port,
|
||||||
|
type: body.type,
|
||||||
|
env_variables: body.env_variables,
|
||||||
|
memory: body.memory,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -328,10 +323,17 @@ const app = new Elysia()
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
name: t.String({ minLength: 1 }),
|
id: t.String({ minLength: 1 }),
|
||||||
description: t.Nullable(t.String({ minLength: 1 })),
|
description: t.Nullable(t.String({ minLength: 1 })),
|
||||||
address: t.String({ minLength: 1 }),
|
external_address: t.String({ minLength: 1 }),
|
||||||
port: t.Integer({ minimum: 1, maximum: 65535 }),
|
external_port: t.Integer({ minimum: 1, maximum: 65535 }),
|
||||||
|
listen_port: t.Optional(t.Integer({ minimum: 1, maximum: 65535 })),
|
||||||
|
type: t.Optional(t.Enum({ VELOCITY: "VELOCITY", BUNGEECORD: "BUNGEECORD" })),
|
||||||
|
env_variables: t.Optional(t.Array(t.Object({
|
||||||
|
key: t.String({ minLength: 1 }),
|
||||||
|
value: t.String(),
|
||||||
|
}))),
|
||||||
|
memory: t.Optional(t.String({ minLength: 1 })),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -348,22 +350,16 @@ const app = new Elysia()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body.name) {
|
// Create update data with only fields that exist in the model
|
||||||
const _server = await ServerService.getServerByName(body.name);
|
const data: any = {};
|
||||||
if (_server && _server.id !== id) {
|
|
||||||
return error("Conflict", {
|
|
||||||
success: false,
|
|
||||||
message: ReturnError.SERVER_NAME_IN_USE,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
if (body.description !== undefined) data.description = body.description;
|
||||||
name: body.name,
|
if (body.external_address !== undefined) data.external_address = body.external_address;
|
||||||
description: body.description,
|
if (body.external_port !== undefined) data.external_port = body.external_port;
|
||||||
address: body.address,
|
if (body.listen_port !== undefined) data.listen_port = body.listen_port;
|
||||||
port: body.port,
|
if (body.type !== undefined) data.type = body.type;
|
||||||
};
|
if (body.memory !== undefined) data.memory = body.memory;
|
||||||
|
// Don't allow service_type to be updated through API
|
||||||
|
|
||||||
await prisma.reverseProxyServer.update({
|
await prisma.reverseProxyServer.update({
|
||||||
where: { id },
|
where: { id },
|
||||||
@@ -384,10 +380,12 @@ const app = new Elysia()
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
body: t.Object({
|
body: t.Object({
|
||||||
name: t.Optional(t.String({ minLength: 1 })),
|
|
||||||
description: t.Optional(t.Nullable(t.String({ minLength: 1 }))),
|
description: t.Optional(t.Nullable(t.String({ minLength: 1 }))),
|
||||||
address: t.Optional(t.String({ minLength: 1 })),
|
external_address: t.Optional(t.String({ minLength: 1 })),
|
||||||
port: t.Optional(t.Integer({ minimum: 1, maximum: 65535 })),
|
external_port: t.Optional(t.Integer({ minimum: 1, maximum: 65535 })),
|
||||||
|
listen_port: t.Optional(t.Integer({ minimum: 1, maximum: 65535 })),
|
||||||
|
type: t.Optional(t.Enum({ VELOCITY: "VELOCITY", BUNGEECORD: "BUNGEECORD" })),
|
||||||
|
memory: t.Optional(t.String({ minLength: 1 })),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -410,6 +408,181 @@ const app = new Elysia()
|
|||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
.get("/servers/:id/env", async ({ params: { id } }) => {
|
||||||
|
const server = await ServerService.getServerById(id);
|
||||||
|
if (!server) {
|
||||||
|
return error("Not Found", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
env_variables: server.env_variables,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.post(
|
||||||
|
"/servers/:id/env",
|
||||||
|
async ({ params: { id }, body }) => {
|
||||||
|
const server = await ServerService.getServerById(id);
|
||||||
|
if (!server) {
|
||||||
|
return error("Not Found", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const envVar = await prisma.customEnvironmentVariable.upsert({
|
||||||
|
where: {
|
||||||
|
key_server_id: {
|
||||||
|
key: body.key,
|
||||||
|
server_id: id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
value: body.value,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: body.key,
|
||||||
|
value: body.value,
|
||||||
|
server_id: id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
env_var: envVar,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
key: t.String({ minLength: 1 }),
|
||||||
|
value: t.String(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.delete("/servers/:id/env/:key", async ({ params: { id, key } }) => {
|
||||||
|
const server = await ServerService.getServerById(id);
|
||||||
|
if (!server) {
|
||||||
|
return error("Not Found", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await prisma.customEnvironmentVariable.delete({
|
||||||
|
where: {
|
||||||
|
key_server_id: {
|
||||||
|
key,
|
||||||
|
server_id: id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return error("Not Found", {
|
||||||
|
success: false,
|
||||||
|
message: "Environment variable not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.get("/reverse_proxy_servers/:id/env", async ({ params: { id } }) => {
|
||||||
|
const server = await ServerService.getReverseProxyServerById(id);
|
||||||
|
if (!server) {
|
||||||
|
return error("Not Found", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
env_variables: server.env_variables,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.post(
|
||||||
|
"/reverse_proxy_servers/:id/env",
|
||||||
|
async ({ params: { id }, body }) => {
|
||||||
|
const server = await ServerService.getReverseProxyServerById(id);
|
||||||
|
if (!server) {
|
||||||
|
return error("Not Found", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const envVar = await prisma.customEnvironmentVariable.upsert({
|
||||||
|
where: {
|
||||||
|
key_reverse_proxy_id: {
|
||||||
|
key: body.key,
|
||||||
|
reverse_proxy_id: id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
value: body.value,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key: body.key,
|
||||||
|
value: body.value,
|
||||||
|
reverse_proxy_id: id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
env_var: envVar,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
body: t.Object({
|
||||||
|
key: t.String({ minLength: 1 }),
|
||||||
|
value: t.String(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.delete("/reverse_proxy_servers/:id/env/:key", async ({ params: { id, key } }) => {
|
||||||
|
const server = await ServerService.getReverseProxyServerById(id);
|
||||||
|
if (!server) {
|
||||||
|
return error("Not Found", {
|
||||||
|
success: false,
|
||||||
|
message: ReturnError.SERVER_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await prisma.customEnvironmentVariable.delete({
|
||||||
|
where: {
|
||||||
|
key_reverse_proxy_id: {
|
||||||
|
key,
|
||||||
|
reverse_proxy_id: id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return error("Not Found", {
|
||||||
|
success: false,
|
||||||
|
message: "Environment variable not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
.listen(3000, async () => {
|
.listen(3000, async () => {
|
||||||
console.log("Server is running on port 3000");
|
console.log("Server is running on port 3000");
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
@@ -4,73 +4,126 @@ import crypto from "node:crypto";
|
|||||||
|
|
||||||
export namespace ServerService {
|
export namespace ServerService {
|
||||||
export async function getAllServers(omitSensitive = false) {
|
export async function getAllServers(omitSensitive = false) {
|
||||||
|
if (omitSensitive) {
|
||||||
return await prisma.server.findMany({
|
return await prisma.server.findMany({
|
||||||
omit: {
|
select: {
|
||||||
api_key: omitSensitive,
|
id: true,
|
||||||
|
type: true,
|
||||||
|
description: true,
|
||||||
|
listen_port: true,
|
||||||
|
memory: true,
|
||||||
|
created_at: true,
|
||||||
|
updated_at: true,
|
||||||
|
env_variables: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
return await prisma.server.findMany({
|
||||||
|
include: {
|
||||||
|
env_variables: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAllReverseProxyServers(omitSensitive = false) {
|
export async function getAllReverseProxyServers(omitSensitive = false) {
|
||||||
|
if (omitSensitive) {
|
||||||
return await prisma.reverseProxyServer.findMany({
|
return await prisma.reverseProxyServer.findMany({
|
||||||
omit: {
|
select: {
|
||||||
api_key: omitSensitive,
|
id: true,
|
||||||
|
type: true,
|
||||||
|
description: true,
|
||||||
|
external_address: true,
|
||||||
|
external_port: true,
|
||||||
|
listen_port: true,
|
||||||
|
memory: true,
|
||||||
|
created_at: true,
|
||||||
|
updated_at: true,
|
||||||
|
env_variables: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return await prisma.reverseProxyServer.findMany({
|
||||||
|
include: {
|
||||||
|
env_variables: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getServerById(id: string, omitSensitive = false) {
|
export async function getServerById(id: string, omitSensitive = false) {
|
||||||
|
if (omitSensitive) {
|
||||||
return await prisma.server.findUnique({
|
return await prisma.server.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
omit: {
|
select: {
|
||||||
api_key: omitSensitive,
|
id: true,
|
||||||
|
type: true,
|
||||||
|
description: true,
|
||||||
|
listen_port: true,
|
||||||
|
memory: true,
|
||||||
|
created_at: true,
|
||||||
|
updated_at: true,
|
||||||
|
env_variables: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
return await prisma.server.findUnique({
|
||||||
|
where: { id },
|
||||||
|
include: {
|
||||||
|
env_variables: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getReverseProxyServerById(
|
export async function getReverseProxyServerById(
|
||||||
id: string,
|
id: string,
|
||||||
omitSensitive = false
|
omitSensitive = false
|
||||||
) {
|
) {
|
||||||
|
if (omitSensitive) {
|
||||||
return await prisma.reverseProxyServer.findUnique({
|
return await prisma.reverseProxyServer.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
omit: {
|
select: {
|
||||||
api_key: omitSensitive,
|
id: true,
|
||||||
|
type: true,
|
||||||
|
description: true,
|
||||||
|
external_address: true,
|
||||||
|
external_port: true,
|
||||||
|
listen_port: true,
|
||||||
|
memory: true,
|
||||||
|
created_at: true,
|
||||||
|
updated_at: true,
|
||||||
|
env_variables: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
} else {
|
||||||
|
|
||||||
export async function getServerByName(name: string, omitSensitive = false) {
|
|
||||||
return await prisma.server.findUnique({
|
|
||||||
where: { name },
|
|
||||||
omit: {
|
|
||||||
api_key: omitSensitive,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getReverseProxyServerByName(
|
|
||||||
name: string,
|
|
||||||
omitSensitive = false
|
|
||||||
) {
|
|
||||||
return await prisma.reverseProxyServer.findUnique({
|
return await prisma.reverseProxyServer.findUnique({
|
||||||
where: { name },
|
where: { id },
|
||||||
omit: {
|
include: {
|
||||||
api_key: omitSensitive,
|
env_variables: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function createReverseProxyServer({
|
export async function createReverseProxyServer({
|
||||||
name,
|
id,
|
||||||
description,
|
description,
|
||||||
address,
|
external_address,
|
||||||
port,
|
external_port,
|
||||||
|
listen_port,
|
||||||
|
type,
|
||||||
|
env_variables,
|
||||||
|
memory,
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
id: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
address: string;
|
external_address: string;
|
||||||
port: number;
|
external_port: number;
|
||||||
|
listen_port?: number;
|
||||||
|
type?: "VELOCITY" | "BUNGEECORD";
|
||||||
|
env_variables?: { key: string; value: string }[];
|
||||||
|
memory?: string;
|
||||||
}) {
|
}) {
|
||||||
let token = crypto.randomBytes(64).toString("hex");
|
let token = crypto.randomBytes(64).toString("hex");
|
||||||
token = token
|
token = token
|
||||||
@@ -81,29 +134,41 @@ export namespace ServerService {
|
|||||||
|
|
||||||
return await prisma.reverseProxyServer.create({
|
return await prisma.reverseProxyServer.create({
|
||||||
data: {
|
data: {
|
||||||
name,
|
id,
|
||||||
description,
|
description,
|
||||||
address,
|
external_address,
|
||||||
port,
|
external_port,
|
||||||
|
listen_port: listen_port || 25565,
|
||||||
|
type: type || "VELOCITY",
|
||||||
api_key: token,
|
api_key: token,
|
||||||
|
memory: memory || "512M",
|
||||||
|
env_variables: env_variables ? {
|
||||||
|
create: env_variables.map(ev => ({
|
||||||
|
key: ev.key,
|
||||||
|
value: ev.value
|
||||||
|
}))
|
||||||
|
} : undefined,
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
env_variables: true,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createServer({
|
export async function createServer({
|
||||||
name,
|
id,
|
||||||
description,
|
description,
|
||||||
address,
|
|
||||||
port,
|
|
||||||
type,
|
type,
|
||||||
join_priority,
|
listen_port,
|
||||||
|
env_variables,
|
||||||
|
memory,
|
||||||
}: {
|
}: {
|
||||||
name: string;
|
id: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
address: string;
|
|
||||||
port: number;
|
|
||||||
type: ServerType;
|
type: ServerType;
|
||||||
join_priority: number | null;
|
listen_port: number;
|
||||||
|
env_variables?: { key: string; value: string }[];
|
||||||
|
memory?: string;
|
||||||
}) {
|
}) {
|
||||||
let token = crypto.randomBytes(64).toString("hex");
|
let token = crypto.randomBytes(64).toString("hex");
|
||||||
token = token
|
token = token
|
||||||
@@ -114,13 +179,97 @@ export namespace ServerService {
|
|||||||
|
|
||||||
return await prisma.server.create({
|
return await prisma.server.create({
|
||||||
data: {
|
data: {
|
||||||
name,
|
id,
|
||||||
description,
|
description,
|
||||||
address,
|
|
||||||
port,
|
|
||||||
type,
|
type,
|
||||||
|
listen_port,
|
||||||
api_key: token,
|
api_key: token,
|
||||||
join_priority,
|
memory: memory || "1G",
|
||||||
|
env_variables: env_variables ? {
|
||||||
|
create: env_variables.map(ev => ({
|
||||||
|
key: ev.key,
|
||||||
|
value: ev.value
|
||||||
|
}))
|
||||||
|
} : undefined,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
env_variables: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setServerEnvironmentVariable(
|
||||||
|
serverId: string,
|
||||||
|
key: string,
|
||||||
|
value: string
|
||||||
|
) {
|
||||||
|
// Upsert pattern - create if doesn't exist, update if it does
|
||||||
|
return await prisma.customEnvironmentVariable.upsert({
|
||||||
|
where: {
|
||||||
|
key_server_id: {
|
||||||
|
key,
|
||||||
|
server_id: serverId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
server_id: serverId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setReverseProxyEnvironmentVariable(
|
||||||
|
proxyId: string,
|
||||||
|
key: string,
|
||||||
|
value: string
|
||||||
|
) {
|
||||||
|
// Upsert pattern - create if doesn't exist, update if it does
|
||||||
|
return await prisma.customEnvironmentVariable.upsert({
|
||||||
|
where: {
|
||||||
|
key_reverse_proxy_id: {
|
||||||
|
key,
|
||||||
|
reverse_proxy_id: proxyId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {
|
||||||
|
value,
|
||||||
|
},
|
||||||
|
create: {
|
||||||
|
key,
|
||||||
|
value,
|
||||||
|
reverse_proxy_id: proxyId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteServerEnvironmentVariable(
|
||||||
|
serverId: string,
|
||||||
|
key: string
|
||||||
|
) {
|
||||||
|
return await prisma.customEnvironmentVariable.delete({
|
||||||
|
where: {
|
||||||
|
key_server_id: {
|
||||||
|
key,
|
||||||
|
server_id: serverId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteReverseProxyEnvironmentVariable(
|
||||||
|
proxyId: string,
|
||||||
|
key: string
|
||||||
|
) {
|
||||||
|
return await prisma.customEnvironmentVariable.delete({
|
||||||
|
where: {
|
||||||
|
key_reverse_proxy_id: {
|
||||||
|
key,
|
||||||
|
reverse_proxy_id: proxyId,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,11 @@
|
|||||||
"db:generate": "bun --filter @minikura/db generate",
|
"db:generate": "bun --filter @minikura/db generate",
|
||||||
"db:studio": "bun --filter @minikura/db studio",
|
"db:studio": "bun --filter @minikura/db studio",
|
||||||
"db:push": "bun --filter @minikura/db push",
|
"db:push": "bun --filter @minikura/db push",
|
||||||
"db:reset": "bun --filter @minikura/db reset"
|
"db:reset": "bun --filter @minikura/db reset",
|
||||||
|
"k8s:dev": "bun --filter @minikura/k8s-operator dev",
|
||||||
|
"k8s:build": "bun --filter @minikura/k8s-operator build",
|
||||||
|
"k8s:start": "bun --filter @minikura/k8s-operator start",
|
||||||
|
"k8s:crd": "bun --filter @minikura/k8s-operator apply-crds"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.9.1",
|
"@biomejs/biome": "^1.9.1",
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
// This is your Prisma schema file,
|
|
||||||
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
|
||||||
|
|
||||||
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
|
||||||
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
previewFeatures = ["omitApi"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
@@ -19,26 +12,33 @@ enum ServerType {
|
|||||||
STATELESS
|
STATELESS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ReverseProxyServerType {
|
||||||
|
VELOCITY
|
||||||
|
BUNGEECORD
|
||||||
|
}
|
||||||
|
|
||||||
model ReverseProxyServer {
|
model ReverseProxyServer {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
type ReverseProxyServerType
|
||||||
description String?
|
description String?
|
||||||
|
external_address String
|
||||||
|
external_port Int
|
||||||
|
listen_port Int @default(25565)
|
||||||
|
memory String @default("512M")
|
||||||
api_key String @unique
|
api_key String @unique
|
||||||
address String
|
env_variables CustomEnvironmentVariable[] @relation("ReverseProxyServerEnvVars")
|
||||||
port Int
|
|
||||||
created_at DateTime @default(now())
|
created_at DateTime @default(now())
|
||||||
updated_at DateTime @updatedAt
|
updated_at DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
model Server {
|
model Server {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @unique
|
|
||||||
description String?
|
|
||||||
address String
|
|
||||||
port Int
|
|
||||||
type ServerType
|
type ServerType
|
||||||
|
description String?
|
||||||
|
listen_port Int @default(25565)
|
||||||
|
memory String @default("1G")
|
||||||
|
env_variables CustomEnvironmentVariable[] @relation("ServerEnvVars")
|
||||||
api_key String @unique
|
api_key String @unique
|
||||||
join_priority Int?
|
|
||||||
created_at DateTime @default(now())
|
created_at DateTime @default(now())
|
||||||
updated_at DateTime @updatedAt
|
updated_at DateTime @updatedAt
|
||||||
}
|
}
|
||||||
@@ -62,3 +62,20 @@ model Session {
|
|||||||
created_at DateTime @default(now())
|
created_at DateTime @default(now())
|
||||||
updated_at DateTime @updatedAt
|
updated_at DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model CustomEnvironmentVariable {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
key String
|
||||||
|
value String
|
||||||
|
created_at DateTime @default(now())
|
||||||
|
updated_at DateTime @updatedAt
|
||||||
|
|
||||||
|
server_id String?
|
||||||
|
server Server? @relation("ServerEnvVars", fields: [server_id], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
reverse_proxy_id String?
|
||||||
|
reverse_proxy_server ReverseProxyServer? @relation("ReverseProxyServerEnvVars", fields: [reverse_proxy_id], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([key, server_id])
|
||||||
|
@@unique([key, reverse_proxy_id])
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"tasks": {
|
"tasks": {
|
||||||
"build": {
|
"build": {
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
"outputs": [".next/**", "!.next/cache/**"]
|
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
|
||||||
},
|
},
|
||||||
"typecheck": {
|
"typecheck": {
|
||||||
"dependsOn": ["^typecheck"]
|
"dependsOn": ["^typecheck"]
|
||||||
|
|||||||
Reference in New Issue
Block a user