230
src/lib/database/applications.ts
Normal file
230
src/lib/database/applications.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import { removeProxyConfiguration } from '$lib/haproxy';
|
||||
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
|
||||
|
||||
import { getDomain, removeDestinationDocker } from '$lib/common';
|
||||
import { prisma } from './common';
|
||||
|
||||
export async function listApplications(teamId) {
|
||||
return await prisma.application.findMany({ where: { teams: { some: { id: teamId } } } });
|
||||
}
|
||||
|
||||
export async function newApplication({ name, teamId }) {
|
||||
return await prisma.application.create({
|
||||
data: {
|
||||
name,
|
||||
teams: { connect: { id: teamId } },
|
||||
settings: { create: { debug: false, previews: false } }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function importApplication({
|
||||
name,
|
||||
teamId,
|
||||
fqdn,
|
||||
port,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
installCommand
|
||||
}) {
|
||||
return await prisma.application.create({
|
||||
data: {
|
||||
name,
|
||||
fqdn,
|
||||
port,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
installCommand,
|
||||
teams: { connect: { id: teamId } }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeApplication({ id, teamId }) {
|
||||
const { fqdn, destinationDockerId, destinationDocker } = await prisma.application.findUnique({
|
||||
where: { id },
|
||||
include: { destinationDocker: true }
|
||||
});
|
||||
const domain = getDomain(fqdn);
|
||||
if (destinationDockerId) {
|
||||
const host = getEngine(destinationDocker.engine);
|
||||
const { stdout: containers } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker ps -a --filter network=${destinationDocker.network} --filter name=${id} --format '{{json .}}'`
|
||||
);
|
||||
if (containers) {
|
||||
const containersArray = containers.trim().split('\n');
|
||||
for (const container of containersArray) {
|
||||
const containerObj = JSON.parse(container);
|
||||
const id = containerObj.ID;
|
||||
const preview = containerObj.Image.split('-')[1];
|
||||
await removeDestinationDocker({ id, engine: destinationDocker.engine });
|
||||
if (preview) {
|
||||
await removeProxyConfiguration({ domain: `${preview}.${domain}` });
|
||||
} else {
|
||||
await removeProxyConfiguration({ domain });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await prisma.applicationSettings.deleteMany({ where: { application: { id } } });
|
||||
await prisma.buildLog.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.secret.deleteMany({ where: { applicationId: id } });
|
||||
await prisma.application.deleteMany({ where: { id, teams: { some: { id: teamId } } } });
|
||||
}
|
||||
|
||||
export async function getApplicationWebhook({ projectId, branch }) {
|
||||
try {
|
||||
let body = await prisma.application.findFirst({
|
||||
where: { projectId, branch },
|
||||
include: {
|
||||
destinationDocker: true,
|
||||
settings: true,
|
||||
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||
secrets: true
|
||||
}
|
||||
});
|
||||
|
||||
if (body.gitSource?.githubApp?.clientSecret) {
|
||||
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret);
|
||||
}
|
||||
if (body.gitSource?.githubApp?.webhookSecret) {
|
||||
body.gitSource.githubApp.webhookSecret = decrypt(body.gitSource.githubApp.webhookSecret);
|
||||
}
|
||||
if (body.gitSource?.githubApp?.privateKey) {
|
||||
body.gitSource.githubApp.privateKey = decrypt(body.gitSource.githubApp.privateKey);
|
||||
}
|
||||
if (body?.gitSource?.gitlabApp?.appSecret) {
|
||||
body.gitSource.gitlabApp.appSecret = decrypt(body.gitSource.gitlabApp.appSecret);
|
||||
}
|
||||
if (body?.gitSource?.gitlabApp?.webhookToken) {
|
||||
body.gitSource.gitlabApp.webhookToken = decrypt(body.gitSource.gitlabApp.webhookToken);
|
||||
}
|
||||
if (body?.secrets.length > 0) {
|
||||
body.secrets = body.secrets.map((s) => {
|
||||
s.value = decrypt(s.value);
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
return { ...body };
|
||||
} catch (e) {
|
||||
throw { status: 404, body: { message: e.message } };
|
||||
}
|
||||
}
|
||||
export async function getApplication({ id, teamId }) {
|
||||
let body = await prisma.application.findFirst({
|
||||
where: { id, teams: { some: { id: teamId } } },
|
||||
include: {
|
||||
destinationDocker: true,
|
||||
settings: true,
|
||||
gitSource: { include: { githubApp: true, gitlabApp: true } },
|
||||
secrets: true
|
||||
}
|
||||
});
|
||||
|
||||
if (body.gitSource?.githubApp?.clientSecret) {
|
||||
body.gitSource.githubApp.clientSecret = decrypt(body.gitSource.githubApp.clientSecret);
|
||||
}
|
||||
if (body.gitSource?.githubApp?.webhookSecret) {
|
||||
body.gitSource.githubApp.webhookSecret = decrypt(body.gitSource.githubApp.webhookSecret);
|
||||
}
|
||||
if (body.gitSource?.githubApp?.privateKey) {
|
||||
body.gitSource.githubApp.privateKey = decrypt(body.gitSource.githubApp.privateKey);
|
||||
}
|
||||
if (body?.gitSource?.gitlabApp?.appSecret) {
|
||||
body.gitSource.gitlabApp.appSecret = decrypt(body.gitSource.gitlabApp.appSecret);
|
||||
}
|
||||
if (body?.secrets.length > 0) {
|
||||
body.secrets = body.secrets.map((s) => {
|
||||
s.value = decrypt(s.value);
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
return { ...body };
|
||||
}
|
||||
|
||||
export async function configureGitRepository({ id, repository, branch, projectId, webhookToken }) {
|
||||
if (webhookToken) {
|
||||
const encryptedWebhookToken = encrypt(webhookToken);
|
||||
return await prisma.application.update({
|
||||
where: { id },
|
||||
data: {
|
||||
repository,
|
||||
branch,
|
||||
projectId,
|
||||
gitSource: { update: { gitlabApp: { update: { webhookToken: encryptedWebhookToken } } } }
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return await prisma.application.update({
|
||||
where: { id },
|
||||
data: { repository, branch, projectId }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function configureBuildPack({ id, buildPack }) {
|
||||
return await prisma.application.update({ where: { id }, data: { buildPack } });
|
||||
}
|
||||
|
||||
export async function configureApplication({
|
||||
id,
|
||||
buildPack,
|
||||
name,
|
||||
fqdn,
|
||||
port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory
|
||||
}) {
|
||||
return await prisma.application.update({
|
||||
where: { id },
|
||||
data: {
|
||||
buildPack,
|
||||
fqdn,
|
||||
port,
|
||||
installCommand,
|
||||
buildCommand,
|
||||
startCommand,
|
||||
baseDirectory,
|
||||
publishDirectory,
|
||||
name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function setApplicationSettings({ id, debug, previews }) {
|
||||
return await prisma.application.update({
|
||||
where: { id },
|
||||
data: { settings: { update: { debug, previews } } },
|
||||
include: { destinationDocker: true }
|
||||
});
|
||||
}
|
||||
|
||||
export async function createBuild({
|
||||
id,
|
||||
applicationId,
|
||||
destinationDockerId,
|
||||
gitSourceId,
|
||||
githubAppId,
|
||||
gitlabAppId,
|
||||
type
|
||||
}) {
|
||||
return await prisma.build.create({
|
||||
data: {
|
||||
id,
|
||||
applicationId,
|
||||
destinationDockerId,
|
||||
gitSourceId,
|
||||
githubAppId,
|
||||
gitlabAppId,
|
||||
status: 'running',
|
||||
type
|
||||
}
|
||||
});
|
||||
}
|
38
src/lib/database/checks.ts
Normal file
38
src/lib/database/checks.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { getDomain } from '$lib/common';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
|
||||
export async function isBranchAlreadyUsed({ repository, branch, id }) {
|
||||
const application = await prisma.application.findUnique({
|
||||
where: { id },
|
||||
include: { gitSource: true }
|
||||
});
|
||||
return await prisma.application.findFirst({
|
||||
where: { branch, repository, gitSource: { type: application.gitSource.type }, id: { not: id } }
|
||||
});
|
||||
}
|
||||
|
||||
export async function isDockerNetworkExists({ network }) {
|
||||
return await prisma.destinationDocker.findFirst({ where: { network } });
|
||||
}
|
||||
|
||||
export async function isSecretExists({ id, name }) {
|
||||
return await prisma.secret.findFirst({ where: { name, applicationId: id } });
|
||||
}
|
||||
|
||||
export async function isDomainConfigured({ id, fqdn }) {
|
||||
const domain = getDomain(fqdn);
|
||||
const foundApp = await prisma.application.findFirst({
|
||||
where: { fqdn: { endsWith: domain }, id: { not: id } },
|
||||
select: { fqdn: true }
|
||||
});
|
||||
const foundService = await prisma.service.findFirst({
|
||||
where: { fqdn: { endsWith: domain }, id: { not: id } },
|
||||
select: { fqdn: true }
|
||||
});
|
||||
const coolifyFqdn = await prisma.setting.findFirst({
|
||||
where: { fqdn: { endsWith: domain }, id: { not: id } },
|
||||
select: { fqdn: true }
|
||||
});
|
||||
if (foundApp || foundService || coolifyFqdn) return true;
|
||||
return false;
|
||||
}
|
232
src/lib/database/common.ts
Normal file
232
src/lib/database/common.ts
Normal file
@@ -0,0 +1,232 @@
|
||||
import { dev } from '$app/env';
|
||||
import { sentry } from '$lib/common';
|
||||
import * as Prisma from '@prisma/client';
|
||||
import { default as ProdPrisma } from '@prisma/client';
|
||||
import generator from 'generate-password';
|
||||
import forge from 'node-forge';
|
||||
|
||||
export function generatePassword(length = 24) {
|
||||
return generator.generate({
|
||||
length,
|
||||
numbers: true,
|
||||
strict: true
|
||||
});
|
||||
}
|
||||
|
||||
let { PrismaClient } = Prisma;
|
||||
let P = Prisma.Prisma;
|
||||
if (!dev) {
|
||||
PrismaClient = ProdPrisma.PrismaClient;
|
||||
P = ProdPrisma.Prisma;
|
||||
}
|
||||
let prismaOptions = {
|
||||
rejectOnNotFound: false
|
||||
};
|
||||
if (dev) {
|
||||
prismaOptions = {
|
||||
errorFormat: 'pretty',
|
||||
rejectOnNotFound: false,
|
||||
log: [
|
||||
{
|
||||
emit: 'event',
|
||||
level: 'query'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
export const prisma = new PrismaClient(prismaOptions);
|
||||
|
||||
export function PrismaErrorHandler(e) {
|
||||
sentry.captureException(e);
|
||||
const payload = {
|
||||
status: e.status || 500,
|
||||
body: {
|
||||
message: 'Ooops, something is not okay, are you okay?',
|
||||
error: e.error || e.message
|
||||
}
|
||||
};
|
||||
if (e.name === 'NotFoundError') {
|
||||
payload.status = 404;
|
||||
}
|
||||
if (e instanceof P.PrismaClientKnownRequestError) {
|
||||
if (e.code === 'P2002') {
|
||||
payload.body.message = 'Already exists. Choose another name.';
|
||||
}
|
||||
}
|
||||
// console.error(e)
|
||||
return payload;
|
||||
}
|
||||
export async function generateSshKeyPair(): Promise<{ publicKey: string; privateKey: string }> {
|
||||
return await new Promise(async (resolve, reject) => {
|
||||
forge.pki.rsa.generateKeyPair({ bits: 4096, workers: -1 }, function (err, keys) {
|
||||
if (keys) {
|
||||
resolve({
|
||||
publicKey: forge.ssh.publicKeyToOpenSSH(keys.publicKey),
|
||||
privateKey: forge.ssh.privateKeyToOpenSSH(keys.privateKey)
|
||||
});
|
||||
} else {
|
||||
reject(keys);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const supportedDatabaseTypesAndVersions = [
|
||||
{
|
||||
name: 'mongodb',
|
||||
fancyName: 'MongoDB',
|
||||
baseImage: 'bitnami/mongodb',
|
||||
versions: ['5.0.5', '4.4.11', '4.2.18', '4.0.27']
|
||||
},
|
||||
{ name: 'mysql', fancyName: 'MySQL', baseImage: 'bitnami/mysql', versions: ['8.0.27', '5.7.36'] },
|
||||
{
|
||||
name: 'postgresql',
|
||||
fancyName: 'PostgreSQL',
|
||||
baseImage: 'bitnami/postgresql',
|
||||
versions: ['14.1.0', '13.5.0', '12.9.0', '11.14.0', '10.19.0', '9.6.24']
|
||||
},
|
||||
{
|
||||
name: 'redis',
|
||||
fancyName: 'Redis',
|
||||
baseImage: 'bitnami/redis',
|
||||
versions: ['6.2.6', '6.0.16', '5.0.14']
|
||||
},
|
||||
{ name: 'couchdb', fancyName: 'CouchDB', baseImage: 'bitnami/couchdb', versions: ['3.2.1'] }
|
||||
];
|
||||
export const supportedServiceTypesAndVersions = [
|
||||
{
|
||||
name: 'plausibleanalytics',
|
||||
fancyName: 'Plausible Analytics',
|
||||
baseImage: 'plausible/analytics',
|
||||
versions: ['latest']
|
||||
},
|
||||
{ name: 'nocodb', fancyName: 'NocoDB', baseImage: 'nocodb/nocodb', versions: ['latest'] },
|
||||
{ name: 'minio', fancyName: 'MinIO', baseImage: 'minio/minio', versions: ['latest'] },
|
||||
{
|
||||
name: 'vscodeserver',
|
||||
fancyName: 'VSCode Server',
|
||||
baseImage: 'codercom/code-server',
|
||||
versions: ['latest']
|
||||
},
|
||||
{
|
||||
name: 'wordpress',
|
||||
fancyName: 'Wordpress',
|
||||
baseImage: 'wordpress',
|
||||
versions: ['latest', 'php8.1', 'php8.0', 'php7.4', 'php7.3']
|
||||
}
|
||||
];
|
||||
|
||||
export function getVersions(type) {
|
||||
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
||||
if (found) {
|
||||
return found.versions;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
export function getDatabaseImage(type) {
|
||||
const found = supportedDatabaseTypesAndVersions.find((t) => t.name === type);
|
||||
if (found) {
|
||||
return found.baseImage;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
export function getServiceImage(type) {
|
||||
const found = supportedServiceTypesAndVersions.find((t) => t.name === type);
|
||||
if (found) {
|
||||
return found.baseImage;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
export function generateDatabaseConfiguration(database) {
|
||||
const {
|
||||
id,
|
||||
dbUser,
|
||||
dbUserPassword,
|
||||
rootUser,
|
||||
rootUserPassword,
|
||||
defaultDatabase,
|
||||
version,
|
||||
type,
|
||||
settings: { appendOnly }
|
||||
} = database;
|
||||
const baseImage = getDatabaseImage(type);
|
||||
if (type === 'mysql') {
|
||||
return {
|
||||
// url: `mysql://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 3306}/${defaultDatabase}`,
|
||||
privatePort: 3306,
|
||||
environmentVariables: {
|
||||
MYSQL_USER: dbUser,
|
||||
MYSQL_PASSWORD: dbUserPassword,
|
||||
MYSQL_ROOT_PASSWORD: rootUserPassword,
|
||||
MYSQL_ROOT_USER: rootUser,
|
||||
MYSQL_DATABASE: defaultDatabase
|
||||
},
|
||||
image: `${baseImage}:${version}`,
|
||||
volume: `${id}-${type}-data:/bitnami/mysql/data`,
|
||||
ulimits: {}
|
||||
};
|
||||
} else if (type === 'mongodb') {
|
||||
return {
|
||||
// url: `mongodb://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 27017}/${defaultDatabase}`,
|
||||
privatePort: 27017,
|
||||
environmentVariables: {
|
||||
MONGODB_ROOT_USER: rootUser,
|
||||
MONGODB_ROOT_PASSWORD: rootUserPassword
|
||||
},
|
||||
image: `${baseImage}:${version}`,
|
||||
volume: `${id}-${type}-data:/bitnami/mongodb`,
|
||||
ulimits: {}
|
||||
};
|
||||
} else if (type === 'postgresql') {
|
||||
return {
|
||||
// url: `psql://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 5432}/${defaultDatabase}`,
|
||||
privatePort: 5432,
|
||||
environmentVariables: {
|
||||
POSTGRESQL_PASSWORD: dbUserPassword,
|
||||
POSTGRESQL_USERNAME: dbUser,
|
||||
POSTGRESQL_DATABASE: defaultDatabase
|
||||
},
|
||||
image: `${baseImage}:${version}`,
|
||||
volume: `${id}-${type}-data:/bitnami/postgresql`,
|
||||
ulimits: {}
|
||||
};
|
||||
} else if (type === 'redis') {
|
||||
return {
|
||||
// url: `redis://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 6379}/${defaultDatabase}`,
|
||||
privatePort: 6379,
|
||||
environmentVariables: {
|
||||
REDIS_PASSWORD: dbUserPassword,
|
||||
REDIS_AOF_ENABLED: appendOnly ? 'yes' : 'no'
|
||||
},
|
||||
image: `${baseImage}:${version}`,
|
||||
volume: `${id}-${type}-data:/bitnami/redis/data`,
|
||||
ulimits: {}
|
||||
};
|
||||
} else if (type === 'couchdb') {
|
||||
return {
|
||||
// url: `couchdb://${dbUser}:${dbUserPassword}@${id}:${isPublic ? port : 5984}/${defaultDatabase}`,
|
||||
privatePort: 5984,
|
||||
environmentVariables: {
|
||||
COUCHDB_PASSWORD: dbUserPassword,
|
||||
COUCHDB_USER: dbUser
|
||||
},
|
||||
image: `${baseImage}:${version}`,
|
||||
volume: `${id}-${type}-data:/bitnami/couchdb`,
|
||||
ulimits: {}
|
||||
};
|
||||
}
|
||||
// } else if (type === 'clickhouse') {
|
||||
// return {
|
||||
// url: `clickhouse://${dbUser}:${dbUserPassword}@${id}:${port}/${defaultDatabase}`,
|
||||
// privatePort: 9000,
|
||||
// image: `bitnami/clickhouse-server:${version}`,
|
||||
// volume: `${id}-${type}-data:/var/lib/clickhouse`,
|
||||
// ulimits: {
|
||||
// nofile: {
|
||||
// soft: 262144,
|
||||
// hard: 262144
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
148
src/lib/database/databases.ts
Normal file
148
src/lib/database/databases.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import { dockerInstance } from '$lib/docker';
|
||||
import cuid from 'cuid';
|
||||
import { generatePassword } from '.';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
import getPort from 'get-port';
|
||||
import { asyncExecShell, getEngine, removeContainer } from '$lib/common';
|
||||
|
||||
export async function listDatabases(teamId) {
|
||||
return await prisma.database.findMany({ where: { teams: { some: { id: teamId } } } });
|
||||
}
|
||||
export async function newDatabase({ name, teamId }) {
|
||||
const dbUser = cuid();
|
||||
const dbUserPassword = encrypt(generatePassword());
|
||||
const rootUser = cuid();
|
||||
const rootUserPassword = encrypt(generatePassword());
|
||||
const defaultDatabase = cuid();
|
||||
|
||||
let publicPort = await getPort();
|
||||
let i = 0;
|
||||
|
||||
do {
|
||||
const usedPorts = await prisma.database.findMany({ where: { publicPort } });
|
||||
if (usedPorts.length === 0) break;
|
||||
publicPort = await getPort();
|
||||
i++;
|
||||
} while (i < 10);
|
||||
if (i === 9) {
|
||||
throw {
|
||||
error: 'No free port found!? Is it possible?'
|
||||
};
|
||||
}
|
||||
return await prisma.database.create({
|
||||
data: {
|
||||
name,
|
||||
publicPort,
|
||||
defaultDatabase,
|
||||
dbUser,
|
||||
dbUserPassword,
|
||||
rootUser,
|
||||
rootUserPassword,
|
||||
teams: { connect: { id: teamId } },
|
||||
settings: { create: { isPublic: false } }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function getDatabase({ id, teamId }) {
|
||||
const body = await prisma.database.findFirst({
|
||||
where: { id, teams: { some: { id: teamId } } },
|
||||
include: { destinationDocker: true, settings: true }
|
||||
});
|
||||
|
||||
if (body.dbUserPassword) body.dbUserPassword = decrypt(body.dbUserPassword);
|
||||
if (body.rootUserPassword) body.rootUserPassword = decrypt(body.rootUserPassword);
|
||||
|
||||
return { ...body };
|
||||
}
|
||||
|
||||
export async function removeDatabase({ id }) {
|
||||
await prisma.databaseSettings.deleteMany({ where: { databaseId: id } });
|
||||
await prisma.database.delete({ where: { id } });
|
||||
return;
|
||||
}
|
||||
|
||||
export async function configureDatabaseType({ id, type }) {
|
||||
return await prisma.database.update({
|
||||
where: { id },
|
||||
data: { type }
|
||||
});
|
||||
}
|
||||
export async function setDatabase({
|
||||
id,
|
||||
version,
|
||||
isPublic,
|
||||
appendOnly
|
||||
}: {
|
||||
id: string;
|
||||
version?: string;
|
||||
isPublic?: boolean;
|
||||
appendOnly?: boolean;
|
||||
}) {
|
||||
return await prisma.database.update({
|
||||
where: { id },
|
||||
data: {
|
||||
version,
|
||||
settings: { upsert: { update: { isPublic, appendOnly }, create: { isPublic, appendOnly } } }
|
||||
}
|
||||
});
|
||||
}
|
||||
export async function updateDatabase({
|
||||
id,
|
||||
name,
|
||||
defaultDatabase,
|
||||
dbUser,
|
||||
dbUserPassword,
|
||||
rootUser,
|
||||
rootUserPassword,
|
||||
version
|
||||
}) {
|
||||
const encryptedDbUserPassword = dbUserPassword && encrypt(dbUserPassword);
|
||||
const encryptedRootUserPassword = rootUserPassword && encrypt(rootUserPassword);
|
||||
return await prisma.database.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name,
|
||||
defaultDatabase,
|
||||
dbUser,
|
||||
dbUserPassword: encryptedDbUserPassword,
|
||||
rootUser,
|
||||
rootUserPassword: encryptedRootUserPassword,
|
||||
version
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// export async function setDatabaseSettings({ id, isPublic }) {
|
||||
// try {
|
||||
// await prisma.databaseSettings.update({ where: { databaseId: id }, data: { isPublic } })
|
||||
// return { status: 201 }
|
||||
// } catch (e) {
|
||||
// throw PrismaErrorHandler(e)
|
||||
// }
|
||||
// }
|
||||
|
||||
export async function stopDatabase(database) {
|
||||
let everStarted = false;
|
||||
const {
|
||||
id,
|
||||
destinationDockerId,
|
||||
destinationDocker: { engine }
|
||||
} = database;
|
||||
if (destinationDockerId) {
|
||||
try {
|
||||
const host = getEngine(engine);
|
||||
const { stdout } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker inspect --format '{{json .State}}' ${id}`
|
||||
);
|
||||
if (stdout) {
|
||||
everStarted = true;
|
||||
await removeContainer(id, engine);
|
||||
}
|
||||
} catch (error) {
|
||||
//
|
||||
}
|
||||
}
|
||||
return everStarted;
|
||||
}
|
132
src/lib/database/destinations.ts
Normal file
132
src/lib/database/destinations.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import { asyncExecShell, getEngine } from '$lib/common';
|
||||
import { dockerInstance } from '$lib/docker';
|
||||
import { defaultProxyImageHttp, defaultProxyImageTcp, startCoolifyProxy } from '$lib/haproxy';
|
||||
import { getDatabaseImage } from '.';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
|
||||
export async function listDestinations(teamId) {
|
||||
return await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId } } } });
|
||||
}
|
||||
|
||||
export async function configureDestinationForService({ id, destinationId }) {
|
||||
return await prisma.service.update({
|
||||
where: { id },
|
||||
data: { destinationDocker: { connect: { id: destinationId } } }
|
||||
});
|
||||
}
|
||||
export async function configureDestinationForApplication({ id, destinationId }) {
|
||||
return await prisma.application.update({
|
||||
where: { id },
|
||||
data: { destinationDocker: { connect: { id: destinationId } } }
|
||||
});
|
||||
}
|
||||
export async function configureDestinationForDatabase({ id, destinationId }) {
|
||||
await prisma.database.update({
|
||||
where: { id },
|
||||
data: { destinationDocker: { connect: { id: destinationId } } }
|
||||
});
|
||||
|
||||
const {
|
||||
destinationDockerId,
|
||||
destinationDocker: { engine },
|
||||
version,
|
||||
type
|
||||
} = await prisma.database.findUnique({ where: { id }, include: { destinationDocker: true } });
|
||||
|
||||
if (destinationDockerId) {
|
||||
const host = getEngine(engine);
|
||||
if (type && version) {
|
||||
const baseImage = getDatabaseImage(type);
|
||||
asyncExecShell(`DOCKER_HOST=${host} docker pull ${baseImage}:${version}`);
|
||||
asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageTcp}`);
|
||||
asyncExecShell(`DOCKER_HOST=${host} docker pull coollabsio/${defaultProxyImageHttp}`);
|
||||
asyncExecShell(`DOCKER_HOST=${host} docker pull certbot/certbot:latest`);
|
||||
asyncExecShell(`DOCKER_HOST=${host} docker pull alpine:latest`);
|
||||
}
|
||||
}
|
||||
}
|
||||
export async function updateDestination({ id, name, engine, network }) {
|
||||
return await prisma.destinationDocker.update({ where: { id }, data: { name, engine, network } });
|
||||
}
|
||||
|
||||
export async function newDestination({ name, teamId, engine, network, isCoolifyProxyUsed }) {
|
||||
const host = getEngine(engine);
|
||||
const docker = dockerInstance({ destinationDocker: { engine, network } });
|
||||
const found = await docker.engine.listNetworks({ filters: { name: [`^${network}$`] } });
|
||||
if (found.length === 0) {
|
||||
await asyncExecShell(`DOCKER_HOST=${host} docker network create --attachable ${network}`);
|
||||
}
|
||||
await prisma.destinationDocker.create({
|
||||
data: { name, teams: { connect: { id: teamId } }, engine, network, isCoolifyProxyUsed }
|
||||
});
|
||||
const destinations = await prisma.destinationDocker.findMany({ where: { engine } });
|
||||
const destination = destinations.find((destination) => destination.network === network);
|
||||
|
||||
if (destinations.length > 0) {
|
||||
const proxyConfigured = destinations.find(
|
||||
(destination) => destination.network !== network && destination.isCoolifyProxyUsed === true
|
||||
);
|
||||
if (proxyConfigured) {
|
||||
if (proxyConfigured.isCoolifyProxyUsed) {
|
||||
isCoolifyProxyUsed = true;
|
||||
} else {
|
||||
isCoolifyProxyUsed = false;
|
||||
}
|
||||
}
|
||||
await prisma.destinationDocker.updateMany({ where: { engine }, data: { isCoolifyProxyUsed } });
|
||||
}
|
||||
if (isCoolifyProxyUsed) await startCoolifyProxy(engine);
|
||||
return destination.id;
|
||||
}
|
||||
export async function removeDestination({ id }) {
|
||||
const destination = await prisma.destinationDocker.delete({ where: { id } });
|
||||
if (destination.isCoolifyProxyUsed) {
|
||||
const host = getEngine(destination.engine);
|
||||
const { network } = destination;
|
||||
const { stdout: found } = await asyncExecShell(
|
||||
`DOCKER_HOST=${host} docker ps -a --filter network=${network} --filter name=coolify-haproxy --format '{{.}}'`
|
||||
);
|
||||
if (found) {
|
||||
await asyncExecShell(
|
||||
`DOCKER_HOST="${host}" docker network disconnect ${network} coolify-haproxy`
|
||||
);
|
||||
await asyncExecShell(`DOCKER_HOST="${host}" docker network rm ${network}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getDestination({ id, teamId }) {
|
||||
return await prisma.destinationDocker.findFirst({
|
||||
where: { id, teams: { some: { id: teamId } } }
|
||||
});
|
||||
}
|
||||
export async function getDestinationByApplicationId({ id, teamId }) {
|
||||
return await prisma.destinationDocker.findFirst({
|
||||
where: { application: { some: { id } }, teams: { some: { id: teamId } } }
|
||||
});
|
||||
}
|
||||
|
||||
export async function setDestinationSettings({ engine, isCoolifyProxyUsed }) {
|
||||
return await prisma.destinationDocker.updateMany({
|
||||
where: { engine },
|
||||
data: { isCoolifyProxyUsed }
|
||||
});
|
||||
|
||||
// if (isCoolifyProxyUsed) {
|
||||
// await installCoolifyProxy(engine)
|
||||
// await configureNetworkCoolifyProxy(engine)
|
||||
// } else {
|
||||
// // TODO: must check if other destination is using the proxy??? or not?
|
||||
// const domain = await prisma.setting.findUnique({ where: { name: 'domain' }, rejectOnNotFound: false })
|
||||
// if (!domain) {
|
||||
// await uninstallCoolifyProxy(engine)
|
||||
// } else {
|
||||
// return {
|
||||
// stastus: 500,
|
||||
// body: {
|
||||
// message: 'You can not disable the Coolify proxy while the domain is set for Coolify itself.'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
71
src/lib/database/gitSources.ts
Normal file
71
src/lib/database/gitSources.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
|
||||
export async function listSources(teamId) {
|
||||
return await prisma.gitSource.findMany({
|
||||
where: { teams: { some: { id: teamId } } },
|
||||
include: { githubApp: true, gitlabApp: true }
|
||||
});
|
||||
}
|
||||
|
||||
export async function newSource({ name, teamId, type, htmlUrl, apiUrl, organization }) {
|
||||
return await prisma.gitSource.create({
|
||||
data: {
|
||||
teams: { connect: { id: teamId } },
|
||||
name,
|
||||
type,
|
||||
htmlUrl,
|
||||
apiUrl,
|
||||
organization
|
||||
}
|
||||
});
|
||||
}
|
||||
export async function removeSource({ id }) {
|
||||
// TODO: Disconnect application with this sourceId! Maybe not needed?
|
||||
const source = await prisma.gitSource.delete({
|
||||
where: { id },
|
||||
include: { githubApp: true, gitlabApp: true }
|
||||
});
|
||||
if (source.githubAppId) await prisma.githubApp.delete({ where: { id: source.githubAppId } });
|
||||
if (source.gitlabAppId) await prisma.gitlabApp.delete({ where: { id: source.gitlabAppId } });
|
||||
}
|
||||
|
||||
export async function getSource({ id, teamId }) {
|
||||
let body = await prisma.gitSource.findFirst({
|
||||
where: { id, teams: { some: { id: teamId } } },
|
||||
include: { githubApp: true, gitlabApp: true }
|
||||
});
|
||||
if (body?.githubApp?.clientSecret)
|
||||
body.githubApp.clientSecret = decrypt(body.githubApp.clientSecret);
|
||||
if (body?.githubApp?.webhookSecret)
|
||||
body.githubApp.webhookSecret = decrypt(body.githubApp.webhookSecret);
|
||||
if (body?.githubApp?.privateKey) body.githubApp.privateKey = decrypt(body.githubApp.privateKey);
|
||||
if (body?.gitlabApp?.appSecret) body.gitlabApp.appSecret = decrypt(body.gitlabApp.appSecret);
|
||||
return body;
|
||||
}
|
||||
export async function addSource({ id, appId, teamId, oauthId, groupName, appSecret }) {
|
||||
const encrptedAppSecret = encrypt(appSecret);
|
||||
return await prisma.gitlabApp.create({
|
||||
data: {
|
||||
teams: { connect: { id: teamId } },
|
||||
appId,
|
||||
oauthId,
|
||||
groupName,
|
||||
appSecret: encrptedAppSecret,
|
||||
gitSource: { connect: { id } }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function configureGitsource({ id, gitSourceId }) {
|
||||
return await prisma.application.update({
|
||||
where: { id },
|
||||
data: { gitSource: { connect: { id: gitSourceId } } }
|
||||
});
|
||||
}
|
||||
export async function updateGitsource({ id, name }) {
|
||||
return await prisma.gitSource.update({
|
||||
where: { id },
|
||||
data: { name }
|
||||
});
|
||||
}
|
44
src/lib/database/github.ts
Normal file
44
src/lib/database/github.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
|
||||
export async function addInstallation({ gitSourceId, installation_id }) {
|
||||
const source = await prisma.gitSource.findUnique({
|
||||
where: { id: gitSourceId },
|
||||
include: { githubApp: true }
|
||||
});
|
||||
return await prisma.githubApp.update({
|
||||
where: { id: source.githubAppId },
|
||||
data: { installationId: Number(installation_id) }
|
||||
});
|
||||
}
|
||||
|
||||
export async function getUniqueGithubApp({ githubAppId }) {
|
||||
let body = await prisma.githubApp.findUnique({ where: { id: githubAppId } });
|
||||
if (body.privateKey) body.privateKey = decrypt(body.privateKey);
|
||||
return body;
|
||||
}
|
||||
|
||||
export async function createGithubApp({
|
||||
id,
|
||||
client_id,
|
||||
slug,
|
||||
client_secret,
|
||||
pem,
|
||||
webhook_secret,
|
||||
state
|
||||
}) {
|
||||
const encryptedClientSecret = encrypt(client_secret);
|
||||
const encryptedWebhookSecret = encrypt(webhook_secret);
|
||||
const encryptedPem = encrypt(pem);
|
||||
return await prisma.githubApp.create({
|
||||
data: {
|
||||
appId: id,
|
||||
name: slug,
|
||||
clientId: client_id,
|
||||
clientSecret: encryptedClientSecret,
|
||||
webhookSecret: encryptedWebhookSecret,
|
||||
privateKey: encryptedPem,
|
||||
gitSource: { connect: { id: state } }
|
||||
}
|
||||
});
|
||||
}
|
37
src/lib/database/gitlab.ts
Normal file
37
src/lib/database/gitlab.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { encrypt } from '$lib/crypto';
|
||||
import { generateSshKeyPair, prisma, PrismaErrorHandler } from './common';
|
||||
|
||||
export async function updateDeployKey({ id, deployKeyId }) {
|
||||
const application = await prisma.application.findUnique({
|
||||
where: { id },
|
||||
include: { gitSource: { include: { gitlabApp: true } } }
|
||||
});
|
||||
return await prisma.gitlabApp.update({
|
||||
where: { id: application.gitSource.gitlabApp.id },
|
||||
data: { deployKeyId }
|
||||
});
|
||||
}
|
||||
export async function getSshKey({ id }) {
|
||||
const application = await prisma.application.findUnique({
|
||||
where: { id },
|
||||
include: { gitSource: { include: { gitlabApp: true } } }
|
||||
});
|
||||
return { status: 200, body: { publicKey: application.gitSource.gitlabApp.publicSshKey } };
|
||||
}
|
||||
export async function generateSshKey({ id }) {
|
||||
const application = await prisma.application.findUnique({
|
||||
where: { id },
|
||||
include: { gitSource: { include: { gitlabApp: true } } }
|
||||
});
|
||||
if (!application.gitSource?.gitlabApp?.privateSshKey) {
|
||||
const keys = await generateSshKeyPair();
|
||||
const encryptedPrivateKey = encrypt(keys.privateKey);
|
||||
await prisma.gitlabApp.update({
|
||||
where: { id: application.gitSource.gitlabApp.id },
|
||||
data: { privateSshKey: encryptedPrivateKey, publicSshKey: keys.publicKey }
|
||||
});
|
||||
return { status: 201, body: { publicKey: keys.publicKey } };
|
||||
} else {
|
||||
return { status: 200 };
|
||||
}
|
||||
}
|
14
src/lib/database/index.ts
Normal file
14
src/lib/database/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export * from './applications';
|
||||
export * from './checks';
|
||||
export * from './common';
|
||||
export * from './destinations';
|
||||
export * from './github';
|
||||
export * from './gitlab';
|
||||
export * from './gitSources';
|
||||
export * from './logs';
|
||||
export * from './settings';
|
||||
export * from './users';
|
||||
export * from './teams';
|
||||
export * from './secrets';
|
||||
export * from './databases';
|
||||
export * from './services';
|
13
src/lib/database/logs.ts
Normal file
13
src/lib/database/logs.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
|
||||
export async function listLogs({ buildId, last = 0 }) {
|
||||
try {
|
||||
const body = await prisma.buildLog.findMany({
|
||||
where: { buildId, time: { gt: last } },
|
||||
orderBy: { time: 'asc' }
|
||||
});
|
||||
return [...body];
|
||||
} catch (e) {
|
||||
throw PrismaErrorHandler(e);
|
||||
}
|
||||
}
|
21
src/lib/database/secrets.ts
Normal file
21
src/lib/database/secrets.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { encrypt } from '$lib/crypto';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
|
||||
export async function listSecrets({ applicationId }) {
|
||||
return await prisma.secret.findMany({
|
||||
where: { applicationId },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
select: { id: true, createdAt: true, name: true, isBuildSecret: true }
|
||||
});
|
||||
}
|
||||
|
||||
export async function createSecret({ id, name, value, isBuildSecret }) {
|
||||
value = encrypt(value);
|
||||
return await prisma.secret.create({
|
||||
data: { name, value, isBuildSecret, application: { connect: { id } } }
|
||||
});
|
||||
}
|
||||
|
||||
export async function removeSecret({ id, name }) {
|
||||
return await prisma.secret.deleteMany({ where: { applicationId: id, name } });
|
||||
}
|
137
src/lib/database/services.ts
Normal file
137
src/lib/database/services.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { decrypt, encrypt } from '$lib/crypto';
|
||||
import { dockerInstance } from '$lib/docker';
|
||||
import cuid from 'cuid';
|
||||
import { generatePassword } from '.';
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
|
||||
export async function listServices(teamId) {
|
||||
return await prisma.service.findMany({ where: { teams: { some: { id: teamId } } } });
|
||||
}
|
||||
|
||||
export async function newService({ name, teamId }) {
|
||||
return await prisma.service.create({ data: { name, teams: { connect: { id: teamId } } } });
|
||||
}
|
||||
|
||||
export async function getService({ id, teamId }) {
|
||||
const body = await prisma.service.findFirst({
|
||||
where: { id, teams: { some: { id: teamId } } },
|
||||
include: {
|
||||
destinationDocker: true,
|
||||
plausibleAnalytics: true,
|
||||
minio: true,
|
||||
vscodeserver: true,
|
||||
wordpress: true
|
||||
}
|
||||
});
|
||||
|
||||
if (body.plausibleAnalytics?.postgresqlPassword)
|
||||
body.plausibleAnalytics.postgresqlPassword = decrypt(
|
||||
body.plausibleAnalytics.postgresqlPassword
|
||||
);
|
||||
if (body.plausibleAnalytics?.password)
|
||||
body.plausibleAnalytics.password = decrypt(body.plausibleAnalytics.password);
|
||||
if (body.plausibleAnalytics?.secretKeyBase)
|
||||
body.plausibleAnalytics.secretKeyBase = decrypt(body.plausibleAnalytics.secretKeyBase);
|
||||
|
||||
if (body.minio?.rootUserPassword)
|
||||
body.minio.rootUserPassword = decrypt(body.minio.rootUserPassword);
|
||||
|
||||
if (body.vscodeserver?.password) body.vscodeserver.password = decrypt(body.vscodeserver.password);
|
||||
|
||||
if (body.wordpress?.mysqlPassword)
|
||||
body.wordpress.mysqlPassword = decrypt(body.wordpress.mysqlPassword);
|
||||
if (body.wordpress?.mysqlRootUserPassword)
|
||||
body.wordpress.mysqlRootUserPassword = decrypt(body.wordpress.mysqlRootUserPassword);
|
||||
|
||||
return { ...body };
|
||||
}
|
||||
|
||||
export async function configureServiceType({ id, type }) {
|
||||
if (type === 'plausibleanalytics') {
|
||||
const password = encrypt(generatePassword());
|
||||
const postgresqlUser = cuid();
|
||||
const postgresqlPassword = encrypt(generatePassword());
|
||||
const postgresqlDatabase = 'plausibleanalytics';
|
||||
const secretKeyBase = encrypt(generatePassword(64));
|
||||
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
plausibleAnalytics: {
|
||||
create: {
|
||||
postgresqlDatabase,
|
||||
postgresqlUser,
|
||||
postgresqlPassword,
|
||||
password,
|
||||
secretKeyBase
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (type === 'nocodb') {
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: { type }
|
||||
});
|
||||
} else if (type === 'minio') {
|
||||
const rootUser = cuid();
|
||||
const rootUserPassword = encrypt(generatePassword());
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: { type, minio: { create: { rootUser, rootUserPassword } } }
|
||||
});
|
||||
} else if (type === 'vscodeserver') {
|
||||
const password = encrypt(generatePassword());
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: { type, vscodeserver: { create: { password } } }
|
||||
});
|
||||
} else if (type === 'wordpress') {
|
||||
const mysqlUser = cuid();
|
||||
const mysqlPassword = encrypt(generatePassword());
|
||||
const mysqlRootUser = cuid();
|
||||
const mysqlRootUserPassword = encrypt(generatePassword());
|
||||
await prisma.service.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type,
|
||||
wordpress: { create: { mysqlPassword, mysqlRootUserPassword, mysqlRootUser, mysqlUser } }
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
export async function setService({ id, version }) {
|
||||
return await prisma.service.update({
|
||||
where: { id },
|
||||
data: { version }
|
||||
});
|
||||
}
|
||||
|
||||
export async function updatePlausibleAnalyticsService({ id, fqdn, email, username, name }) {
|
||||
await prisma.plausibleAnalytics.update({ where: { serviceId: id }, data: { email, username } });
|
||||
await prisma.service.update({ where: { id }, data: { name, fqdn } });
|
||||
}
|
||||
export async function updateNocoDbOrMinioService({ id, fqdn, name }) {
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||
}
|
||||
export async function updateVsCodeServer({ id, fqdn, name }) {
|
||||
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
|
||||
}
|
||||
export async function updateWordpress({ id, fqdn, name, mysqlDatabase, extraConfig }) {
|
||||
return await prisma.service.update({
|
||||
where: { id },
|
||||
data: { fqdn, name, wordpress: { update: { mysqlDatabase, extraConfig } } }
|
||||
});
|
||||
}
|
||||
export async function updateMinioService({ id, publicPort }) {
|
||||
return await prisma.minio.update({ where: { serviceId: id }, data: { publicPort } });
|
||||
}
|
||||
|
||||
export async function removeService({ id }) {
|
||||
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.vscodeserver.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.wordpress.deleteMany({ where: { serviceId: id } });
|
||||
await prisma.service.delete({ where: { id } });
|
||||
}
|
8
src/lib/database/settings.ts
Normal file
8
src/lib/database/settings.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { decrypt } from '$lib/crypto';
|
||||
import { prisma } from './common';
|
||||
|
||||
export async function listSettings() {
|
||||
let settings = await prisma.setting.findFirst({});
|
||||
if (settings.proxyPassword) settings.proxyPassword = decrypt(settings.proxyPassword);
|
||||
return settings;
|
||||
}
|
20
src/lib/database/teams.ts
Normal file
20
src/lib/database/teams.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
|
||||
export async function listTeams() {
|
||||
return await prisma.team.findMany();
|
||||
}
|
||||
export async function newTeam({ name, userId }) {
|
||||
return await prisma.team.create({
|
||||
data: {
|
||||
name,
|
||||
permissions: { create: { user: { connect: { id: userId } }, permission: 'owner' } },
|
||||
users: { connect: { id: userId } }
|
||||
}
|
||||
});
|
||||
}
|
||||
export async function getMyTeams({ userId }) {
|
||||
return await prisma.permission.findMany({
|
||||
where: { userId },
|
||||
include: { team: { include: { _count: { select: { users: true } } } } }
|
||||
});
|
||||
}
|
125
src/lib/database/users.ts
Normal file
125
src/lib/database/users.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import cuid from 'cuid';
|
||||
import bcrypt from 'bcrypt';
|
||||
|
||||
import { prisma, PrismaErrorHandler } from './common';
|
||||
import { asyncExecShell, removeContainer, uniqueName } from '$lib/common';
|
||||
|
||||
import * as db from '$lib/database';
|
||||
import { startCoolifyProxy } from '$lib/haproxy';
|
||||
|
||||
export async function login({ email, password }) {
|
||||
const saltRounds = 15;
|
||||
const users = await prisma.user.count();
|
||||
const userFound = await prisma.user.findUnique({
|
||||
where: { email },
|
||||
include: { teams: true },
|
||||
rejectOnNotFound: false
|
||||
});
|
||||
// Registration disabled if database is not seeded properly
|
||||
const { isRegistrationEnabled, id } = await db.listSettings();
|
||||
|
||||
let uid = cuid();
|
||||
// Disable registration if we are registering the first user.
|
||||
if (users === 0) {
|
||||
await prisma.setting.update({ where: { id }, data: { isRegistrationEnabled: false } });
|
||||
// Create default network & start Coolify Proxy
|
||||
asyncExecShell(`docker network create --attachable coolify`)
|
||||
.then(() => {
|
||||
console.log('Network created');
|
||||
})
|
||||
.catch(() => {
|
||||
console.log('Network already exists');
|
||||
});
|
||||
|
||||
startCoolifyProxy('/var/run/docker.sock')
|
||||
.then(() => {
|
||||
console.log('Coolify Proxy Started');
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
uid = '0';
|
||||
}
|
||||
|
||||
if (userFound) {
|
||||
if (userFound.type === 'email') {
|
||||
const passwordMatch = await bcrypt.compare(password, userFound.password);
|
||||
if (!passwordMatch) {
|
||||
throw {
|
||||
error: 'Wrong password or email address.'
|
||||
};
|
||||
}
|
||||
uid = userFound.id;
|
||||
}
|
||||
} else {
|
||||
// If registration disabled, return 403
|
||||
if (!isRegistrationEnabled) {
|
||||
throw {
|
||||
error: 'Registration disabled by administrator.'
|
||||
};
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, saltRounds);
|
||||
if (users === 0) {
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
id: uid,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
type: 'email',
|
||||
teams: {
|
||||
create: {
|
||||
id: uid,
|
||||
name: uniqueName(),
|
||||
destinationDocker: { connect: { network: 'coolify' } }
|
||||
}
|
||||
},
|
||||
permission: { create: { teamId: uid, permission: 'owner' } }
|
||||
},
|
||||
include: { teams: true }
|
||||
});
|
||||
} else {
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
id: uid,
|
||||
email,
|
||||
password: hashedPassword,
|
||||
type: 'email',
|
||||
teams: {
|
||||
create: {
|
||||
id: uid,
|
||||
name: uniqueName()
|
||||
}
|
||||
},
|
||||
permission: { create: { teamId: uid, permission: 'owner' } }
|
||||
},
|
||||
include: { teams: true }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// const token = jsonwebtoken.sign({}, secretKey, {
|
||||
// expiresIn: 15778800,
|
||||
// algorithm: 'HS256',
|
||||
// audience: 'coolify',
|
||||
// issuer: 'coolify',
|
||||
// jwtid: uid,
|
||||
// subject: `User:${uid}`,
|
||||
// notBefore: -1000
|
||||
// });
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Set-Cookie': `teamId=${uid}; HttpOnly; Path=/; Max-Age=15778800;`
|
||||
},
|
||||
body: {
|
||||
uid,
|
||||
teamId: uid
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export async function getUser({ userId }) {
|
||||
return await prisma.user.findUnique({ where: { id: userId } });
|
||||
}
|
Reference in New Issue
Block a user