Skip to content

Update database service to use upsert method #688

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions server/src/config/config.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,13 +299,8 @@ describe('ConfigService', () => {
*/

it('should get cluster issuer', async () => {
kubectl.getKuberoConfig.mockResolvedValueOnce({
spec: {
kubero: { config: { clusterissuer: 'issuer' } },
},
});
const result = await service.getClusterIssuer();
expect(result.clusterissuer).toBe('issuer');
expect(result.clusterissuer).toBe('letsencrypt-prod');
});

/*
Expand Down
160 changes: 75 additions & 85 deletions server/src/database/database.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export class DatabaseService {
private readonly prisma = new PrismaClient();

constructor() {

// Initialize the Prisma client
this.prisma
.$connect()
Expand All @@ -21,25 +22,20 @@ export class DatabaseService {
.catch((error) => {
this.logger.error('Failed to connect to the database.', error);
});
this.runMigrations()
.then(() => {
// create user after migrations
this.seedDefaultData().then(() => {
this.createSystemUser();
this.createAdminUser();
this.migrateLegeacyUsers();

});
})
.catch((error) => {
this.logger.error('Error during database migrations.', error);
});
}

this.seedRunpacks();
this.seedPodSizes();
public static async DBinit() {
await this.Init();
await this.RunMigrations();
await this.SeedDefaultData();
await this.CreateSystemUser();
await this.CreateAdminUser();
await this.MigrateLegacyUsers();
await this.SeedRunpacks();
await this.SeedPodSizes();
}

private async init() {
private static async Init() {
if (
process.env.DATABASE_URL === '' ||
process.env.DATABASE_URL === undefined
Expand All @@ -54,37 +50,36 @@ export class DatabaseService {
}
}

private async runMigrations() {
private static async RunMigrations() {
const { execSync } = await import('child_process');

await this.init();

const prisma = new PrismaClient();

try {
this.logger.log('Running Prisma migrations...');
Logger.debug('Running Prisma migrations...', 'DatabaseService');
// @ts-ignore
await prisma.$executeRawUnsafe?.('PRAGMA foreign_keys=OFF;'); // For SQLite, optional
// Use CLI for migrations
execSync('npx prisma migrate deploy', { stdio: 'inherit' });
await execSync('npx prisma migrate deploy', { stdio: 'inherit' });
//execSync('npx prisma migrate deploy', {});
this.logger.log('Prisma migrations completed.');
Logger.log('Prisma migrations completed.', 'DatabaseService');
//await prisma.$disconnect();
} catch (err) {
this.logger.error('Prisma migration failed', err);
Logger.error('Prisma migration failed', err, 'DatabaseService');
process.exit(1);
}
}

private async createSystemUser() {

private static async CreateSystemUser() {
const prisma = new PrismaClient();

// Check if the system user already exists
const existingUser = await prisma.user.findUnique({
where: { id: '1' },
});
if (existingUser) {
this.logger.log('System user already exists. Skipping creation.');
Logger.log('System user already exists. Skipping creation.', 'DatabaseService');
return;
}

Expand All @@ -107,21 +102,21 @@ export class DatabaseService {
: undefined,
},
});
this.logger.log('System user created successfully.');
Logger.log('System user created successfully.', 'DatabaseService');
} catch (error) {
this.logger.error('Failed to create system user.', error);
Logger.error('Failed to create system user.', error, 'DatabaseService');
}
}

private async createAdminUser() {
private static async CreateAdminUser() {
const prisma = new PrismaClient();

// Check if the admin user already exists
const existingUser = await prisma.user.findUnique({
where: { id: '2' },
});
if (existingUser) {
this.logger.log('Admin user already exists. Skipping creation.');
Logger.log('Admin user already exists. Skipping creation.', 'DatabaseService');
return;
}

Expand Down Expand Up @@ -161,9 +156,9 @@ export class DatabaseService {
updatedAt: new Date(),
},
});
this.logger.log('Admin user created successfully.');
Logger.log('Admin user created successfully.', 'DatabaseService');
} catch (error) {
Logger.error('Failed to create admin user.', error);
Logger.error('Failed to create admin user.', error, 'DatabaseService');
}
}

Expand Down Expand Up @@ -239,17 +234,17 @@ export class DatabaseService {
}
}

private async migrateLegeacyUsers() {
private static async MigrateLegacyUsers() {
const prisma = new PrismaClient();

const existingUsers = await prisma.user.count();
if (existingUsers > 2) {
this.logger.log('Legacy users already migrated. Skipping migration.');
Logger.log('Legacy users already migrated. Skipping migration.', 'DatabaseService');
return;
}

if (!process.env.KUBERO_USERS || process.env.KUBERO_USERS === '') {
this.logger.log('No legacy users to migrate. KUBERO_USERS is not set.');
Logger.log('No legacy users to migrate. KUBERO_USERS is not set.', 'DatabaseService');
return;
}

Expand All @@ -263,10 +258,11 @@ export class DatabaseService {
user.insecure === true &&
process.env.KUBERO_SESSION_KEY
) {
this.logger.warn(
Logger.warn(
'User with unencrypted Password detected: "' +
user.username +
'" - This feature is deprecated and will be removed in the future',
'DatabaseService',
);
password = crypto
.createHmac('sha256', process.env.KUBERO_SESSION_KEY)
Expand Down Expand Up @@ -294,18 +290,19 @@ export class DatabaseService {
: undefined,
},
});
this.logger.log(`Migrated user ${user.username} successfully.`);
Logger.log(`Migrated user ${user.username} successfully.`, 'DatabaseService');
} catch (error) {
this.logger.error(`Failed to migrate user ${user.username}.`, error);
Logger.error(`Failed to migrate user ${user.username}.`, error, 'DatabaseService');
}
}

this.logger.log('Legacy users migrated successfully.');
Logger.log('Legacy users migrated successfully.', 'DatabaseService');
}

private async seedDefaultData() {
private static async SeedDefaultData() {
const prisma = new PrismaClient();
// Ensure the 'admin' role exists with permissions
this.prisma.role
await prisma.role
.upsert({
where: { name: 'admin' },
update: {},
Expand All @@ -329,11 +326,11 @@ export class DatabaseService {
},
})
.then(() => {
this.logger.log('Role "admin" seeded successfully.');
Logger.log('Role "admin" seeded successfully.', 'DatabaseService');
});

// Ensure the 'member' role exists with limited permissions
this.prisma.role
await prisma.role
.upsert({
where: { name: 'member' },
update: {},
Expand All @@ -357,11 +354,11 @@ export class DatabaseService {
},
})
.then(() => {
this.logger.log('Role "member" seeded successfully.');
Logger.log('Role "member" seeded successfully.', 'DatabaseService');
});

// Ensure the 'guest' role exists with minimal permissions
this.prisma.role
await prisma.role
.upsert({
where: { name: 'guest' },
update: {},
Expand All @@ -385,56 +382,48 @@ export class DatabaseService {
},
})
.then(() => {
this.logger.log('Role "guest" seeded successfully.');
Logger.log('Role "guest" seeded successfully.', 'DatabaseService');
});

// Ensure the 'everyone' user group exists
const existingGroup = await this.prisma.userGroup.findUnique({
prisma.userGroup
.upsert({
where: { name: 'everyone' },
update: {},
create: {
name: 'everyone',
description: 'Standard group for all users',
},
})
.then(() => {
Logger.log('UserGroup "everyone" seeded successfully.', 'DatabaseService');
});

if (!existingGroup) {
await this.prisma.userGroup.create({
data: {
name: 'everyone',
description: 'Standard group for all users',
},
});
this.logger.log('UserGroup "everyone" created successfully.');
} else {
this.logger.log(
'UserGroup "everyone" already exists. Skipping creation.',
);
}

// Ensure the 'admin' user group exists
const adminGroup = await this.prisma.userGroup.findUnique({
where: { name: 'admin' },
});

if (!adminGroup) {
await this.prisma.userGroup.create({
data: {
await prisma.userGroup
.upsert({
where: { name: 'admin' },
update: {},
create: {
name: 'admin',
description: 'Group for admin users',
},
})
.then(() => {
Logger.log('UserGroup "admin" seeded successfully.', 'DatabaseService');
});
this.logger.log('UserGroup "admin" created successfully.');
} else {
this.logger.log('UserGroup "admin" already exists. Skipping creation.');
}

this.logger.log('Default data seeded successfully.');
Logger.log('Default data seeded successfully.', 'DatabaseService');
}

private async seedRunpacks() {
private static async SeedRunpacks() {
const prisma = new PrismaClient();
const config = yaml.parse(runpacks);

const buildpacks = config || [];
for (const bp of buildpacks) {
// Find existing by name
const existing = await this.prisma.runpack.findFirst({ where: { name: bp.name } });
const prisma = this.prisma;
const existing = await prisma.runpack.findFirst({ where: { name: bp.name } });
const createPhase = async (phase: any) => {
// Create SecurityContext
const sec = await prisma.securityContext.create({
Expand Down Expand Up @@ -468,10 +457,10 @@ export class DatabaseService {
const runPhase = await createPhase(bp.run);
if (existing) {
// Optionally update here
this.logger.log(`Runpack/Buildpack '${bp.name}' already exists. Skipping.`);
Logger.log(`Runpack/Buildpack '${bp.name}' already exists. Skipping.`, 'DatabaseService');
continue;
}
await this.prisma.runpack.create({
await prisma.runpack.create({
data: {
name: bp.name,
language: bp.language,
Expand All @@ -480,21 +469,22 @@ export class DatabaseService {
runId: runPhase.id,
},
});
this.logger.log(`Runpack/Buildpack '${bp.name}' seeded.`);
Logger.log(`Runpack/Buildpack '${bp.name}' seeded.`, 'DatabaseService');
}
this.logger.log('Buildpacks/Runpacks seeded successfully.');
Logger.log('Buildpacks/Runpacks seeded successfully.', 'DatabaseService');
}

private async seedPodSizes() {
private static async SeedPodSizes() {
const prisma = new PrismaClient();
const config = yaml.parse(podsizes);
// seed pod sizes if the table is empty
const existingSizes = await this.prisma.podSize.count();
const existingSizes = await prisma.podSize.count();
if (existingSizes > 0) {
this.logger.log('Pod sizes already exist. Skipping seeding.');
Logger.log('Pod sizes already exist. Skipping seeding.', 'DatabaseService');
return;
}
for (const size of config) {
await this.prisma.podSize.create({
await prisma.podSize.create({
data: {
name: size.name,
description: size.description,
Expand All @@ -504,8 +494,8 @@ export class DatabaseService {
memoryRequest: size.resources.requests.memory,
},
});
this.logger.log(`Pod size '${size.name}' seeded successfully.`);
Logger.log(`Pod size '${size.name}' seeded successfully.`, 'DatabaseService');
}
this.logger.log('Pod sizes seeded successfully.');
Logger.log('Pod sizes seeded successfully.', 'DatabaseService');
}
}
1 change: 1 addition & 0 deletions server/src/database/runpacks.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,4 +299,5 @@ export const runpacks = `
allowPrivilegeEscalation: false
capabilities:
add: []
drop: []
`
3 changes: 1 addition & 2 deletions server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,7 @@ async function bootstrap() {
cors: true,
});

//DatabaseService.RunMigrations();
//DatabaseService.CreateAdminUser();
await DatabaseService.DBinit();

app.use(
helmet({
Expand Down
Loading