WIP - additional fixes for multitenant
This commit is contained in:
@@ -5,28 +5,28 @@ exports.up = function (knex) {
|
|||||||
table.string('slug', 255).notNullable().unique();
|
table.string('slug', 255).notNullable().unique();
|
||||||
table.string('label', 255).notNullable();
|
table.string('label', 255).notNullable();
|
||||||
table.text('description');
|
table.text('description');
|
||||||
table.integer('displayOrder').defaultTo(0);
|
table.integer('display_order').defaultTo(0);
|
||||||
table.timestamps(true, true);
|
table.timestamps(true, true);
|
||||||
|
|
||||||
table.index(['slug']);
|
table.index(['slug']);
|
||||||
})
|
})
|
||||||
.createTable('app_pages', (table) => {
|
.createTable('app_pages', (table) => {
|
||||||
table.uuid('id').primary().defaultTo(knex.raw('(UUID())'));
|
table.uuid('id').primary().defaultTo(knex.raw('(UUID())'));
|
||||||
table.uuid('appId').notNullable();
|
table.uuid('app_id').notNullable();
|
||||||
table.string('slug', 255).notNullable();
|
table.string('slug', 255).notNullable();
|
||||||
table.string('label', 255).notNullable();
|
table.string('label', 255).notNullable();
|
||||||
table.string('type', 50).notNullable(); // List, Detail, Custom
|
table.string('type', 50).notNullable(); // List, Detail, Custom
|
||||||
table.string('objectApiName', 255);
|
table.string('object_api_name', 255);
|
||||||
table.integer('displayOrder').defaultTo(0);
|
table.integer('display_order').defaultTo(0);
|
||||||
table.timestamps(true, true);
|
table.timestamps(true, true);
|
||||||
|
|
||||||
table
|
table
|
||||||
.foreign('appId')
|
.foreign('app_id')
|
||||||
.references('id')
|
.references('id')
|
||||||
.inTable('apps')
|
.inTable('apps')
|
||||||
.onDelete('CASCADE');
|
.onDelete('CASCADE');
|
||||||
table.unique(['appId', 'slug']);
|
table.unique(['app_id', 'slug']);
|
||||||
table.index(['appId']);
|
table.index(['app_id']);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `isActive` on the `tenants` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the `accounts` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `app_pages` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `apps` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `field_definitions` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `object_definitions` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `permissions` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `role_permissions` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `roles` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `user_roles` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `users` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- Added the required column `dbHost` to the `tenants` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `dbName` to the `tenants` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `dbPassword` to the `tenants` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `dbUsername` to the `tenants` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `accounts` DROP FOREIGN KEY `accounts_ownerId_fkey`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `accounts` DROP FOREIGN KEY `accounts_tenantId_fkey`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `app_pages` DROP FOREIGN KEY `app_pages_appId_fkey`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `app_pages` DROP FOREIGN KEY `app_pages_objectId_fkey`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `apps` DROP FOREIGN KEY `apps_tenantId_fkey`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `field_definitions` DROP FOREIGN KEY `field_definitions_objectId_fkey`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `object_definitions` DROP FOREIGN KEY `object_definitions_tenantId_fkey`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `permissions` DROP FOREIGN KEY `permissions_tenantId_fkey`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `role_permissions` DROP FOREIGN KEY `role_permissions_permissionId_fkey`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `role_permissions` DROP FOREIGN KEY `role_permissions_roleId_fkey`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `roles` DROP FOREIGN KEY `roles_tenantId_fkey`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `user_roles` DROP FOREIGN KEY `user_roles_roleId_fkey`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `user_roles` DROP FOREIGN KEY `user_roles_userId_fkey`;
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE `users` DROP FOREIGN KEY `users_tenantId_fkey`;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE `tenants` DROP COLUMN `isActive`,
|
||||||
|
ADD COLUMN `dbHost` VARCHAR(191) NOT NULL,
|
||||||
|
ADD COLUMN `dbName` VARCHAR(191) NOT NULL,
|
||||||
|
ADD COLUMN `dbPassword` VARCHAR(191) NOT NULL,
|
||||||
|
ADD COLUMN `dbPort` INTEGER NOT NULL DEFAULT 3306,
|
||||||
|
ADD COLUMN `dbUsername` VARCHAR(191) NOT NULL,
|
||||||
|
ADD COLUMN `status` VARCHAR(191) NOT NULL DEFAULT 'active';
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE `accounts`;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE `app_pages`;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE `apps`;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE `field_definitions`;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE `object_definitions`;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE `permissions`;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE `role_permissions`;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE `roles`;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE `user_roles`;
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE `users`;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE `domains` (
|
||||||
|
`id` VARCHAR(191) NOT NULL,
|
||||||
|
`domain` VARCHAR(191) NOT NULL,
|
||||||
|
`tenantId` VARCHAR(191) NOT NULL,
|
||||||
|
`isPrimary` BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||||
|
`updatedAt` DATETIME(3) NOT NULL,
|
||||||
|
|
||||||
|
UNIQUE INDEX `domains_domain_key`(`domain`),
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE `domains` ADD CONSTRAINT `domains_tenantId_fkey` FOREIGN KEY (`tenantId`) REFERENCES `tenants`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -2,8 +2,10 @@ import { Module } from '@nestjs/common';
|
|||||||
import { AppBuilderService } from './app-builder.service';
|
import { AppBuilderService } from './app-builder.service';
|
||||||
import { RuntimeAppController } from './runtime-app.controller';
|
import { RuntimeAppController } from './runtime-app.controller';
|
||||||
import { SetupAppController } from './setup-app.controller';
|
import { SetupAppController } from './setup-app.controller';
|
||||||
|
import { TenantModule } from '../tenant/tenant.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
imports: [TenantModule],
|
||||||
providers: [AppBuilderService],
|
providers: [AppBuilderService],
|
||||||
controllers: [RuntimeAppController, SetupAppController],
|
controllers: [RuntimeAppController, SetupAppController],
|
||||||
exports: [AppBuilderService],
|
exports: [AppBuilderService],
|
||||||
|
|||||||
@@ -1,44 +1,26 @@
|
|||||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { TenantDatabaseService } from '../tenant/tenant-database.service';
|
||||||
|
import { App } from '../models/app.model';
|
||||||
|
import { AppPage } from '../models/app-page.model';
|
||||||
|
import { ObjectDefinition } from '../models/object-definition.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AppBuilderService {
|
export class AppBuilderService {
|
||||||
constructor(private prisma: PrismaService) {}
|
constructor(private tenantDbService: TenantDatabaseService) {}
|
||||||
|
|
||||||
// Runtime endpoints
|
// Runtime endpoints
|
||||||
async getApps(tenantId: string, userId: string) {
|
async getApps(tenantId: string, userId: string) {
|
||||||
// For now, return all active apps for the tenant
|
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
||||||
|
// For now, return all apps
|
||||||
// In production, you'd filter by user permissions
|
// In production, you'd filter by user permissions
|
||||||
return this.prisma.app.findMany({
|
return App.query(knex).withGraphFetched('pages').orderBy('label', 'asc');
|
||||||
where: {
|
|
||||||
tenantId,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
pages: {
|
|
||||||
where: { isActive: true },
|
|
||||||
orderBy: { sortOrder: 'asc' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: { label: 'asc' },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getApp(tenantId: string, slug: string, userId: string) {
|
async getApp(tenantId: string, slug: string, userId: string) {
|
||||||
const app = await this.prisma.app.findUnique({
|
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
||||||
where: {
|
const app = await App.query(knex)
|
||||||
tenantId_slug: {
|
.findOne({ slug })
|
||||||
tenantId,
|
.withGraphFetched('pages');
|
||||||
slug,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
pages: {
|
|
||||||
where: { isActive: true },
|
|
||||||
orderBy: { sortOrder: 'asc' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!app) {
|
if (!app) {
|
||||||
throw new NotFoundException(`App ${slug} not found`);
|
throw new NotFoundException(`App ${slug} not found`);
|
||||||
@@ -53,23 +35,12 @@ export class AppBuilderService {
|
|||||||
pageSlug: string,
|
pageSlug: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
) {
|
) {
|
||||||
|
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
||||||
const app = await this.getApp(tenantId, appSlug, userId);
|
const app = await this.getApp(tenantId, appSlug, userId);
|
||||||
|
|
||||||
const page = await this.prisma.appPage.findFirst({
|
const page = await AppPage.query(knex).findOne({
|
||||||
where: {
|
|
||||||
appId: app.id,
|
appId: app.id,
|
||||||
slug: pageSlug,
|
slug: pageSlug,
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
object: {
|
|
||||||
include: {
|
|
||||||
fields: {
|
|
||||||
where: { isActive: true },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
@@ -81,31 +52,15 @@ export class AppBuilderService {
|
|||||||
|
|
||||||
// Setup endpoints
|
// Setup endpoints
|
||||||
async getAllApps(tenantId: string) {
|
async getAllApps(tenantId: string) {
|
||||||
return this.prisma.app.findMany({
|
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
||||||
where: { tenantId },
|
return App.query(knex).withGraphFetched('pages').orderBy('label', 'asc');
|
||||||
include: {
|
|
||||||
pages: {
|
|
||||||
orderBy: { sortOrder: 'asc' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: { label: 'asc' },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAppForSetup(tenantId: string, slug: string) {
|
async getAppForSetup(tenantId: string, slug: string) {
|
||||||
const app = await this.prisma.app.findUnique({
|
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
||||||
where: {
|
const app = await App.query(knex)
|
||||||
tenantId_slug: {
|
.findOne({ slug })
|
||||||
tenantId,
|
.withGraphFetched('pages');
|
||||||
slug,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
pages: {
|
|
||||||
orderBy: { sortOrder: 'asc' },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!app) {
|
if (!app) {
|
||||||
throw new NotFoundException(`App ${slug} not found`);
|
throw new NotFoundException(`App ${slug} not found`);
|
||||||
@@ -120,14 +75,12 @@ export class AppBuilderService {
|
|||||||
slug: string;
|
slug: string;
|
||||||
label: string;
|
label: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
icon?: string;
|
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
return this.prisma.app.create({
|
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
||||||
data: {
|
return App.query(knex).insert({
|
||||||
tenantId,
|
|
||||||
...data,
|
...data,
|
||||||
},
|
displayOrder: 0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,16 +90,12 @@ export class AppBuilderService {
|
|||||||
data: {
|
data: {
|
||||||
label?: string;
|
label?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
icon?: string;
|
|
||||||
isActive?: boolean;
|
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
||||||
const app = await this.getAppForSetup(tenantId, slug);
|
const app = await this.getAppForSetup(tenantId, slug);
|
||||||
|
|
||||||
return this.prisma.app.update({
|
return App.query(knex).patchAndFetchById(app.id, data);
|
||||||
where: { id: app.id },
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async createPage(
|
async createPage(
|
||||||
@@ -157,37 +106,19 @@ export class AppBuilderService {
|
|||||||
label: string;
|
label: string;
|
||||||
type: string;
|
type: string;
|
||||||
objectApiName?: string;
|
objectApiName?: string;
|
||||||
config?: any;
|
|
||||||
sortOrder?: number;
|
sortOrder?: number;
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
||||||
const app = await this.getAppForSetup(tenantId, appSlug);
|
const app = await this.getAppForSetup(tenantId, appSlug);
|
||||||
|
|
||||||
// If objectApiName is provided, find the object
|
return AppPage.query(knex).insert({
|
||||||
let objectId: string | undefined;
|
|
||||||
if (data.objectApiName) {
|
|
||||||
const obj = await this.prisma.objectDefinition.findUnique({
|
|
||||||
where: {
|
|
||||||
tenantId_apiName: {
|
|
||||||
tenantId,
|
|
||||||
apiName: data.objectApiName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
objectId = obj?.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.prisma.appPage.create({
|
|
||||||
data: {
|
|
||||||
appId: app.id,
|
appId: app.id,
|
||||||
slug: data.slug,
|
slug: data.slug,
|
||||||
label: data.label,
|
label: data.label,
|
||||||
type: data.type,
|
type: data.type,
|
||||||
objectApiName: data.objectApiName,
|
objectApiName: data.objectApiName,
|
||||||
objectId,
|
displayOrder: data.sortOrder || 0,
|
||||||
config: data.config,
|
|
||||||
sortOrder: data.sortOrder || 0,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,44 +130,24 @@ export class AppBuilderService {
|
|||||||
label?: string;
|
label?: string;
|
||||||
type?: string;
|
type?: string;
|
||||||
objectApiName?: string;
|
objectApiName?: string;
|
||||||
config?: any;
|
|
||||||
sortOrder?: number;
|
sortOrder?: number;
|
||||||
isActive?: boolean;
|
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
const knex = await this.tenantDbService.getTenantKnex(tenantId);
|
||||||
const app = await this.getAppForSetup(tenantId, appSlug);
|
const app = await this.getAppForSetup(tenantId, appSlug);
|
||||||
|
|
||||||
const page = await this.prisma.appPage.findFirst({
|
const page = await AppPage.query(knex).findOne({
|
||||||
where: {
|
|
||||||
appId: app.id,
|
appId: app.id,
|
||||||
slug: pageSlug,
|
slug: pageSlug,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
throw new NotFoundException(`Page ${pageSlug} not found`);
|
throw new NotFoundException(`Page ${pageSlug} not found`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If objectApiName is provided, find the object
|
return AppPage.query(knex).patchAndFetchById(page.id, {
|
||||||
let objectId: string | undefined;
|
|
||||||
if (data.objectApiName) {
|
|
||||||
const obj = await this.prisma.objectDefinition.findUnique({
|
|
||||||
where: {
|
|
||||||
tenantId_apiName: {
|
|
||||||
tenantId,
|
|
||||||
apiName: data.objectApiName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
objectId = obj?.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.prisma.appPage.update({
|
|
||||||
where: { id: page.id },
|
|
||||||
data: {
|
|
||||||
...data,
|
...data,
|
||||||
objectId,
|
displayOrder: data.sortOrder,
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
|||||||
import { TenantId } from '../tenant/tenant.decorator';
|
import { TenantId } from '../tenant/tenant.decorator';
|
||||||
|
|
||||||
@Controller('setup/apps')
|
@Controller('setup/apps')
|
||||||
@UseGuards(JwtAuthGuard)
|
//@UseGuards(JwtAuthGuard)
|
||||||
export class SetupAppController {
|
export class SetupAppController {
|
||||||
constructor(private appBuilderService: AppBuilderService) {}
|
constructor(private appBuilderService: AppBuilderService) {}
|
||||||
|
|
||||||
@@ -59,11 +59,6 @@ export class SetupAppController {
|
|||||||
@Param('pageSlug') pageSlug: string,
|
@Param('pageSlug') pageSlug: string,
|
||||||
@Body() data: any,
|
@Body() data: any,
|
||||||
) {
|
) {
|
||||||
return this.appBuilderService.updatePage(
|
return this.appBuilderService.updatePage(tenantId, appSlug, pageSlug, data);
|
||||||
tenantId,
|
|
||||||
appSlug,
|
|
||||||
pageSlug,
|
|
||||||
data,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { BaseModel } from './base.model';
|
import { BaseModel } from './base.model';
|
||||||
|
import { App } from './app.model';
|
||||||
|
|
||||||
export class AppPage extends BaseModel {
|
export class AppPage extends BaseModel {
|
||||||
static tableName = 'app_pages';
|
static tableName = 'app_pages';
|
||||||
@@ -14,7 +15,7 @@ export class AppPage extends BaseModel {
|
|||||||
static relationMappings = {
|
static relationMappings = {
|
||||||
app: {
|
app: {
|
||||||
relation: BaseModel.BelongsToOneRelation,
|
relation: BaseModel.BelongsToOneRelation,
|
||||||
modelClass: 'app.model',
|
modelClass: App,
|
||||||
join: {
|
join: {
|
||||||
from: 'app_pages.appId',
|
from: 'app_pages.appId',
|
||||||
to: 'apps.id',
|
to: 'apps.id',
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { BaseModel } from './base.model';
|
import { BaseModel } from './base.model';
|
||||||
|
import { AppPage } from './app-page.model';
|
||||||
|
|
||||||
export class App extends BaseModel {
|
export class App extends BaseModel {
|
||||||
static tableName = 'apps';
|
static tableName = 'apps';
|
||||||
@@ -12,7 +13,7 @@ export class App extends BaseModel {
|
|||||||
static relationMappings = {
|
static relationMappings = {
|
||||||
pages: {
|
pages: {
|
||||||
relation: BaseModel.HasManyRelation,
|
relation: BaseModel.HasManyRelation,
|
||||||
modelClass: 'app-page.model',
|
modelClass: AppPage,
|
||||||
join: {
|
join: {
|
||||||
from: 'apps.id',
|
from: 'apps.id',
|
||||||
to: 'app_pages.appId',
|
to: 'app_pages.appId',
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Model, ModelOptions, QueryContext } from 'objection';
|
import { Model, ModelOptions, QueryContext, snakeCaseMappers } from 'objection';
|
||||||
|
|
||||||
export class BaseModel extends Model {
|
export class BaseModel extends Model {
|
||||||
|
static columnNameMappers = snakeCaseMappers();
|
||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class SchemaManagementService {
|
|||||||
|
|
||||||
// Custom fields from field definitions
|
// Custom fields from field definitions
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
this.addFieldToTable(table, field);
|
this.addFieldColumn(table, field);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ export class SchemaManagementService {
|
|||||||
const tableName = this.getTableName(objectApiName);
|
const tableName = this.getTableName(objectApiName);
|
||||||
|
|
||||||
await knex.schema.alterTable(tableName, (table) => {
|
await knex.schema.alterTable(tableName, (table) => {
|
||||||
this.addFieldToTable(table, field);
|
this.addFieldColumn(table, field);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.log(`Added field ${field.apiName} to table ${tableName}`);
|
this.logger.log(`Added field ${field.apiName} to table ${tableName}`);
|
||||||
@@ -85,7 +85,7 @@ export class SchemaManagementService {
|
|||||||
/**
|
/**
|
||||||
* Add a field column to a table builder
|
* Add a field column to a table builder
|
||||||
*/
|
*/
|
||||||
private addFieldToTable(
|
private addFieldColumn(
|
||||||
table: Knex.CreateTableBuilder | Knex.AlterTableBuilder,
|
table: Knex.CreateTableBuilder | Knex.AlterTableBuilder,
|
||||||
field: FieldDefinition,
|
field: FieldDefinition,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -180,8 +180,9 @@ export class TenantProvisioningService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Create default roles
|
// Create default roles
|
||||||
const [adminRoleId] = await tenantKnex('roles').insert({
|
const adminRoleId = crypto.randomUUID();
|
||||||
id: tenantKnex.raw('(UUID())'),
|
await tenantKnex('roles').insert({
|
||||||
|
id: adminRoleId,
|
||||||
name: 'Admin',
|
name: 'Admin',
|
||||||
guardName: 'api',
|
guardName: 'api',
|
||||||
description: 'Full system administrator access',
|
description: 'Full system administrator access',
|
||||||
@@ -189,8 +190,9 @@ export class TenantProvisioningService {
|
|||||||
updated_at: tenantKnex.fn.now(),
|
updated_at: tenantKnex.fn.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const [userRoleId] = await tenantKnex('roles').insert({
|
const userRoleId = crypto.randomUUID();
|
||||||
id: tenantKnex.raw('(UUID())'),
|
await tenantKnex('roles').insert({
|
||||||
|
id: userRoleId,
|
||||||
name: 'User',
|
name: 'User',
|
||||||
guardName: 'api',
|
guardName: 'api',
|
||||||
description: 'Standard user access',
|
description: 'Standard user access',
|
||||||
@@ -212,7 +214,7 @@ export class TenantProvisioningService {
|
|||||||
|
|
||||||
for (const perm of permissions) {
|
for (const perm of permissions) {
|
||||||
await tenantKnex('permissions').insert({
|
await tenantKnex('permissions').insert({
|
||||||
id: tenantKnex.raw('(UUID())'),
|
id: crypto.randomUUID(),
|
||||||
name: perm.name,
|
name: perm.name,
|
||||||
guardName: 'api',
|
guardName: 'api',
|
||||||
description: perm.description,
|
description: perm.description,
|
||||||
@@ -225,7 +227,7 @@ export class TenantProvisioningService {
|
|||||||
const allPermissions = await tenantKnex('permissions').select('id');
|
const allPermissions = await tenantKnex('permissions').select('id');
|
||||||
for (const perm of allPermissions) {
|
for (const perm of allPermissions) {
|
||||||
await tenantKnex('role_permissions').insert({
|
await tenantKnex('role_permissions').insert({
|
||||||
id: tenantKnex.raw('(UUID())'),
|
id: crypto.randomUUID(),
|
||||||
roleId: adminRoleId,
|
roleId: adminRoleId,
|
||||||
permissionId: perm.id,
|
permissionId: perm.id,
|
||||||
created_at: tenantKnex.fn.now(),
|
created_at: tenantKnex.fn.now(),
|
||||||
@@ -239,7 +241,7 @@ export class TenantProvisioningService {
|
|||||||
.select('id');
|
.select('id');
|
||||||
for (const perm of userPermissions) {
|
for (const perm of userPermissions) {
|
||||||
await tenantKnex('role_permissions').insert({
|
await tenantKnex('role_permissions').insert({
|
||||||
id: tenantKnex.raw('(UUID())'),
|
id: crypto.randomUUID(),
|
||||||
roleId: userRoleId,
|
roleId: userRoleId,
|
||||||
permissionId: perm.id,
|
permissionId: perm.id,
|
||||||
created_at: tenantKnex.fn.now(),
|
created_at: tenantKnex.fn.now(),
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
export const useApi = () => {
|
export const useApi = () => {
|
||||||
const config = useRuntimeConfig()
|
const config = useRuntimeConfig()
|
||||||
const apiBaseUrl = config.public.apiBaseUrl
|
|
||||||
|
// Use current domain for API calls (same subdomain routing)
|
||||||
|
const getApiBaseUrl = () => {
|
||||||
|
if (import.meta.client) {
|
||||||
|
// In browser, use current hostname but with port 3000 for API
|
||||||
|
const currentHost = window.location.hostname
|
||||||
|
const protocol = window.location.protocol
|
||||||
|
return `${protocol}//${currentHost}:3000`
|
||||||
|
}
|
||||||
|
// Fallback for SSR
|
||||||
|
return config.public.apiBaseUrl
|
||||||
|
}
|
||||||
|
|
||||||
const getHeaders = () => {
|
const getHeaders = () => {
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
@@ -25,7 +36,7 @@ export const useApi = () => {
|
|||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
async get(path: string) {
|
async get(path: string) {
|
||||||
const response = await fetch(`${apiBaseUrl}/api${path}`, {
|
const response = await fetch(`${getApiBaseUrl()}/api${path}`, {
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
})
|
})
|
||||||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
@@ -33,7 +44,7 @@ export const useApi = () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async post(path: string, data: any) {
|
async post(path: string, data: any) {
|
||||||
const response = await fetch(`${apiBaseUrl}/api${path}`, {
|
const response = await fetch(`${getApiBaseUrl()}/api${path}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@@ -43,7 +54,7 @@ export const useApi = () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async put(path: string, data: any) {
|
async put(path: string, data: any) {
|
||||||
const response = await fetch(`${apiBaseUrl}/api${path}`, {
|
const response = await fetch(`${getApiBaseUrl()}/api${path}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
@@ -53,7 +64,7 @@ export const useApi = () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async delete(path: string) {
|
async delete(path: string) {
|
||||||
const response = await fetch(`${apiBaseUrl}/api${path}`, {
|
const response = await fetch(`${getApiBaseUrl()}/api${path}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ services:
|
|||||||
context: ../backend
|
context: ../backend
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
container_name: platform-api
|
container_name: platform-api
|
||||||
command: npm run start:dev
|
command: npm run start:dev -- --host 0.0.0.0
|
||||||
env_file:
|
env_file:
|
||||||
- ../.env.api
|
- ../.env.api
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
Reference in New Issue
Block a user