import { Body, Controller, Get, HttpException, Post, Query, Req, Res, } from '@nestjs/common'; import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request'; 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('/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; // @ts-ignore return { ...user, orgId: organization.id, // @ts-ignore totalChannels: !process.env.STRIPE_PUBLISHABLE_KEY ? 10000 : organization?.subscription?.totalChannels || pricing.FREE.channel, // @ts-ignore tier: organization?.subscription?.subscriptionTier || (!process.env.STRIPE_PUBLISHABLE_KEY ? 'ULTIMATE' : 'FREE'), // @ts-ignore role: organization?.users[0]?.role, // @ts-ignore isLifetime: !!organization?.subscription?.isLifetime, admin: !!user.isSuperAdmin, impersonate: !!impersonate, isTrailing: !process.env.STRIPE_PUBLISHABLE_KEY ? false : organization?.isTrailing, allowTrial: organization?.allowTrial, // @ts-ignore 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); } @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 } ) { 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, }); } }