| import { |
| Body, |
| Controller, |
| Get, |
| HttpException, |
| Post, |
| Query, |
| Req, |
| Res, |
| } from '@nestjs/common'; |
| import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request'; |
| import { sign } from 'jsonwebtoken'; |
| import { Organization, User } from '@prisma/client'; |
| import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service'; |
| import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request'; |
| import { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service'; |
| import { Response, Request } from 'express'; |
| import { AuthService } from '@gitroom/backend/services/auth/auth.service'; |
| import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service'; |
| import { CheckPolicies } from '@gitroom/backend/services/auth/permissions/permissions.ability'; |
| import { getCookieUrlFromDomain } from '@gitroom/helpers/subdomain/subdomain.management'; |
| import { pricing } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/pricing'; |
| import { ApiTags } from '@nestjs/swagger'; |
| import { UsersService } from '@gitroom/nestjs-libraries/database/prisma/users/users.service'; |
| import { UserDetailDto } from '@gitroom/nestjs-libraries/dtos/users/user.details.dto'; |
| import { EmailNotificationsDto } from '@gitroom/nestjs-libraries/dtos/users/email-notifications.dto'; |
| import { HttpForbiddenException } from '@gitroom/nestjs-libraries/services/exception.filter'; |
| import { RealIP } from 'nestjs-real-ip'; |
| import { UserAgent } from '@gitroom/nestjs-libraries/user/user.agent'; |
| import { TrackEnum } from '@gitroom/nestjs-libraries/user/track.enum'; |
| import { TrackService } from '@gitroom/nestjs-libraries/track/track.service'; |
| import { makeId } from '@gitroom/nestjs-libraries/services/make.is'; |
| import { |
| AuthorizationActions, |
| Sections, |
| } from '@gitroom/backend/services/auth/permissions/permission.exception.class'; |
|
|
| @ApiTags('User') |
| @Controller('/user') |
| export class UsersController { |
| constructor( |
| private _subscriptionService: SubscriptionService, |
| private _stripeService: StripeService, |
| private _authService: AuthService, |
| private _orgService: OrganizationService, |
| private _userService: UsersService, |
| private _trackService: TrackService |
| ) {} |
|
|
| @Get('/chatbase-token') |
| async getChatbaseToken( |
| @GetUserFromRequest() user: User, |
| @GetOrgFromRequest() organization: Organization |
| ) { |
| if (!process.env.CHATBASE_TOKEN) { |
| throw new HttpException('Chatbase SSO is not configured', 400); |
| } |
|
|
| const token = sign( |
| { |
| user_id: organization.id, |
| email: user.email, |
| ...(organization.paymentId |
| ? { |
| stripe_accounts: [ |
| { |
| label: organization.name, |
| stripe_id: organization.paymentId, |
| }, |
| ], |
| } |
| : {}), |
| }, |
| process.env.CHATBASE_TOKEN, |
| { expiresIn: '1h' } |
| ); |
|
|
| return { token }; |
| } |
|
|
| @Get('/agent-media-sso') |
| async getAgentMediaSsoUrl( |
| @GetUserFromRequest() user: User, |
| @GetOrgFromRequest() organization: Organization |
| ) { |
| if (!process.env.AGENT_MEDIA_SSO_KEY) { |
| throw new HttpException('Agent Media SSO is not configured', 400); |
| } |
|
|
| const token = sign( |
| { id: organization.id, displayName: organization.name }, |
| process.env.AGENT_MEDIA_SSO_KEY |
| ); |
|
|
| return { url: `https://agent-media.ai/sso/${token}` }; |
| } |
|
|
| @Get('/self') |
| async getSelf( |
| @GetUserFromRequest() user: User, |
| @GetOrgFromRequest() organization: Organization, |
| @Req() req: Request |
| ) { |
| if (!organization) { |
| throw new HttpForbiddenException(); |
| } |
|
|
| const impersonate = req.cookies.impersonate || req.headers.impersonate; |
| |
| return { |
| ...user, |
| orgId: organization.id, |
| totalChannels: !process.env.STRIPE_PUBLISHABLE_KEY |
| ? 10000 |
| : |
| organization?.subscription?.totalChannels || pricing.FREE.channel, |
| tier: |
| |
| organization?.subscription?.subscriptionTier || |
| (!process.env.STRIPE_PUBLISHABLE_KEY ? 'ULTIMATE' : 'FREE'), |
| |
| role: organization?.users[0]?.role, |
| |
| isLifetime: !!organization?.subscription?.isLifetime, |
| admin: !!user.isSuperAdmin, |
| impersonate: !!impersonate, |
| isTrailing: !process.env.STRIPE_PUBLISHABLE_KEY |
| ? false |
| : organization?.isTrailing, |
| allowTrial: organization?.allowTrial, |
| streakSince: organization?.streakSince || null, |
| publicApi: |
| |
| organization?.users[0]?.role === 'SUPERADMIN' || |
| |
| organization?.users[0]?.role === 'ADMIN' |
| ? organization?.apiKey |
| : '', |
| }; |
| } |
|
|
| @Get('/personal') |
| async getPersonalInformation(@GetUserFromRequest() user: User) { |
| return this._userService.getPersonal(user.id); |
| } |
|
|
| @Get('/impersonate') |
| async getImpersonate( |
| @GetUserFromRequest() user: User, |
| @Query('name') name: string |
| ) { |
| if (!user.isSuperAdmin) { |
| throw new HttpException('Unauthorized', 400); |
| } |
|
|
| return this._userService.getImpersonateUser(name); |
| } |
|
|
| @Post('/impersonate') |
| async setImpersonate( |
| @GetUserFromRequest() user: User, |
| @Body('id') id: string, |
| @Res({ passthrough: true }) response: Response |
| ) { |
| if (!user.isSuperAdmin) { |
| throw new HttpException('Unauthorized', 400); |
| } |
|
|
| response.cookie('impersonate', id, { |
| domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!), |
| ...(!process.env.NOT_SECURED |
| ? { |
| secure: true, |
| httpOnly: true, |
| sameSite: 'none', |
| } |
| : {}), |
| expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), |
| }); |
|
|
| if (process.env.NOT_SECURED) { |
| response.header('impersonate', id); |
| } |
| } |
|
|
| @Post('/personal') |
| async changePersonal( |
| @GetUserFromRequest() user: User, |
| @Body() body: UserDetailDto |
| ) { |
| return this._userService.changePersonal(user.id, body); |
| } |
|
|
| @Get('/email-notifications') |
| async getEmailNotifications(@GetUserFromRequest() user: User) { |
| return this._userService.getEmailNotifications(user.id); |
| } |
|
|
| @Post('/email-notifications') |
| async updateEmailNotifications( |
| @GetUserFromRequest() user: User, |
| @Body() body: EmailNotificationsDto |
| ) { |
| return this._userService.updateEmailNotifications(user.id, body); |
| } |
|
|
| @Post('/api-key/rotate') |
| @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN]) |
| async rotateApiKey(@GetOrgFromRequest() organization: Organization) { |
| return this._orgService.updateApiKey(organization.id); |
| } |
|
|
| @Get('/subscription') |
| @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN]) |
| async getSubscription(@GetOrgFromRequest() organization: Organization) { |
| const subscription = |
| await this._subscriptionService.getSubscriptionByOrganizationId( |
| organization.id |
| ); |
|
|
| return subscription ? { subscription } : { subscription: undefined }; |
| } |
|
|
| @Get('/subscription/tiers') |
| @CheckPolicies([AuthorizationActions.Create, Sections.ADMIN]) |
| async tiers() { |
| return this._stripeService.getPackages(); |
| } |
|
|
| @Post('/join-org') |
| async joinOrg( |
| @GetUserFromRequest() user: User, |
| @Body('org') org: string, |
| @Res({ passthrough: true }) response: Response |
| ) { |
| const getOrgFromCookie = this._authService.getOrgFromCookie(org); |
|
|
| if (!getOrgFromCookie) { |
| return response.status(200).json({ id: null }); |
| } |
|
|
| const addedOrg = await this._orgService.addUserToOrg( |
| user.id, |
| getOrgFromCookie.id, |
| getOrgFromCookie.orgId, |
| getOrgFromCookie.role |
| ); |
|
|
| response.status(200).json({ |
| id: typeof addedOrg !== 'boolean' ? addedOrg.organizationId : null, |
| }); |
| } |
|
|
| @Get('/organizations') |
| async getOrgs(@GetUserFromRequest() user: User) { |
| return (await this._orgService.getOrgsByUserId(user.id)).filter( |
| (f) => !f.users[0].disabled |
| ); |
| } |
|
|
| @Post('/change-org') |
| changeOrg( |
| @Body('id') id: string, |
| @Res({ passthrough: true }) response: Response |
| ) { |
| response.cookie('showorg', id, { |
| domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!), |
| ...(!process.env.NOT_SECURED |
| ? { |
| secure: true, |
| httpOnly: true, |
| sameSite: 'none', |
| } |
| : {}), |
| expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), |
| }); |
|
|
| if (process.env.NOT_SECURED) { |
| response.header('showorg', id); |
| } |
|
|
| response.status(200).send(); |
| } |
|
|
| @Post('/logout') |
| logout(@Res({ passthrough: true }) response: Response) { |
| response.header('logout', 'true'); |
| response.cookie('auth', '', { |
| domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!), |
| ...(!process.env.NOT_SECURED |
| ? { |
| secure: true, |
| httpOnly: true, |
| sameSite: 'none', |
| } |
| : {}), |
| maxAge: -1, |
| expires: new Date(0), |
| }); |
|
|
| response.cookie('showorg', '', { |
| domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!), |
| ...(!process.env.NOT_SECURED |
| ? { |
| secure: true, |
| httpOnly: true, |
| sameSite: 'none', |
| } |
| : {}), |
| maxAge: -1, |
| expires: new Date(0), |
| }); |
|
|
| response.cookie('impersonate', '', { |
| domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!), |
| ...(!process.env.NOT_SECURED |
| ? { |
| secure: true, |
| httpOnly: true, |
| sameSite: 'none', |
| } |
| : {}), |
| maxAge: -1, |
| expires: new Date(0), |
| }); |
|
|
| response.status(200).send(); |
| } |
|
|
| @Post('/t') |
| async trackEvent( |
| @Res({ passthrough: true }) res: Response, |
| @Req() req: Request, |
| @GetUserFromRequest() user: User, |
| @RealIP() ip: string, |
| @UserAgent() userAgent: string, |
| @Body() |
| body: { tt: TrackEnum; fbclid: string; additional: Record<string, any> } |
| ) { |
| const uniqueId = req?.cookies?.track || makeId(10); |
| const fbclid = req?.cookies?.fbclid || body.fbclid; |
| await this._trackService.track( |
| uniqueId, |
| ip, |
| userAgent, |
| body.tt, |
| body.additional, |
| fbclid, |
| user |
| ); |
| if (!req.cookies.track) { |
| res.cookie('track', uniqueId, { |
| domain: getCookieUrlFromDomain(process.env.FRONTEND_URL!), |
| ...(!process.env.NOT_SECURED |
| ? { |
| secure: true, |
| httpOnly: true, |
| sameSite: 'none', |
| } |
| : {}), |
| expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365), |
| }); |
| } |
|
|
| res.status(200).json({ |
| track: uniqueId, |
| }); |
| } |
| } |
|
|