feat: merge

This commit is contained in:
Nevo David 2024-08-20 16:21:52 +07:00
commit 7e8b6c87f4
12 changed files with 531 additions and 2 deletions

View File

@ -26,6 +26,8 @@ import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
import { ExtractContentService } from '@gitroom/nestjs-libraries/openai/extract.content.service';
import { CodesService } from '@gitroom/nestjs-libraries/services/codes.service';
import { CopilotController } from '@gitroom/backend/api/routes/copilot.controller';
import { AgenciesController } from '@gitroom/backend/api/routes/agencies.controller';
import { PublicController } from '@gitroom/backend/api/routes/public.controller';
const authenticatedController = [
UsersController,
@ -40,6 +42,7 @@ const authenticatedController = [
MarketplaceController,
MessagesController,
CopilotController,
AgenciesController,
];
@Module({
imports: [
@ -60,7 +63,7 @@ const authenticatedController = [
]
: []),
],
controllers: [StripeController, AuthController, ...authenticatedController],
controllers: [StripeController, AuthController, PublicController, ...authenticatedController],
providers: [
AuthService,
StripeService,

View File

@ -0,0 +1,24 @@
import { Body, Controller, Get, Post } from '@nestjs/common';
import { User } from '@prisma/client';
import { ApiTags } from '@nestjs/swagger';
import { AgenciesService } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.service';
import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.request';
import { CreateAgencyDto } from '@gitroom/nestjs-libraries/dtos/agencies/create.agency.dto';
@ApiTags('Agencies')
@Controller('/agencies')
export class AgenciesController {
constructor(private _agenciesService: AgenciesService) {}
@Get('/')
async getAgencyByUser(@GetUserFromRequest() user: User) {
return this._agenciesService.getAgencyByUser(user);
}
@Post('/')
async createAgency(
@GetUserFromRequest() user: User,
@Body() body: CreateAgencyDto
) {
return this._agenciesService.createAgency(user, body);
}
}

View File

@ -0,0 +1,30 @@
import { Controller, Get, Param } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AgenciesService } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.service';
@ApiTags('Public')
@Controller('/public')
export class PublicController {
constructor(private _agenciesService: AgenciesService) {}
@Get('/agencies-list')
async getAgencyByUser() {
return this._agenciesService.getAllAgencies();
}
@Get('/agencies-list-slug')
async getAgencySlug() {
return this._agenciesService.getAllAgenciesSlug();
}
@Get('/agencies-information/:agency')
async getAgencyInformation(
@Param('agency') agency: string,
) {
return this._agenciesService.getAgencyInformation(agency);
}
@Get('/agencies-list-count')
async getAgenciesCount() {
return this._agenciesService.getCount();
}
}

View File

@ -5,6 +5,8 @@ export const dynamic = 'force-dynamic';
import { ReactNode } from 'react';
import Image from 'next/image';
import clsx from 'clsx';
import loadDynamic from 'next/dynamic';
const ReturnUrlComponent = loadDynamic(() => import('./return.url.component'));
export default async function AuthLayout({
children,
@ -13,6 +15,7 @@ export default async function AuthLayout({
}) {
return (
<>
<ReturnUrlComponent />
<div className="absolute left-0 top-0 z-[0] h-[100vh] w-[100vw] overflow-hidden bg-loginBg bg-contain bg-no-repeat bg-left-top" />
<div className="relative z-[1] pr-[100px] flex justify-end items-center h-[100vh] w-[100vw] overflow-hidden">
<div className="w-[557px] flex h-[614px] bg-loginBox bg-contain">

View File

@ -0,0 +1,27 @@
'use client';
import { useSearchParams } from 'next/navigation';
import { useCallback, useEffect } from 'react';
const ReturnUrlComponent = () => {
const params = useSearchParams();
const url = params.get('returnUrl');
useEffect(() => {
if (url?.indexOf?.('http')! > -1) {
localStorage.setItem('returnUrl', url!);
}
}, [url]);
return null;
}
export const useReturnUrl = () => {
return {
getAndClear: useCallback(() => {
const data = localStorage.getItem('returnUrl');
localStorage.removeItem('returnUrl');
return data;
}, [])
}
}
export default ReturnUrlComponent;

View File

@ -4,6 +4,7 @@ import { ReactNode, useCallback } from 'react';
import { FetchWrapperComponent } from '@gitroom/helpers/utils/custom.fetch';
import { deleteDialog } from '@gitroom/react/helpers/delete.dialog';
import { isGeneral } from '@gitroom/react/helpers/is.general';
import { useReturnUrl } from '@gitroom/frontend/app/auth/return.url.component';
export default function LayoutContext(params: { children: ReactNode }) {
if (params?.children) {
@ -14,16 +15,33 @@ export default function LayoutContext(params: { children: ReactNode }) {
return <></>;
}
function LayoutContextInner(params: { children: ReactNode }) {
const returnUrl = useReturnUrl();
const afterRequest = useCallback(
async (url: string, options: RequestInit, response: Response) => {
const reloadOrOnboarding =
response?.headers?.get('reload') ||
response?.headers?.get('onboarding');
if (reloadOrOnboarding) {
const getAndClear = returnUrl.getAndClear();
if (getAndClear) {
window.location.href = getAndClear;
return true;
}
}
if (response?.headers?.get('onboarding')) {
window.location.href = isGeneral()
? '/launches?onboarding=true'
: '/analytics?onboarding=true';
return true;
}
if (response?.headers?.get('reload')) {
window.location.reload();
return true;
}
if (response.status === 401) {

View File

@ -0,0 +1,154 @@
import { PrismaRepository } from '@gitroom/nestjs-libraries/database/prisma/prisma.service';
import { Injectable } from '@nestjs/common';
import { User } from '@prisma/client';
import { CreateAgencyDto } from '@gitroom/nestjs-libraries/dtos/agencies/create.agency.dto';
@Injectable()
export class AgenciesRepository {
constructor(
private _socialMediaAgencies: PrismaRepository<'socialMediaAgency'>,
private _socialMediaAgenciesNiche: PrismaRepository<'socialMediaAgencyNiche'>
) {}
getAllAgencies() {
return this._socialMediaAgencies.model.socialMediaAgency.findMany({
where: {
deletedAt: null,
approved: true,
},
include: {
logo: true,
niches: true,
},
});
}
getCount() {
return this._socialMediaAgencies.model.socialMediaAgency.count({
where: {
deletedAt: null,
approved: true,
},
});
}
getAllAgenciesSlug() {
return this._socialMediaAgencies.model.socialMediaAgency.findMany({
where: {
deletedAt: null,
approved: true,
},
select: {
slug: true,
},
});
}
getAgencyInformation(agency: string) {
return this._socialMediaAgencies.model.socialMediaAgency.findFirst({
where: {
slug: agency,
deletedAt: null,
approved: true,
},
include: {
logo: true,
niches: true,
},
});
}
getAgencyByUser(user: User) {
return this._socialMediaAgencies.model.socialMediaAgency.findFirst({
where: {
userId: user.id,
deletedAt: null,
},
include: {
logo: true,
niches: true,
},
});
}
async createAgency(user: User, body: CreateAgencyDto) {
const insertAgency =
await this._socialMediaAgencies.model.socialMediaAgency.upsert({
where: {
userId: user.id,
},
update: {
userId: user.id,
name: body.name,
website: body.website,
facebook: body.facebook,
instagram: body.instagram,
twitter: body.twitter,
linkedIn: body.linkedIn,
youtube: body.youtube,
tiktok: body.tiktok,
logoId: body.logo.id,
shortDescription: body.shortDescription,
description: body.description,
approved: false,
},
create: {
userId: user.id,
name: body.name,
website: body.website,
facebook: body.facebook,
instagram: body.instagram,
twitter: body.twitter,
linkedIn: body.linkedIn,
youtube: body.youtube,
tiktok: body.tiktok,
logoId: body.logo.id,
shortDescription: body.shortDescription,
description: body.description,
slug: body.name.toLowerCase().replace(/ /g, '-'),
approved: false,
},
select: {
id: true,
},
});
await this._socialMediaAgenciesNiche.model.socialMediaAgencyNiche.deleteMany(
{
where: {
agencyId: insertAgency.id,
niche: {
notIn: body.niches,
},
},
}
);
const currentNiche =
await this._socialMediaAgenciesNiche.model.socialMediaAgencyNiche.findMany(
{
where: {
agencyId: insertAgency.id,
},
select: {
niche: true,
},
}
);
const addNewNiche = body.niches.filter(
(n) => !currentNiche.some((c) => c.niche === n)
);
await this._socialMediaAgenciesNiche.model.socialMediaAgencyNiche.createMany(
{
data: addNewNiche.map((n) => ({
agencyId: insertAgency.id,
niche: n,
})),
}
);
return insertAgency;
}
}

View File

@ -0,0 +1,165 @@
import { Injectable } from '@nestjs/common';
import { AgenciesRepository } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.repository';
import { User } from '@prisma/client';
import { CreateAgencyDto } from '@gitroom/nestjs-libraries/dtos/agencies/create.agency.dto';
import { EmailService } from '@gitroom/nestjs-libraries/services/email.service';
@Injectable()
export class AgenciesService {
constructor(
private _agenciesRepository: AgenciesRepository,
private _emailService: EmailService
) {}
getAgencyByUser(user: User) {
return this._agenciesRepository.getAgencyByUser(user);
}
getCount() {
return this._agenciesRepository.getCount();
}
getAllAgencies() {
return this._agenciesRepository.getAllAgencies();
}
getAllAgenciesSlug() {
return this._agenciesRepository.getAllAgenciesSlug();
}
getAgencyInformation(agency: string) {
return this._agenciesRepository.getAgencyInformation(agency);
}
async createAgency(user: User, body: CreateAgencyDto) {
const agency = await this._agenciesRepository.createAgency(user, body);
await this._emailService.sendEmail(
'nevo@postiz.com',
'New agency created',
`
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Template</title>
</head>
<body style="font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f4f4f4;">
<table align="center" cellpadding="0" cellspacing="0" style="max-width: 600px; width: 100%; background-color: #ffffff; margin-top: 20px; border: 1px solid #ddd;">
<tr>
<td style="padding: 0 20px 20px 20px; text-align: center;">
<!-- Website -->
<a href="${
body.website
}" style="text-decoration: none; color: #007bff;">${
body.website
}</a>
</td>
</tr>
<tr>
<td style="padding: 20px; text-align: center;">
<!-- Social Media Links -->
<p style="margin: 10px 0; font-size: 16px;">
Social Medias:
<a href="${
body.facebook
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">${
body.facebook
}</a><br />
<a href="${
body.instagram
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">${
body.instagram
}</a><br />
<a href="${
body.twitter
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">${
body.twitter
}</a><br />
<a href="${
body.linkedIn
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">${
body.linkedIn
}</a><br />
<a href="${
body.youtube
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">${
body.youtube
}</a><br />
<a href="${
body.tiktok
}" style="margin: 0 10px; text-decoration: none; color: #007bff;">${
body.tiktok
}</a>
</p>
</td>
</tr>
<tr>
<td style="padding: 20px;">
<!-- Short Description -->
<h2 style="text-align: center; color: #333;">Logo</h2>
<p style="text-align: center; color: #555; font-size: 16px;">
<img src="${body.logo.path}" width="60" height="60" />
</p>
</td>
</tr>
<tr>
<td style="padding: 20px;">
<!-- Short Description -->
<h2 style="text-align: center; color: #333;">Name</h2>
<p style="text-align: center; color: #555; font-size: 16px;">${
body.name
}</p>
</td>
</tr>
<tr>
<td style="padding: 20px;">
<!-- Short Description -->
<h2 style="text-align: center; color: #333;">Short Description</h2>
<p style="text-align: center; color: #555; font-size: 16px;">${
body.shortDescription
}</p>
</td>
</tr>
<tr>
<td style="padding: 20px;">
<!-- Description -->
<h2 style="text-align: center; color: #333;">Description</h2>
<p style="text-align: center; color: #555; font-size: 16px;">${
body.description
}</p>
</td>
</tr>
<tr>
<td style="padding: 20px;">
<!-- Niches -->
<h2 style="text-align: center; color: #333;">Niches</h2>
<p style="text-align: center; color: #555; font-size: 16px;">${body.niches.join(
','
)}</p>
</td>
</tr>
<tr>
<td style="padding: 20px; text-align: center; background-color: #000;">
<a href="https://platform.postiz.com/agencies/${
agency.id
}/approve" style="margin: 0 10px; text-decoration: none; color: #007bff;">To approve click here</a><br /><br /><br />
<a href="https://platform.postiz.com/agencies/${
agency.id
}/decline" style="margin: 0 10px; text-decoration: none; color: #007bff;">To decline click here</a><br /><br /><br />
</td>
</tr>
<tr>
<td style="padding: 20px; text-align: center; background-color: #f4f4f4;">
<p style="color: #777; font-size: 14px;">&copy; 2024 Your Gitroom Limited All rights reserved.</p>
</td>
</tr>
</table>
</body>
</html>
`
);
return agency;
}
}

View File

@ -27,6 +27,8 @@ import { MessagesRepository } from '@gitroom/nestjs-libraries/database/prisma/ma
import { StripeService } from '@gitroom/nestjs-libraries/services/stripe.service';
import { ExtractContentService } from '@gitroom/nestjs-libraries/openai/extract.content.service';
import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
import { AgenciesService } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.service';
import { AgenciesRepository } from '@gitroom/nestjs-libraries/database/prisma/agencies/agencies.repository';
@Global()
@Module({
@ -55,6 +57,8 @@ import { OpenaiService } from '@gitroom/nestjs-libraries/openai/openai.service';
MediaRepository,
CommentsRepository,
ItemUserRepository,
AgenciesService,
AgenciesRepository,
ItemUserService,
MessagesService,
CommentsService,

View File

@ -62,6 +62,7 @@ model User {
orderSeller Orders[] @relation("orderSeller")
payoutProblems PayoutProblems[]
lastOnline DateTime @default(now())
agencies SocialMediaAgency[]
@@unique([email, providerName])
@@index([lastReadNotifications])
@ -165,10 +166,51 @@ model Media {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userPicture User[]
agencies SocialMediaAgency[]
@@index([organizationId])
}
model SocialMediaAgency {
id String @id @default(uuid())
user User @relation(fields: [userId], references: [id])
userId String @unique()
name String
logoId String?
logo Media? @relation(fields: [logoId], references: [id])
website String?
slug String?
facebook String?
instagram String?
twitter String?
linkedIn String?
youtube String?
tiktok String?
otherSocialMedia String?
shortDescription String
description String
niches SocialMediaAgencyNiche[]
approved Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
@@index([userId])
@@index([deletedAt])
@@index([id])
}
model SocialMediaAgencyNiche {
agencyId String
agency SocialMediaAgency @relation(fields: [agencyId], references: [id])
niche String
@@id([agencyId, niche])
}
model Credits {
id String @id @default(uuid())
organization Organization @relation(fields: [organizationId], references: [id])

View File

@ -0,0 +1,59 @@
import { ArrayMaxSize, ArrayMinSize, IsDefined, IsOptional, IsString, IsUrl, MinLength, ValidateIf } from 'class-validator';
import { Type } from 'class-transformer';
export class CreateAgencyLogoDto {
@IsString()
@IsDefined()
id: string;
path: string;
}
export class CreateAgencyDto {
@IsString()
@MinLength(3)
name: string;
@IsUrl()
@IsDefined()
website: string;
@IsUrl()
@ValidateIf((o) => o.facebook)
facebook: string;
@IsString()
@IsOptional()
instagram: string;
@IsString()
@IsOptional()
twitter: string;
@IsUrl()
@ValidateIf((o) => o.linkedIn)
linkedIn: string;
@IsUrl()
@ValidateIf((o) => o.youtube)
youtube: string;
@IsString()
@IsOptional()
tiktok: string;
@Type(() => CreateAgencyLogoDto)
logo: CreateAgencyLogoDto;
@IsString()
shortDescription: string;
@IsString()
description: string;
@IsString({
each: true
})
@ArrayMinSize(1)
@ArrayMaxSize(3)
niches: string[];
}

View File

@ -13,7 +13,7 @@ export class EmailService {
console.log('Sending email to', to);
const sends = await resend.emails.send({
from: process.env.IS_GENERAL === 'true' ? 'Nevo <nevo@postiz.com>' : 'Nevo <nevo@gitroom.com>',
from: process.env.IS_GENERAL === 'true' ? 'Nevo <nevo@postiz.com>' : 'Nevo <nevo@postiz.com>',
to,
subject,
html,