use drizzleOrm and zod schema for projectBranding Code

This commit is contained in:
einhornimmond 2026-03-09 15:34:23 +01:00
parent b3e304efde
commit 750563ca7d
10 changed files with 118 additions and 68 deletions

View File

@ -1,10 +1,11 @@
import { projectBrandingsTable } from 'database'
import { ProjectBrandingSelect } from 'database'
import { ProjectBranding as ProjectBrandingZodSchema } from 'shared'
import { Field, Int, ObjectType } from 'type-graphql'
@ObjectType()
export class ProjectBranding {
// TODO: replace with valibot schema
constructor(projectBranding: typeof projectBrandingsTable.$inferSelect) {
constructor(projectBranding: ProjectBrandingZodSchema | ProjectBrandingSelect) {
Object.assign(this, projectBranding)
}

View File

@ -2,12 +2,13 @@ import { ProjectBrandingInput } from '@input/ProjectBrandingInput'
import { ProjectBranding } from '@model/ProjectBranding'
import { Space } from '@model/Space'
import { SpaceList } from '@model/SpaceList'
import {
dbDeleteProjectBranding,
dbFindAllProjectBrandings,
dbFindProjectBrandingById,
dbGetProjectLogoURL,
projectBrandingsTable
import {
dbDeleteProjectBranding,
dbFindAllProjectBrandings,
dbFindProjectBrandingById,
dbGetProjectLogoURL,
dbUpsertProjectBranding,
projectBrandingsTable,
} from 'database'
import { getLogger } from 'log4js'
import { Arg, Authorized, ID, Int, Mutation, Query, Resolver } from 'type-graphql'
@ -49,14 +50,7 @@ export class ProjectBrandingResolver {
async upsertProjectBranding(
@Arg('input') input: ProjectBrandingInput,
): Promise<ProjectBranding | null> {
const projectBranding = input.id
? await DbProjectBranding.findOneOrFail({ where: { id: input.id } })
: new DbProjectBranding()
Object.assign(projectBranding, input)
await projectBranding.save()
return new ProjectBranding(projectBranding)
return new ProjectBranding(await dbUpsertProjectBranding(input))
}
@Mutation(() => Boolean)

View File

@ -1,6 +1,5 @@
{
"lockfileVersion": 1,
"configVersion": 0,
"workspaces": {
"": {
"name": "gradido",

View File

@ -0,0 +1,9 @@
export async function upgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(
`ALTER TABLE project_brandings ADD UNIQUE INDEX project_brandings_alias_unique (alias);`,
)
}
export async function downgrade(queryFn: (query: string, values?: any[]) => Promise<Array<any>>) {
await queryFn(`ALTER TABLE project_brandings DROP INDEX project_brandings_alias_unique;`)
}

View File

@ -1,28 +0,0 @@
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm'
@Entity('project_brandings')
export class ProjectBranding extends BaseEntity {
@PrimaryGeneratedColumn('increment', { unsigned: true })
id: number
@Column({ name: 'name', type: 'varchar', length: 255 })
name: string
@Column({ name: 'alias', type: 'varchar', length: 32 })
alias: string
@Column({ name: 'description', type: 'text', nullable: true, default: null })
description: string | null
@Column({ name: 'space_id', type: 'int', unsigned: true, nullable: true, default: null })
spaceId: number | null
@Column({ name: 'space_url', type: 'varchar', length: 255, nullable: true, default: null })
spaceUrl: string | null
@Column({ name: 'new_user_to_space', type: 'tinyint', width: 1, default: 0 })
newUserToSpace: boolean
@Column({ name: 'logo_url', type: 'varchar', length: 255, nullable: true, default: null })
logoUrl: string | null
}

View File

@ -9,7 +9,6 @@ import { FederatedCommunity } from './FederatedCommunity'
import { LoginElopageBuys } from './LoginElopageBuys'
import { Migration } from './Migration'
import { PendingTransaction } from './PendingTransaction'
import { ProjectBranding } from './ProjectBranding'
import { Transaction } from './Transaction'
import { TransactionLink } from './TransactionLink'
import { User } from './User'
@ -27,7 +26,6 @@ export {
FederatedCommunity,
LoginElopageBuys,
Migration,
ProjectBranding,
PendingTransaction,
Transaction,
TransactionLink,
@ -47,7 +45,6 @@ export const entities = [
FederatedCommunity,
LoginElopageBuys,
Migration,
ProjectBranding,
PendingTransaction,
Transaction,
TransactionLink,

View File

@ -1,6 +1,52 @@
import { eq } from 'drizzle-orm'
import { eq, sql } from 'drizzle-orm'
import { ProjectBranding } from 'shared/src/schema/projectBranding.schema'
import { drizzleDb } from '../AppDatabase'
import { projectBrandingsTable } from '../schemas/drizzle.schema'
import {
ProjectBrandingInsert,
ProjectBrandingSelect,
projectBrandingsTable,
} from '../schemas/drizzle.schema'
/**
* Needed because of TypeScript 4, in TypeScript 5 we can use valibot and auto deduct a valibot schema from drizzle db schema
* Converts a ProjectBranding object to a ProjectBrandingInsert object to be used in database operations.
* @param projectBranding - The ProjectBranding object to convert.
* @returns The converted ProjectBrandingInsert object.
*/
function toDbInsert(projectBranding: ProjectBranding): ProjectBrandingInsert {
return {
// Omit ID when inserting (autoincrement) or set it if it exists
id: projectBranding.id ?? undefined,
name: projectBranding.name,
alias: projectBranding.alias,
// Set null in DB if undefined/null
description: projectBranding.description ?? null,
spaceId: projectBranding.spaceId ?? null,
spaceUrl: projectBranding.spaceUrl ?? null,
// Convert boolean to tinyint (1/0)
newUserToSpace: projectBranding.newUserToSpace ? 1 : 0,
logoUrl: projectBranding.logoUrl ?? null,
}
}
export async function dbUpsertProjectBranding(
projectBranding: ProjectBranding,
): Promise<ProjectBranding> {
if (projectBranding.id) {
await drizzleDb()
.update(projectBrandingsTable)
.set(toDbInsert(projectBranding))
.where(eq(projectBrandingsTable.id, projectBranding.id))
return projectBranding
} else {
const drizzleProjectBranding = toDbInsert(projectBranding)
const result = await drizzleDb().insert(projectBrandingsTable).values(drizzleProjectBranding)
projectBranding.id = result[0].insertId
return projectBranding
}
}
export async function dbFindProjectSpaceUrl(alias: string): Promise<string | null | undefined> {
const result = await drizzleDb()
@ -11,7 +57,7 @@ export async function dbFindProjectSpaceUrl(alias: string): Promise<string | nul
return result.at(0)?.spaceUrl
}
/**
*
*
* @param alias throw if project not found
* @returns logoUrl if project has logoUrl, else return null
*/
@ -29,14 +75,14 @@ export async function dbGetProjectLogoURL(alias: string): Promise<string | null>
return firstEntry.logoUrl
}
export async function dbFindAllProjectBrandings(): Promise<typeof projectBrandingsTable.$inferSelect[]> {
const result = await drizzleDb()
.select()
.from(projectBrandingsTable)
export async function dbFindAllProjectBrandings(): Promise<ProjectBrandingSelect[]> {
const result = await drizzleDb().select().from(projectBrandingsTable)
return result
}
export async function dbFindProjectBrandingById(id: number): Promise<typeof projectBrandingsTable.$inferSelect | undefined> {
export async function dbFindProjectBrandingById(
id: number,
): Promise<ProjectBrandingSelect | undefined> {
const result = await drizzleDb()
.select()
.from(projectBrandingsTable)
@ -47,4 +93,4 @@ export async function dbFindProjectBrandingById(id: number): Promise<typeof proj
export async function dbDeleteProjectBranding(id: number): Promise<void> {
await drizzleDb().delete(projectBrandingsTable).where(eq(projectBrandingsTable.id, id))
}
}

View File

@ -1,5 +1,14 @@
import { sql } from 'drizzle-orm'
import { int, mysqlTable, text, timestamp, tinyint, varchar } from 'drizzle-orm/mysql-core'
import {
int,
mysqlTable,
text,
timestamp,
tinyint,
uniqueIndex,
varchar,
} from 'drizzle-orm/mysql-core'
import { z } from 'zod'
export const openaiThreadsTable = mysqlTable('openai_threads', {
id: varchar({ length: 128 }).notNull(),
@ -8,13 +17,20 @@ export const openaiThreadsTable = mysqlTable('openai_threads', {
userId: int('user_id').notNull(),
})
export const projectBrandingsTable = mysqlTable("project_brandings", {
id: int().autoincrement().notNull(),
name: varchar({ length: 255 }).notNull(),
alias: varchar({ length: 32 }).notNull(),
description: text().default(sql`NULL`),
spaceId: int("space_id").default(sql`NULL`),
spaceUrl: varchar("space_url", { length: 255 }).default(sql`NULL`),
newUserToSpace: tinyint("new_user_to_space").default(0).notNull(),
logoUrl: varchar("logo_url", { length: 255 }).default(sql`NULL`),
})
export const projectBrandingsTable = mysqlTable(
'project_brandings',
{
id: int().autoincrement().notNull(),
name: varchar({ length: 255 }).notNull(),
alias: varchar({ length: 32 }).notNull(),
description: text().default(sql`NULL`),
spaceId: int('space_id').default(sql`NULL`),
spaceUrl: varchar('space_url', { length: 255 }).default(sql`NULL`),
newUserToSpace: tinyint('new_user_to_space').default(0).notNull(),
logoUrl: varchar('logo_url', { length: 255 }).default(sql`NULL`),
},
(table) => [uniqueIndex('project_brandings_alias_unique').on(table.alias)],
)
export type ProjectBrandingSelect = typeof projectBrandingsTable.$inferSelect
export type ProjectBrandingInsert = typeof projectBrandingsTable.$inferInsert

View File

@ -1,3 +1,4 @@
export * from './base.schema'
export * from './community.schema'
export * from './projectBranding.schema'
export * from './user.schema'

View File

@ -0,0 +1,15 @@
import { z } from 'zod'
// will be auto-generated in future directly from Drizzle table schema, this need TypeScript 5
export const projectBrandingSchema = z.object({
id: z.number().optional().nullable(),
name: z.string(),
alias: z.string().max(32),
description: z.string().optional().nullable(),
spaceId: z.number().optional().nullable(),
spaceUrl: z.string().url().optional().nullable(),
newUserToSpace: z.boolean(),
logoUrl: z.string().url().optional().nullable(),
})
export type ProjectBranding = z.infer<typeof projectBrandingSchema>