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()
|
||||
.use(swagger())
|
||||
.ws("/ws", {
|
||||
body: t.Object({
|
||||
message: t.String(),
|
||||
}),
|
||||
// query: t.Object({
|
||||
// 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,
|
||||
});
|
||||
.use(swagger({
|
||||
path: '/swagger',
|
||||
documentation: {
|
||||
info: {
|
||||
title: 'Minikura API Documentation',
|
||||
version: '1.0.0'
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
session_token.httpOnly = true;
|
||||
session_token.value = session.token;
|
||||
|
||||
}))
|
||||
.group('/api', app => app
|
||||
.derive(async ({ headers, cookie: { session_token }, path }) => {
|
||||
// Skip token validation for login route
|
||||
if (path === '/api/login') {
|
||||
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 token = headers.authorization?.split(" ")[1];
|
||||
|
||||
@@ -161,6 +121,48 @@ const app = new Elysia()
|
||||
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 } }) => {
|
||||
if (!session) return { success: true };
|
||||
|
||||
@@ -182,11 +184,11 @@ const app = new Elysia()
|
||||
"/servers",
|
||||
async ({ body, error }) => {
|
||||
// Must be a-z, A-Z, 0-9, and -_ only
|
||||
if (!/^[a-zA-Z0-9-_]+$/.test(body.name)) {
|
||||
return error("Bad Request", "Name must be a-z, A-Z, 0-9, and -_ only");
|
||||
if (!/^[a-zA-Z0-9-_]+$/.test(body.id)) {
|
||||
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) {
|
||||
return error("Conflict", {
|
||||
success: false,
|
||||
@@ -195,12 +197,12 @@ const app = new Elysia()
|
||||
}
|
||||
|
||||
const server = await ServerService.createServer({
|
||||
name: body.name,
|
||||
id: body.id,
|
||||
description: body.description,
|
||||
address: body.address,
|
||||
port: body.port,
|
||||
listen_port: body.listen_port,
|
||||
type: body.type,
|
||||
join_priority: body.join_priority,
|
||||
env_variables: body.env_variables,
|
||||
memory: body.memory,
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -212,12 +214,15 @@ const app = new Elysia()
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
name: t.String({ minLength: 1 }),
|
||||
id: t.String({ minLength: 1 }),
|
||||
description: t.Nullable(t.String({ minLength: 1 })),
|
||||
address: t.String({ minLength: 1 }),
|
||||
port: t.Integer({ minimum: 1, maximum: 65535 }),
|
||||
listen_port: t.Integer({ minimum: 1, maximum: 65535 }),
|
||||
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) {
|
||||
const _server = await ServerService.getServerByName(body.name);
|
||||
if (_server && _server.id !== id) {
|
||||
return error("Conflict", {
|
||||
success: false,
|
||||
message: ReturnError.SERVER_NAME_IN_USE,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Create update data with only fields that exist in the model
|
||||
const data: any = {};
|
||||
|
||||
const data = {
|
||||
name: body.name,
|
||||
description: body.description,
|
||||
address: body.address,
|
||||
port: body.port,
|
||||
join_priority: body.join_priority,
|
||||
};
|
||||
if (body.description !== undefined) data.description = body.description;
|
||||
if (body.listen_port !== undefined) data.listen_port = body.listen_port;
|
||||
if (body.memory !== undefined) data.memory = body.memory;
|
||||
// Don't allow service_type to be updated through API
|
||||
|
||||
await prisma.server.update({
|
||||
where: { id },
|
||||
@@ -266,11 +261,9 @@ const app = new Elysia()
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
name: t.Optional(t.String({ minLength: 1 })),
|
||||
description: t.Optional(t.Nullable(t.String({ minLength: 1 }))),
|
||||
address: t.Optional(t.String({ minLength: 1 })),
|
||||
port: t.Optional(t.Integer({ minimum: 1, maximum: 65535 })),
|
||||
join_priority: t.Optional(t.Integer({ minimum: 0 })),
|
||||
listen_port: t.Optional(t.Integer({ minimum: 1, maximum: 65535 })),
|
||||
memory: t.Optional(t.String({ minLength: 1 })),
|
||||
}),
|
||||
}
|
||||
)
|
||||
@@ -298,13 +291,11 @@ const app = new Elysia()
|
||||
"/reverse_proxy_servers",
|
||||
async ({ body, error }) => {
|
||||
// Must be a-z, A-Z, 0-9, and -_ only
|
||||
if (!/^[a-zA-Z0-9-_]+$/.test(body.name)) {
|
||||
return error("Bad Request", "Name must be a-z, A-Z, 0-9, and -_ only");
|
||||
if (!/^[a-zA-Z0-9-_]+$/.test(body.id)) {
|
||||
return error("Bad Request", "ID must be a-z, A-Z, 0-9, and -_ only");
|
||||
}
|
||||
|
||||
const _server = await ServerService.getReverseProxyServerByName(
|
||||
body.name
|
||||
);
|
||||
const _server = await ServerService.getReverseProxyServerById(body.id);
|
||||
if (_server) {
|
||||
return error("Conflict", {
|
||||
success: false,
|
||||
@@ -313,10 +304,14 @@ const app = new Elysia()
|
||||
}
|
||||
|
||||
const server = await ServerService.createReverseProxyServer({
|
||||
name: body.name,
|
||||
address: body.address,
|
||||
port: body.port,
|
||||
id: body.id,
|
||||
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 {
|
||||
@@ -328,10 +323,17 @@ const app = new Elysia()
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
name: t.String({ minLength: 1 }),
|
||||
id: t.String({ minLength: 1 }),
|
||||
description: t.Nullable(t.String({ minLength: 1 })),
|
||||
address: t.String({ minLength: 1 }),
|
||||
port: t.Integer({ minimum: 1, maximum: 65535 }),
|
||||
external_address: t.String({ minLength: 1 }),
|
||||
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) {
|
||||
const _server = await ServerService.getServerByName(body.name);
|
||||
if (_server && _server.id !== id) {
|
||||
return error("Conflict", {
|
||||
success: false,
|
||||
message: ReturnError.SERVER_NAME_IN_USE,
|
||||
});
|
||||
}
|
||||
}
|
||||
// Create update data with only fields that exist in the model
|
||||
const data: any = {};
|
||||
|
||||
const data = {
|
||||
name: body.name,
|
||||
description: body.description,
|
||||
address: body.address,
|
||||
port: body.port,
|
||||
};
|
||||
if (body.description !== undefined) data.description = body.description;
|
||||
if (body.external_address !== undefined) data.external_address = body.external_address;
|
||||
if (body.external_port !== undefined) data.external_port = body.external_port;
|
||||
if (body.listen_port !== undefined) data.listen_port = body.listen_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({
|
||||
where: { id },
|
||||
@@ -384,10 +380,12 @@ const app = new Elysia()
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
name: t.Optional(t.String({ minLength: 1 })),
|
||||
description: t.Optional(t.Nullable(t.String({ minLength: 1 }))),
|
||||
address: t.Optional(t.String({ minLength: 1 })),
|
||||
port: t.Optional(t.Integer({ minimum: 1, maximum: 65535 })),
|
||||
external_address: t.Optional(t.String({ minLength: 1 })),
|
||||
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,
|
||||
};
|
||||
})
|
||||
.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 () => {
|
||||
console.log("Server is running on port 3000");
|
||||
bootstrap();
|
||||
|
||||
@@ -4,73 +4,126 @@ import crypto from "node:crypto";
|
||||
|
||||
export namespace ServerService {
|
||||
export async function getAllServers(omitSensitive = false) {
|
||||
if (omitSensitive) {
|
||||
return await prisma.server.findMany({
|
||||
omit: {
|
||||
api_key: omitSensitive,
|
||||
select: {
|
||||
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) {
|
||||
if (omitSensitive) {
|
||||
return await prisma.reverseProxyServer.findMany({
|
||||
omit: {
|
||||
api_key: omitSensitive,
|
||||
select: {
|
||||
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) {
|
||||
if (omitSensitive) {
|
||||
return await prisma.server.findUnique({
|
||||
where: { id },
|
||||
omit: {
|
||||
api_key: omitSensitive,
|
||||
select: {
|
||||
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(
|
||||
id: string,
|
||||
omitSensitive = false
|
||||
) {
|
||||
if (omitSensitive) {
|
||||
return await prisma.reverseProxyServer.findUnique({
|
||||
where: { id },
|
||||
omit: {
|
||||
api_key: omitSensitive,
|
||||
select: {
|
||||
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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
) {
|
||||
} else {
|
||||
return await prisma.reverseProxyServer.findUnique({
|
||||
where: { name },
|
||||
omit: {
|
||||
api_key: omitSensitive,
|
||||
where: { id },
|
||||
include: {
|
||||
env_variables: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function createReverseProxyServer({
|
||||
name,
|
||||
id,
|
||||
description,
|
||||
address,
|
||||
port,
|
||||
external_address,
|
||||
external_port,
|
||||
listen_port,
|
||||
type,
|
||||
env_variables,
|
||||
memory,
|
||||
}: {
|
||||
name: string;
|
||||
id: string;
|
||||
description: string | null;
|
||||
address: string;
|
||||
port: number;
|
||||
external_address: string;
|
||||
external_port: number;
|
||||
listen_port?: number;
|
||||
type?: "VELOCITY" | "BUNGEECORD";
|
||||
env_variables?: { key: string; value: string }[];
|
||||
memory?: string;
|
||||
}) {
|
||||
let token = crypto.randomBytes(64).toString("hex");
|
||||
token = token
|
||||
@@ -81,29 +134,41 @@ export namespace ServerService {
|
||||
|
||||
return await prisma.reverseProxyServer.create({
|
||||
data: {
|
||||
name,
|
||||
id,
|
||||
description,
|
||||
address,
|
||||
port,
|
||||
external_address,
|
||||
external_port,
|
||||
listen_port: listen_port || 25565,
|
||||
type: type || "VELOCITY",
|
||||
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({
|
||||
name,
|
||||
id,
|
||||
description,
|
||||
address,
|
||||
port,
|
||||
type,
|
||||
join_priority,
|
||||
listen_port,
|
||||
env_variables,
|
||||
memory,
|
||||
}: {
|
||||
name: string;
|
||||
id: string;
|
||||
description: string | null;
|
||||
address: string;
|
||||
port: number;
|
||||
type: ServerType;
|
||||
join_priority: number | null;
|
||||
listen_port: number;
|
||||
env_variables?: { key: string; value: string }[];
|
||||
memory?: string;
|
||||
}) {
|
||||
let token = crypto.randomBytes(64).toString("hex");
|
||||
token = token
|
||||
@@ -114,13 +179,97 @@ export namespace ServerService {
|
||||
|
||||
return await prisma.server.create({
|
||||
data: {
|
||||
name,
|
||||
id,
|
||||
description,
|
||||
address,
|
||||
port,
|
||||
type,
|
||||
listen_port,
|
||||
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:studio": "bun --filter @minikura/db studio",
|
||||
"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": {
|
||||
"@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 {
|
||||
provider = "prisma-client-js"
|
||||
previewFeatures = ["omitApi"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
@@ -19,26 +12,33 @@ enum ServerType {
|
||||
STATELESS
|
||||
}
|
||||
|
||||
enum ReverseProxyServerType {
|
||||
VELOCITY
|
||||
BUNGEECORD
|
||||
}
|
||||
|
||||
model ReverseProxyServer {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
type ReverseProxyServerType
|
||||
description String?
|
||||
external_address String
|
||||
external_port Int
|
||||
listen_port Int @default(25565)
|
||||
memory String @default("512M")
|
||||
api_key String @unique
|
||||
address String
|
||||
port Int
|
||||
env_variables CustomEnvironmentVariable[] @relation("ReverseProxyServerEnvVars")
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Server {
|
||||
id String @id @default(cuid())
|
||||
name String @unique
|
||||
description String?
|
||||
address String
|
||||
port Int
|
||||
type ServerType
|
||||
description String?
|
||||
listen_port Int @default(25565)
|
||||
memory String @default("1G")
|
||||
env_variables CustomEnvironmentVariable[] @relation("ServerEnvVars")
|
||||
api_key String @unique
|
||||
join_priority Int?
|
||||
created_at DateTime @default(now())
|
||||
updated_at DateTime @updatedAt
|
||||
}
|
||||
@@ -62,3 +62,20 @@ model Session {
|
||||
created_at DateTime @default(now())
|
||||
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": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": [".next/**", "!.next/cache/**"]
|
||||
"outputs": [".next/**", "!.next/cache/**", "dist/**"]
|
||||
},
|
||||
"typecheck": {
|
||||
"dependsOn": ["^typecheck"]
|
||||
|
||||
Reference in New Issue
Block a user