mirror of
https://github.com/IT4Change/gradido.git
synced 2026-04-06 01:25:28 +00:00
Merge branch 'master' into migrate_with_zig
This commit is contained in:
commit
243475008f
@ -1,9 +1,8 @@
|
||||
import { ProjectBranding } from 'database'
|
||||
import { dbFindProjectSpaceUrl } from 'database'
|
||||
import { SignJWT } from 'jose'
|
||||
import { getLogger } from 'log4js'
|
||||
import { IRequestOptions, IRestResponse, RestClient } from 'typed-rest-client'
|
||||
import { CONFIG } from '@/config'
|
||||
|
||||
import { LOG4JS_BASE_CATEGORY_NAME } from '@/config/const'
|
||||
import { LogError } from '@/server/LogError'
|
||||
import { PostUserLoggingView } from './logging/PostUserLogging.view'
|
||||
@ -67,15 +66,9 @@ export class HumHubClient {
|
||||
public async createAutoLoginUrl(username: string, project?: string | null) {
|
||||
const secret = new TextEncoder().encode(CONFIG.HUMHUB_JWT_KEY)
|
||||
logger.info(`user ${username} as username for humhub auto-login`)
|
||||
let redirectLink: string | undefined
|
||||
let redirectLink: string | undefined | null
|
||||
if (project) {
|
||||
const projectBranding = await ProjectBranding.findOne({
|
||||
where: { alias: project },
|
||||
select: { spaceUrl: true },
|
||||
})
|
||||
if (projectBranding?.spaceUrl) {
|
||||
redirectLink = projectBranding.spaceUrl
|
||||
}
|
||||
redirectLink = await dbFindProjectSpaceUrl(project)
|
||||
}
|
||||
const token = await new SignJWT({ username, redirectLink })
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { ProjectBranding as dbProjectBranding } from 'database'
|
||||
import { ProjectBrandingSelect } from 'database'
|
||||
import { ProjectBranding as ProjectBrandingZodSchema } from 'shared'
|
||||
import { Field, Int, ObjectType } from 'type-graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class ProjectBranding {
|
||||
constructor(projectBranding: dbProjectBranding) {
|
||||
// TODO: replace with valibot schema
|
||||
constructor(projectBranding: ProjectBrandingZodSchema | ProjectBrandingSelect) {
|
||||
Object.assign(this, projectBranding)
|
||||
}
|
||||
|
||||
|
||||
@ -2,8 +2,16 @@ import { ProjectBrandingInput } from '@input/ProjectBrandingInput'
|
||||
import { ProjectBranding } from '@model/ProjectBranding'
|
||||
import { Space } from '@model/Space'
|
||||
import { SpaceList } from '@model/SpaceList'
|
||||
import { ProjectBranding as DbProjectBranding } from 'database'
|
||||
import {
|
||||
dbDeleteProjectBranding,
|
||||
dbFindAllProjectBrandings,
|
||||
dbFindProjectBrandingById,
|
||||
dbGetProjectLogoURL,
|
||||
dbUpsertProjectBranding,
|
||||
projectBrandingsTable,
|
||||
} from 'database'
|
||||
import { getLogger } from 'log4js'
|
||||
import { projectBrandingSchema } from 'shared'
|
||||
import { Arg, Authorized, ID, Int, Mutation, Query, Resolver } from 'type-graphql'
|
||||
import { HumHubClient } from '@/apis/humhub/HumHubClient'
|
||||
import { RIGHTS } from '@/auth/RIGHTS'
|
||||
@ -17,15 +25,15 @@ export class ProjectBrandingResolver {
|
||||
@Query(() => [ProjectBranding])
|
||||
@Authorized([RIGHTS.PROJECT_BRANDING_VIEW])
|
||||
async projectBrandings(): Promise<ProjectBranding[]> {
|
||||
return (await DbProjectBranding.find()).map(
|
||||
(entity: DbProjectBranding) => new ProjectBranding(entity),
|
||||
return (await dbFindAllProjectBrandings()).map(
|
||||
(entity: typeof projectBrandingsTable.$inferSelect) => new ProjectBranding(entity),
|
||||
)
|
||||
}
|
||||
|
||||
@Query(() => ProjectBranding)
|
||||
@Authorized([RIGHTS.PROJECT_BRANDING_VIEW])
|
||||
async projectBranding(@Arg('id', () => Int) id: number): Promise<ProjectBranding> {
|
||||
const projectBrandingEntity = await DbProjectBranding.findOneBy({ id })
|
||||
const projectBrandingEntity = await dbFindProjectBrandingById(id)
|
||||
if (!projectBrandingEntity) {
|
||||
throw new LogError(`Project Branding with id: ${id} not found`)
|
||||
}
|
||||
@ -35,14 +43,7 @@ export class ProjectBrandingResolver {
|
||||
@Query(() => String, { nullable: true })
|
||||
@Authorized([RIGHTS.PROJECT_BRANDING_BANNER])
|
||||
async projectBrandingBanner(@Arg('alias', () => String) alias: string): Promise<string | null> {
|
||||
const projectBrandingEntity = await DbProjectBranding.findOne({
|
||||
where: { alias },
|
||||
select: { id: true, logoUrl: true },
|
||||
})
|
||||
if (!projectBrandingEntity) {
|
||||
throw new LogError(`Project Branding with alias: ${alias} not found`)
|
||||
}
|
||||
return projectBrandingEntity.logoUrl
|
||||
return await dbGetProjectLogoURL(alias)
|
||||
}
|
||||
|
||||
@Mutation(() => ProjectBranding, { nullable: true })
|
||||
@ -50,21 +51,14 @@ 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(projectBrandingSchema.parse(input)))
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@Authorized([RIGHTS.PROJECT_BRANDING_MUTATE])
|
||||
async deleteProjectBranding(@Arg('id', () => ID) id: number): Promise<boolean> {
|
||||
try {
|
||||
await DbProjectBranding.delete({ id })
|
||||
await dbDeleteProjectBranding(id)
|
||||
return true
|
||||
} catch (err) {
|
||||
logger.error(err)
|
||||
|
||||
@ -30,9 +30,11 @@ import {
|
||||
TransactionLink as DbTransactionLink,
|
||||
User as DbUser,
|
||||
UserContact as DbUserContact,
|
||||
dbFindProjectBrandingByAlias,
|
||||
dbFindProjectSpaceId,
|
||||
findUserByIdentifier,
|
||||
getHomeCommunity,
|
||||
ProjectBranding,
|
||||
ProjectBrandingSelect,
|
||||
UserLoggingView,
|
||||
} from 'database'
|
||||
import { GraphQLResolveInfo } from 'graphql'
|
||||
@ -202,7 +204,7 @@ export class UserResolver {
|
||||
|
||||
// request to humhub and klicktipp run in parallel
|
||||
let humhubUserPromise: Promise<IRestResponse<GetUser>> | undefined
|
||||
let projectBrandingPromise: Promise<ProjectBranding | null> | undefined
|
||||
let projectBrandingSpaceIdPromise: Promise<number | null | undefined> | undefined
|
||||
const klicktippStatePromise = getKlicktippState(dbUser.emailContact.email)
|
||||
if (CONFIG.HUMHUB_ACTIVE && dbUser.humhubAllowed) {
|
||||
const getHumhubUser = new PostUser(dbUser)
|
||||
@ -211,10 +213,7 @@ export class UserResolver {
|
||||
)
|
||||
}
|
||||
if (project) {
|
||||
projectBrandingPromise = ProjectBranding.findOne({
|
||||
where: { alias: project },
|
||||
select: { spaceId: true },
|
||||
})
|
||||
projectBrandingSpaceIdPromise = dbFindProjectSpaceId(project)
|
||||
}
|
||||
|
||||
if (
|
||||
@ -244,19 +243,15 @@ export class UserResolver {
|
||||
})
|
||||
|
||||
await EVENT_USER_LOGIN(dbUser)
|
||||
const projectBranding = await projectBrandingPromise
|
||||
logger.debug('project branding: ', projectBranding?.id)
|
||||
const projectBrandingSpaceId = await projectBrandingSpaceIdPromise
|
||||
logger.debug('project branding: ', projectBrandingSpaceId)
|
||||
// load humhub state
|
||||
if (humhubUserPromise) {
|
||||
try {
|
||||
const result = await humhubUserPromise
|
||||
user.humhubAllowed = result?.result?.account.status === 1
|
||||
if (user.humhubAllowed && result?.result?.account?.username) {
|
||||
let spaceId = null
|
||||
if (projectBranding) {
|
||||
spaceId = projectBranding.spaceId
|
||||
}
|
||||
await syncHumhub(null, dbUser, result.result.account.username, spaceId)
|
||||
await syncHumhub(null, dbUser, result.result.account.username, projectBrandingSpaceId)
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error("couldn't reach out to humhub, disable for now", e)
|
||||
@ -359,12 +354,9 @@ export class UserResolver {
|
||||
return user
|
||||
}
|
||||
}
|
||||
let projectBrandingPromise: Promise<ProjectBranding | null> | undefined
|
||||
let projectBrandingPromise: Promise<ProjectBrandingSelect | undefined> | undefined
|
||||
if (project) {
|
||||
projectBrandingPromise = ProjectBranding.findOne({
|
||||
where: { alias: project },
|
||||
select: { logoUrl: true, spaceId: true },
|
||||
})
|
||||
projectBrandingPromise = dbFindProjectBrandingByAlias(project)
|
||||
}
|
||||
const gradidoID = await newGradidoID(logger)
|
||||
|
||||
@ -423,7 +415,7 @@ export class UserResolver {
|
||||
const queryRunner = db.getDataSource().createQueryRunner()
|
||||
await queryRunner.connect()
|
||||
await queryRunner.startTransaction('REPEATABLE READ')
|
||||
let projectBranding: ProjectBranding | null | undefined
|
||||
let projectBranding: ProjectBrandingSelect | undefined
|
||||
try {
|
||||
dbUser = await queryRunner.manager.save(dbUser).catch((error) => {
|
||||
throw new LogError('Error while saving dbUser', error)
|
||||
@ -924,7 +916,9 @@ export class UserResolver {
|
||||
}
|
||||
// should rarely happen, so we don't optimize for parallel processing
|
||||
if (!dbUser.humhubAllowed && project) {
|
||||
await ProjectBranding.findOneOrFail({ where: { alias: project } })
|
||||
if (!(await dbFindProjectBrandingByAlias(project))) {
|
||||
throw new LogError(`project branding with alias: ${project} not found`)
|
||||
}
|
||||
dbUser.humhubAllowed = true
|
||||
await dbUser.save()
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ export async function syncHumhub(
|
||||
updateUserInfosArg: UpdateUserInfosArgs | null,
|
||||
user: User,
|
||||
oldHumhubUsername: string,
|
||||
spaceId?: number | null,
|
||||
spaceId?: number | null | undefined,
|
||||
): Promise<GetUser | null | undefined> {
|
||||
const logger = createLogger()
|
||||
logger.addContext('user', user.id)
|
||||
|
||||
@ -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;`)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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,
|
||||
|
||||
@ -5,6 +5,7 @@ export * from './communityHandshakes'
|
||||
export * from './events'
|
||||
export * from './openaiThreads'
|
||||
export * from './pendingTransactions'
|
||||
export * from './projectBranding'
|
||||
export * from './transactionLinks'
|
||||
export * from './transactions'
|
||||
export * from './user'
|
||||
|
||||
116
database/src/queries/projectBranding.ts
Normal file
116
database/src/queries/projectBranding.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import { eq, sql } from 'drizzle-orm'
|
||||
import { ProjectBranding } from 'shared/src/schema/projectBranding.schema'
|
||||
import { drizzleDb } from '../AppDatabase'
|
||||
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()
|
||||
.select({ spaceUrl: projectBrandingsTable.spaceUrl })
|
||||
.from(projectBrandingsTable)
|
||||
.where(eq(projectBrandingsTable.alias, alias))
|
||||
.limit(1)
|
||||
return result.at(0)?.spaceUrl
|
||||
}
|
||||
|
||||
export async function dbFindProjectSpaceId(alias: string): Promise<number | null | undefined> {
|
||||
const result = await drizzleDb()
|
||||
.select({ spaceId: projectBrandingsTable.spaceId })
|
||||
.from(projectBrandingsTable)
|
||||
.where(eq(projectBrandingsTable.alias, alias))
|
||||
.limit(1)
|
||||
return result.at(0)?.spaceId
|
||||
}
|
||||
/**
|
||||
*
|
||||
* @param alias throw if project not found
|
||||
* @returns logoUrl if project has logoUrl, else return null
|
||||
*/
|
||||
|
||||
export async function dbGetProjectLogoURL(alias: string): Promise<string | null> {
|
||||
const result = await drizzleDb()
|
||||
.select({ logoUrl: projectBrandingsTable.logoUrl })
|
||||
.from(projectBrandingsTable)
|
||||
.where(eq(projectBrandingsTable.alias, alias))
|
||||
.limit(1)
|
||||
const firstEntry = result.at(0)
|
||||
if (!firstEntry) {
|
||||
throw new Error(`Project Branding with alias: ${alias} not found`)
|
||||
}
|
||||
return firstEntry.logoUrl
|
||||
}
|
||||
|
||||
export async function dbFindAllProjectBrandings(): Promise<ProjectBrandingSelect[]> {
|
||||
const result = await drizzleDb().select().from(projectBrandingsTable)
|
||||
return result
|
||||
}
|
||||
|
||||
export async function dbFindProjectBrandingById(
|
||||
id: number,
|
||||
): Promise<ProjectBrandingSelect | undefined> {
|
||||
const result = await drizzleDb()
|
||||
.select()
|
||||
.from(projectBrandingsTable)
|
||||
.where(eq(projectBrandingsTable.id, id))
|
||||
.limit(1)
|
||||
return result.at(0)
|
||||
}
|
||||
|
||||
export async function dbFindProjectBrandingByAlias(
|
||||
alias: string,
|
||||
): Promise<ProjectBrandingSelect | undefined> {
|
||||
const result = await drizzleDb()
|
||||
.select()
|
||||
.from(projectBrandingsTable)
|
||||
.where(eq(projectBrandingsTable.alias, alias))
|
||||
.limit(1)
|
||||
return result.at(0)
|
||||
}
|
||||
|
||||
export async function dbDeleteProjectBranding(id: number): Promise<void> {
|
||||
await drizzleDb().delete(projectBrandingsTable).where(eq(projectBrandingsTable.id, id))
|
||||
}
|
||||
@ -1,4 +1,14 @@
|
||||
import { int, mysqlTable, timestamp, varchar } from 'drizzle-orm/mysql-core'
|
||||
import { sql } from 'drizzle-orm'
|
||||
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(),
|
||||
@ -6,3 +16,21 @@ export const openaiThreadsTable = mysqlTable('openai_threads', {
|
||||
updatedAt: timestamp({ mode: 'date' }).defaultNow().onUpdateNow().notNull(),
|
||||
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`),
|
||||
},
|
||||
(table) => [uniqueIndex('project_brandings_alias_unique').on(table.alias)],
|
||||
)
|
||||
|
||||
export type ProjectBrandingSelect = typeof projectBrandingsTable.$inferSelect
|
||||
export type ProjectBrandingInsert = typeof projectBrandingsTable.$inferInsert
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
export * from './base.schema'
|
||||
export * from './community.schema'
|
||||
export * from './projectBranding.schema'
|
||||
export * from './user.schema'
|
||||
|
||||
15
shared/src/schema/projectBranding.schema.ts
Normal file
15
shared/src/schema/projectBranding.schema.ts
Normal 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>
|
||||
Loading…
x
Reference in New Issue
Block a user