Merge branch 'dev' into dependabot/npm_and_yarn/nx/vite-20.5.0
This commit is contained in:
commit
c2b3f1fdbf
|
|
@ -12,7 +12,7 @@ pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
stage('Chechout Node.js and npm') {
|
||||
stage('Check Node.js and npm') {
|
||||
steps {
|
||||
script {
|
||||
sh "node -v"
|
||||
|
|
@ -27,17 +27,25 @@ pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
stage('Run Unit Tests') {
|
||||
steps {
|
||||
sh 'npm test'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build Project') {
|
||||
steps {
|
||||
sh 'npm run build'
|
||||
sh 'npm run build 2>&1 | tee build_report.log' // Captures build output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
cleanWs(cleanWhenNotBuilt: false,
|
||||
notFailBuild: true)
|
||||
junit '**/reports/junit.xml'
|
||||
archiveArtifacts artifacts: 'reports/**', fingerprint: true
|
||||
archiveArtifacts artifacts: 'build_report.log', fingerprint: true
|
||||
cleanWs(cleanWhenNotBuilt: false, notFailBuild: true)
|
||||
}
|
||||
success {
|
||||
echo 'Build completed successfully!'
|
||||
|
|
|
|||
|
|
@ -56,8 +56,12 @@ export class MarketplaceController {
|
|||
connectBankAccount(
|
||||
@GetUserFromRequest() user: User,
|
||||
@Query('country') country: string
|
||||
) {
|
||||
return this._stripeService.createAccountProcess(user.id, user.email, country);
|
||||
) {
|
||||
return this._stripeService.createAccountProcess(
|
||||
user.id,
|
||||
user.email,
|
||||
country
|
||||
);
|
||||
}
|
||||
|
||||
@Post('/item')
|
||||
|
|
@ -126,12 +130,19 @@ export class MarketplaceController {
|
|||
@GetOrgFromRequest() organization: Organization,
|
||||
@Param('id') id: string
|
||||
) {
|
||||
const getPost = await this._messagesService.getPost(user.id, organization.id, id);
|
||||
const getPost = await this._messagesService.getPost(
|
||||
user.id,
|
||||
organization.id,
|
||||
id
|
||||
);
|
||||
if (!getPost) {
|
||||
return ;
|
||||
return;
|
||||
}
|
||||
|
||||
return {...await this._postsService.getPost(getPost.organizationId, id), providerId: getPost.integration.providerIdentifier};
|
||||
return {
|
||||
...(await this._postsService.getPost(getPost.organizationId, id)),
|
||||
providerId: getPost.integration.providerIdentifier,
|
||||
};
|
||||
}
|
||||
|
||||
@Post('/posts/:id/revision')
|
||||
|
|
|
|||
|
|
@ -0,0 +1,404 @@
|
|||
import { mock } from 'jest-mock-extended';
|
||||
import { IntegrationService } from '@gitroom/nestjs-libraries/database/prisma/integrations/integration.service';
|
||||
import { PostsService } from '@gitroom/nestjs-libraries/database/prisma/posts/posts.service';
|
||||
import { SubscriptionService } from '@gitroom/nestjs-libraries/database/prisma/subscriptions/subscription.service';
|
||||
import { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';
|
||||
import { PermissionsService } from './permissions.service';
|
||||
import { AuthorizationActions, Sections } from './permissions.service';
|
||||
import { Period, SubscriptionTier } from '@prisma/client';
|
||||
|
||||
// Mock of dependent services
|
||||
const mockSubscriptionService = mock<SubscriptionService>();
|
||||
const mockPostsService = mock<PostsService>();
|
||||
const mockIntegrationService = mock<IntegrationService>();
|
||||
const mockWebHookService = mock<WebhooksService>();
|
||||
|
||||
describe('PermissionsService', () => {
|
||||
let service: PermissionsService;
|
||||
|
||||
// Initial setup before each test
|
||||
beforeEach(() => {
|
||||
process.env.STRIPE_PUBLISHABLE_KEY = 'mock_stripe_key';
|
||||
service = new PermissionsService(
|
||||
mockSubscriptionService,
|
||||
mockPostsService,
|
||||
mockIntegrationService,
|
||||
mockWebHookService
|
||||
);
|
||||
});
|
||||
|
||||
// Reusable mocks for `getPackageOptions`
|
||||
const baseSubscription = {
|
||||
id: 'mock-id',
|
||||
organizationId: 'mock-org-id',
|
||||
subscriptionTier: 'PRO' as SubscriptionTier,
|
||||
identifier: 'mock-identifier',
|
||||
cancelAt: new Date(),
|
||||
period: {} as Period,
|
||||
totalChannels: 5,
|
||||
isLifetime: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: null,
|
||||
disabled: false,
|
||||
tokenExpiration: new Date(),
|
||||
profile: 'mock-profile',
|
||||
postingTimes: '[]',
|
||||
lastPostedAt: new Date(),
|
||||
};
|
||||
|
||||
const baseOptions = {
|
||||
channel: 10,
|
||||
current: 'mock-current',
|
||||
month_price: 20,
|
||||
year_price: 200,
|
||||
posts_per_month: 100,
|
||||
team_members: true,
|
||||
community_features: true,
|
||||
featured_by_gitroom: true,
|
||||
ai: true,
|
||||
import_from_channels: true,
|
||||
image_generator: false,
|
||||
image_generation_count: 50,
|
||||
public_api: true,
|
||||
webhooks: 10,
|
||||
autoPost: true // Added the missing property
|
||||
};
|
||||
|
||||
const baseIntegration = {
|
||||
id: 'mock-integration-id',
|
||||
organizationId: 'mock-org-id',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: new Date(),
|
||||
additionalSettings: '{}',
|
||||
refreshNeeded: false,
|
||||
refreshToken: 'mock-refresh-token',
|
||||
name: 'Mock Integration',
|
||||
internalId: 'mock-internal-id',
|
||||
picture: 'mock-picture-url',
|
||||
providerIdentifier: 'mock-provider',
|
||||
token: 'mock-token',
|
||||
type: 'social',
|
||||
inBetweenSteps: false,
|
||||
disabled: false,
|
||||
tokenExpiration: new Date(),
|
||||
profile: 'mock-profile',
|
||||
postingTimes: '[]',
|
||||
lastPostedAt: new Date(),
|
||||
customInstanceDetails: 'mock-details',
|
||||
customerId: 'mock-customer-id',
|
||||
rootInternalId: 'mock-root-id',
|
||||
customer: {
|
||||
id: 'mock-customer-id',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
deletedAt: new Date(),
|
||||
name: 'Mock Customer',
|
||||
orgId: 'mock-org-id',
|
||||
},
|
||||
};
|
||||
|
||||
describe('check()', () => {
|
||||
describe('Verification Bypass (64)', () => {
|
||||
|
||||
it('Bypass for Empty List', async () => {
|
||||
// Setup: STRIPE_PUBLISHABLE_KEY exists and requestedPermission is empty
|
||||
|
||||
// Execution: call the check method with an empty list of permissions
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'ADMIN',
|
||||
[] // empty requestedPermission
|
||||
);
|
||||
|
||||
// Verification: not requested, no authorization
|
||||
expect(result.cannot(AuthorizationActions.Create, Sections.CHANNEL)).toBe(true);
|
||||
});
|
||||
|
||||
it('Bypass for Missing Stripe', async () => {
|
||||
// Setup: STRIPE_PUBLISHABLE_KEY does not exist
|
||||
process.env.STRIPE_PUBLISHABLE_KEY = undefined;
|
||||
// Necessary mock to avoid undefined filter error
|
||||
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
|
||||
{ ...baseIntegration, refreshNeeded: false }
|
||||
]);
|
||||
// Mock of getPackageOptions (even if not used due to bypass)
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: baseSubscription,
|
||||
options: baseOptions,
|
||||
});
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Read, Sections.CHANNEL],
|
||||
[AuthorizationActions.Create, Sections.AI]
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should allow all requested actions due to the absence of the Stripe key
|
||||
expect(result.can(AuthorizationActions.Read, Sections.CHANNEL)).toBe(true);
|
||||
expect(result.can(AuthorizationActions.Create, Sections.AI)).toBe(true);
|
||||
});
|
||||
|
||||
it('No Bypass', async () => {
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Read, Sections.CHANNEL],
|
||||
[AuthorizationActions.Create, Sections.AI]
|
||||
];
|
||||
// Mock of getPackageOptions to force a scenario without permissions
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: { ...baseSubscription, totalChannels: 0 },
|
||||
options: {
|
||||
...baseOptions,
|
||||
channel: 0,
|
||||
ai: false
|
||||
},
|
||||
});
|
||||
// Mock of getIntegrationsList for the channel scenario
|
||||
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
|
||||
{ ...baseIntegration, refreshNeeded: false }
|
||||
]);
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should not allow the requested actions as there is no bypass
|
||||
expect(result.can(AuthorizationActions.Read, Sections.CHANNEL)).toBe(false);
|
||||
expect(result.can(AuthorizationActions.Create, Sections.AI)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Channel Permission (82/87)', () => {
|
||||
it('All Conditions True', async () => {
|
||||
// Mock of getPackageOptions to set channel limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: { ...baseSubscription, totalChannels: 10 },
|
||||
options: { ...baseOptions, channel: 10 },
|
||||
});
|
||||
|
||||
// Mock of getIntegrationsList to set existing channels
|
||||
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
]);
|
||||
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.CHANNEL]
|
||||
];
|
||||
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should allow the requested action
|
||||
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(true);
|
||||
});
|
||||
|
||||
it('Channel With Option Limit', async () => {
|
||||
// Mock of getPackageOptions to set channel limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: { ...baseSubscription, totalChannels: 3 },
|
||||
options: { ...baseOptions, channel: 10 },
|
||||
});
|
||||
// Mock of getIntegrationsList to set existing channels
|
||||
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
]);
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.CHANNEL]
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should allow the requested action
|
||||
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(true);
|
||||
});
|
||||
|
||||
it('Channel With Subscription Limit', async () => {
|
||||
// Mock of getPackageOptions to set channel limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: { ...baseSubscription, totalChannels: 10 },
|
||||
options: { ...baseOptions, channel: 3 },
|
||||
});
|
||||
// Mock of getIntegrationsList to set existing channels
|
||||
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
]);
|
||||
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.CHANNEL]
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should allow the requested action
|
||||
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(true);
|
||||
});
|
||||
it('Channel Without Available Limits', async () => {
|
||||
// Mock of getPackageOptions to set channel limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: { ...baseSubscription, totalChannels: 3 },
|
||||
options: { ...baseOptions, channel: 3 },
|
||||
});
|
||||
// Mock of getIntegrationsList to set existing channels
|
||||
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
]);
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.CHANNEL]
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should not allow the requested action
|
||||
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(false);
|
||||
});
|
||||
it('Section Different from Channel', async () => {
|
||||
// Mock of getPackageOptions to set channel limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: { ...baseSubscription, totalChannels: 10 },
|
||||
options: { ...baseOptions, channel: 10 },
|
||||
});
|
||||
// Mock of getIntegrationsList to set existing channels
|
||||
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
{ ...baseIntegration, refreshNeeded: false },
|
||||
]);
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.AI] // Requesting permission for AI instead of CHANNEL
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should not allow the requested action in CHANNEL
|
||||
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(false);
|
||||
});
|
||||
});
|
||||
describe('Monthly Posts Permission (97/110)', () => {
|
||||
it('Posts Within Limit', async () => {
|
||||
// Mock of getPackageOptions to set post limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: baseSubscription,
|
||||
options: { ...baseOptions, posts_per_month: 100 },
|
||||
});
|
||||
// Mock of getSubscription
|
||||
jest.spyOn(mockSubscriptionService, 'getSubscription').mockResolvedValue({
|
||||
...baseSubscription,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
// Mock of countPostsFromDay to return quantity within the limit
|
||||
jest.spyOn(mockPostsService, 'countPostsFromDay').mockResolvedValue(50);
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.POSTS_PER_MONTH]
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should allow the requested action
|
||||
expect(result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)).toBe(true);
|
||||
});
|
||||
it('Posts Exceed Limit', async () => {
|
||||
// Mock of getPackageOptions to set post limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: baseSubscription,
|
||||
options: { ...baseOptions, posts_per_month: 100 },
|
||||
});
|
||||
// Mock of getSubscription
|
||||
jest.spyOn(mockSubscriptionService, 'getSubscription').mockResolvedValue({
|
||||
...baseSubscription,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
// Mock of countPostsFromDay to return quantity above the limit
|
||||
jest.spyOn(mockPostsService, 'countPostsFromDay').mockResolvedValue(150);
|
||||
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.POSTS_PER_MONTH]
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should not allow the requested action
|
||||
expect(result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)).toBe(false);
|
||||
});
|
||||
it('Section Different with Posts Within Limit', async () => {
|
||||
// Mock of getPackageOptions to set post limits
|
||||
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
|
||||
subscription: baseSubscription,
|
||||
options: { ...baseOptions, posts_per_month: 100 },
|
||||
});
|
||||
// Mock of getSubscription
|
||||
jest.spyOn(mockSubscriptionService, 'getSubscription').mockResolvedValue({
|
||||
...baseSubscription,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
// Mock of countPostsFromDay to return quantity within the limit
|
||||
jest.spyOn(mockPostsService, 'countPostsFromDay').mockResolvedValue(50);
|
||||
// List of requested permissions
|
||||
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
|
||||
[AuthorizationActions.Create, Sections.AI] // Requesting permission for AI instead of POSTS_PER_MONTH
|
||||
];
|
||||
// Execution: call the check method
|
||||
const result = await service.check(
|
||||
'mock-org-id',
|
||||
new Date(),
|
||||
'USER',
|
||||
requestedPermissions
|
||||
);
|
||||
// Verification: should not allow the requested action in POSTS_PER_MONTH
|
||||
expect(result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -46,6 +46,13 @@
|
|||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"]
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "apps/frontend/jest.config.ts"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
|
|
|
|||
|
|
@ -38,6 +38,8 @@ export const useValues = (
|
|||
criteriaMode: 'all',
|
||||
});
|
||||
|
||||
console.log(form.formState.errors);
|
||||
|
||||
const getValues = useMemo(() => {
|
||||
return () => ({ ...form.getValues(), __type: identifier });
|
||||
}, [form, integration]);
|
||||
|
|
|
|||
|
|
@ -46,11 +46,11 @@ const contentPostingMethod = [
|
|||
|
||||
const yesNo = [
|
||||
{
|
||||
value: 'true',
|
||||
value: 'yes',
|
||||
label: 'Yes',
|
||||
},
|
||||
{
|
||||
value: 'false',
|
||||
value: 'no',
|
||||
label: 'No',
|
||||
},
|
||||
];
|
||||
|
|
@ -120,7 +120,7 @@ const TikTokSettings: FC<{ values?: any }> = (props) => {
|
|||
const disclose = watch('disclose');
|
||||
const brand_organic_toggle = watch('brand_organic_toggle');
|
||||
const brand_content_toggle = watch('brand_content_toggle');
|
||||
const content_posting_method = watch('content_posting_method');
|
||||
const content_posting_method = watch('content_posting_method');
|
||||
|
||||
const isUploadMode = content_posting_method === 'UPLOAD';
|
||||
|
||||
|
|
@ -129,7 +129,8 @@ const content_posting_method = watch('content_posting_method');
|
|||
<CheckTikTokValidity picture={props?.values?.[0]?.image?.[0]?.path} />
|
||||
<Select
|
||||
label="Who can see this video?"
|
||||
disabled={isUploadMode}
|
||||
hideErrors={true}
|
||||
disabled={isUploadMode}
|
||||
{...register('privacy_level', {
|
||||
value: 'PUBLIC_TO_EVERYONE',
|
||||
})}
|
||||
|
|
@ -141,13 +142,13 @@ disabled={isUploadMode}
|
|||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<div className="text-[14px] mb-[10px] text-balance">
|
||||
<div className="text-[14px] mt-[10px] mb-[18px] text-balance">
|
||||
{`Choose upload without posting if you want to review and edit your content within TikTok's app before publishing.
|
||||
This gives you access to TikTok's built-in editing tools and lets you make final adjustments before posting.`}
|
||||
</div>
|
||||
<Select
|
||||
label="Content posting method"
|
||||
disabled={isUploadMode}
|
||||
disabled={isUploadMode}
|
||||
{...register('content_posting_method', {
|
||||
value: 'DIRECT_POST',
|
||||
})}
|
||||
|
|
@ -159,13 +160,31 @@ disabled={isUploadMode}
|
|||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<Select
|
||||
hideErrors={true}
|
||||
label="Auto add music"
|
||||
{...register('autoAddMusic', {
|
||||
value: 'no',
|
||||
})}
|
||||
>
|
||||
<option value="">Select</option>
|
||||
{yesNo.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
<div className="text-[14px] mt-[10px] mb-[24px] text-balance">
|
||||
This feature available only for photos, it will add a default music that
|
||||
you can change later.
|
||||
</div>
|
||||
<hr className="mb-[15px] border-tableBorder" />
|
||||
<div className="text-[14px] mb-[10px]">Allow User To:</div>
|
||||
<div className="flex gap-[40px]">
|
||||
<Checkbox
|
||||
variant="hollow"
|
||||
label="Duet"
|
||||
disabled={isUploadMode}
|
||||
disabled={isUploadMode}
|
||||
{...register('duet', {
|
||||
value: false,
|
||||
})}
|
||||
|
|
@ -173,7 +192,7 @@ disabled={isUploadMode}
|
|||
<Checkbox
|
||||
label="Stitch"
|
||||
variant="hollow"
|
||||
disabled={isUploadMode}
|
||||
disabled={isUploadMode}
|
||||
{...register('stitch', {
|
||||
value: false,
|
||||
})}
|
||||
|
|
@ -181,7 +200,7 @@ disabled={isUploadMode}
|
|||
<Checkbox
|
||||
label="Comments"
|
||||
variant="hollow"
|
||||
disabled={isUploadMode}
|
||||
disabled={isUploadMode}
|
||||
{...register('comment', {
|
||||
value: false,
|
||||
})}
|
||||
|
|
@ -192,7 +211,7 @@ disabled={isUploadMode}
|
|||
<Checkbox
|
||||
variant="hollow"
|
||||
label="Disclose Video Content"
|
||||
disabled={isUploadMode}
|
||||
disabled={isUploadMode}
|
||||
{...register('disclose', {
|
||||
value: false,
|
||||
})}
|
||||
|
|
@ -225,12 +244,11 @@ disabled={isUploadMode}
|
|||
third party, or both.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={clsx(!disclose && 'invisible', 'mt-[20px]')}>
|
||||
<Checkbox
|
||||
variant="hollow"
|
||||
label="Your brand"
|
||||
disabled={isUploadMode}
|
||||
disabled={isUploadMode}
|
||||
{...register('brand_organic_toggle', {
|
||||
value: false,
|
||||
})}
|
||||
|
|
@ -243,7 +261,7 @@ disabled={isUploadMode}
|
|||
<Checkbox
|
||||
variant="hollow"
|
||||
label="Branded content"
|
||||
disabled={isUploadMode}
|
||||
disabled={isUploadMode}
|
||||
{...register('brand_content_toggle', {
|
||||
value: false,
|
||||
})}
|
||||
|
|
@ -290,19 +308,22 @@ export default withProvider(
|
|||
TikTokDto,
|
||||
async (items) => {
|
||||
const [firstItems] = items;
|
||||
|
||||
if (items.length !== 1) {
|
||||
return 'Tiktok items should be one';
|
||||
}
|
||||
|
||||
if (items[0].length !== 1) {
|
||||
if (
|
||||
firstItems.length > 1 &&
|
||||
firstItems?.some((p) => p?.path?.indexOf('mp4') > -1)
|
||||
) {
|
||||
return 'Only pictures are supported when selecting multiple items';
|
||||
} else if (
|
||||
firstItems?.length !== 1 &&
|
||||
firstItems?.[0]?.path?.indexOf('mp4') > -1
|
||||
) {
|
||||
return 'You need one media';
|
||||
}
|
||||
|
||||
if (firstItems[0].path.indexOf('mp4') === -1) {
|
||||
return 'Item must be a video';
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
2200
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ import { getJestProjects } from '@nx/jest';
|
|||
|
||||
export default {
|
||||
projects: getJestProjects(),
|
||||
};
|
||||
};
|
||||
|
|
@ -80,7 +80,7 @@ export class BullMqClient extends ClientProxy {
|
|||
async dispatchEvent(packet: ReadPacket<any>): Promise<any> {
|
||||
console.log('event to dispatch: ', packet);
|
||||
const queue = this.getQueue(packet.pattern);
|
||||
if (packet.data.options.every) {
|
||||
if (packet?.data?.options?.every) {
|
||||
const { every, immediately } = packet.data.options;
|
||||
const id = packet.data.id ?? v4();
|
||||
await queue.upsertJobScheduler(
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ import { MediaService } from '@gitroom/nestjs-libraries/database/prisma/media/me
|
|||
import { ShortLinkService } from '@gitroom/nestjs-libraries/short-linking/short.link.service';
|
||||
import { WebhooksService } from '@gitroom/nestjs-libraries/database/prisma/webhooks/webhooks.service';
|
||||
import { CreateTagDto } from '@gitroom/nestjs-libraries/dtos/posts/create.tag.dto';
|
||||
import axios from 'axios';
|
||||
import sharp from 'sharp';
|
||||
import { UploadFactory } from '@gitroom/nestjs-libraries/upload/upload.factory';
|
||||
import { Readable } from 'stream';
|
||||
dayjs.extend(utc);
|
||||
|
||||
type PostWithConditionals = Post & {
|
||||
|
|
@ -33,6 +37,7 @@ type PostWithConditionals = Post & {
|
|||
|
||||
@Injectable()
|
||||
export class PostsService {
|
||||
private storage = UploadFactory.createStorage();
|
||||
constructor(
|
||||
private _postRepository: PostsRepository,
|
||||
private _workerServiceProducer: BullMqClient,
|
||||
|
|
@ -92,36 +97,90 @@ export class PostsService {
|
|||
return this._postRepository.getPosts(orgId, query);
|
||||
}
|
||||
|
||||
async updateMedia(id: string, imagesList: any[]) {
|
||||
async updateMedia(id: string, imagesList: any[], convertToJPEG = false) {
|
||||
let imageUpdateNeeded = false;
|
||||
const getImageList = (
|
||||
await Promise.all(
|
||||
imagesList.map(async (p: any) => {
|
||||
if (!p.path && p.id) {
|
||||
imageUpdateNeeded = true;
|
||||
return this._mediaService.getMediaById(p.id);
|
||||
const getImageList = await Promise.all(
|
||||
(
|
||||
await Promise.all(
|
||||
imagesList.map(async (p: any) => {
|
||||
if (!p.path && p.id) {
|
||||
imageUpdateNeeded = true;
|
||||
return this._mediaService.getMediaById(p.id);
|
||||
}
|
||||
|
||||
return p;
|
||||
})
|
||||
)
|
||||
)
|
||||
.map((m) => {
|
||||
return {
|
||||
...m,
|
||||
url:
|
||||
m.path.indexOf('http') === -1
|
||||
? process.env.FRONTEND_URL +
|
||||
'/' +
|
||||
process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +
|
||||
m.path
|
||||
: m.path,
|
||||
type: 'image',
|
||||
path:
|
||||
m.path.indexOf('http') === -1
|
||||
? process.env.UPLOAD_DIRECTORY + m.path
|
||||
: m.path,
|
||||
};
|
||||
})
|
||||
.map(async (m) => {
|
||||
if (!convertToJPEG) {
|
||||
return m;
|
||||
}
|
||||
|
||||
return p;
|
||||
if (m.path.indexOf('.png') > -1) {
|
||||
imageUpdateNeeded = true;
|
||||
const response = await axios.get(m.url, {
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
|
||||
const imageBuffer = Buffer.from(response.data);
|
||||
|
||||
// Use sharp to get the metadata of the image
|
||||
const buffer = await sharp(imageBuffer)
|
||||
.jpeg({ quality: 100 })
|
||||
.toBuffer();
|
||||
|
||||
const { path, originalname } = await this.storage.uploadFile({
|
||||
buffer,
|
||||
mimetype: 'image/jpeg',
|
||||
size: buffer.length,
|
||||
path: '',
|
||||
fieldname: '',
|
||||
destination: '',
|
||||
stream: new Readable(),
|
||||
filename: '',
|
||||
originalname: '',
|
||||
encoding: '',
|
||||
});
|
||||
|
||||
return {
|
||||
...m,
|
||||
name: originalname,
|
||||
url:
|
||||
path.indexOf('http') === -1
|
||||
? process.env.FRONTEND_URL +
|
||||
'/' +
|
||||
process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +
|
||||
path
|
||||
: path,
|
||||
type: 'image',
|
||||
path:
|
||||
path.indexOf('http') === -1
|
||||
? process.env.UPLOAD_DIRECTORY + path
|
||||
: path,
|
||||
};
|
||||
}
|
||||
|
||||
return m;
|
||||
})
|
||||
)
|
||||
).map((m) => {
|
||||
return {
|
||||
...m,
|
||||
url:
|
||||
m.path.indexOf('http') === -1
|
||||
? process.env.FRONTEND_URL +
|
||||
'/' +
|
||||
process.env.NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY +
|
||||
m.path
|
||||
: m.path,
|
||||
type: 'image',
|
||||
path:
|
||||
m.path.indexOf('http') === -1
|
||||
? process.env.UPLOAD_DIRECTORY + m.path
|
||||
: m.path,
|
||||
};
|
||||
});
|
||||
);
|
||||
|
||||
if (imageUpdateNeeded) {
|
||||
await this._postRepository.updateImages(id, JSON.stringify(getImageList));
|
||||
|
|
@ -130,7 +189,7 @@ export class PostsService {
|
|||
return getImageList;
|
||||
}
|
||||
|
||||
async getPost(orgId: string, id: string) {
|
||||
async getPost(orgId: string, id: string, convertToJPEG = false) {
|
||||
const posts = await this.getPostsRecursively(id, true, orgId, true);
|
||||
const list = {
|
||||
group: posts?.[0]?.group,
|
||||
|
|
@ -139,7 +198,8 @@ export class PostsService {
|
|||
...post,
|
||||
image: await this.updateMedia(
|
||||
post.id,
|
||||
JSON.parse(post.image || '[]')
|
||||
JSON.parse(post.image || '[]'),
|
||||
convertToJPEG,
|
||||
),
|
||||
}))
|
||||
),
|
||||
|
|
@ -361,7 +421,11 @@ export class PostsService {
|
|||
id: p.id,
|
||||
message: p.content,
|
||||
settings: JSON.parse(p.settings || '{}'),
|
||||
media: await this.updateMedia(p.id, JSON.parse(p.image || '[]')),
|
||||
media: await this.updateMedia(
|
||||
p.id,
|
||||
JSON.parse(p.image || '[]'),
|
||||
getIntegration.convertToJPEG
|
||||
),
|
||||
}))
|
||||
),
|
||||
integration
|
||||
|
|
|
|||
|
|
@ -23,15 +23,18 @@ export class TikTokDto {
|
|||
@IsBoolean()
|
||||
comment: boolean;
|
||||
|
||||
@IsIn(['yes', 'no'])
|
||||
autoAddMusic: 'yes' | 'no';
|
||||
|
||||
@IsBoolean()
|
||||
brand_content_toggle: boolean;
|
||||
|
||||
@IsBoolean()
|
||||
brand_organic_toggle: boolean;
|
||||
|
||||
@IsIn(['true'])
|
||||
@IsDefined()
|
||||
isValidVideo: boolean;
|
||||
// @IsIn(['true'])
|
||||
// @IsDefined()
|
||||
// isValidVideo: boolean;
|
||||
|
||||
@IsIn(['DIRECT_POST', 'UPLOAD'])
|
||||
@IsString()
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ export class BlueskyProvider extends SocialAbstract implements SocialProvider {
|
|||
$type: 'app.bsky.embed.images',
|
||||
images: images.map((p) => ({
|
||||
// can be an array up to 4 values
|
||||
alt: 'image', // the alt text
|
||||
// alt: 'image', // the alt text - commented this out for now until there is a way to set this from within Postiz
|
||||
image: p.data.blob,
|
||||
})),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ export interface SocialProvider
|
|||
ISocialMediaIntegration {
|
||||
identifier: string;
|
||||
refreshWait?: boolean;
|
||||
convertToJPEG?: boolean;
|
||||
isWeb3?: boolean;
|
||||
customFields?: () => Promise<
|
||||
{
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
|
|||
identifier = 'tiktok';
|
||||
name = 'Tiktok';
|
||||
isBetweenSteps = false;
|
||||
convertToJPEG = true;
|
||||
scopes = [
|
||||
'user.info.basic',
|
||||
'video.publish',
|
||||
|
|
@ -103,10 +104,10 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
|
|||
grant_type: 'authorization_code',
|
||||
code_verifier: params.codeVerifier,
|
||||
redirect_uri: `${
|
||||
process?.env?.FRONTEND_URL?.indexOf('https') === -1
|
||||
? 'https://redirectmeto.com/'
|
||||
: ''
|
||||
}${process?.env?.FRONTEND_URL}/integrations/social/tiktok`
|
||||
process?.env?.FRONTEND_URL?.indexOf('https') === -1
|
||||
? 'https://redirectmeto.com/'
|
||||
: ''
|
||||
}${process?.env?.FRONTEND_URL}/integrations/social/tiktok`,
|
||||
};
|
||||
|
||||
const { access_token, refresh_token, scope } = await (
|
||||
|
|
@ -208,23 +209,27 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
|
|||
}
|
||||
|
||||
if (status === 'FAILED') {
|
||||
throw new BadBody('titok-error-upload', JSON.stringify(post), {
|
||||
// @ts-ignore
|
||||
postDetails,
|
||||
});
|
||||
throw new BadBody(
|
||||
'titok-error-upload',
|
||||
JSON.stringify(post),
|
||||
Buffer.from(JSON.stringify(post))
|
||||
);
|
||||
}
|
||||
|
||||
await timer(3000);
|
||||
}
|
||||
}
|
||||
|
||||
private postingMethod(method: TikTokDto["content_posting_method"]): string {
|
||||
switch (method) {
|
||||
case 'UPLOAD':
|
||||
return '/inbox/video/init/';
|
||||
case 'DIRECT_POST':
|
||||
default:
|
||||
return '/video/init/';
|
||||
private postingMethod(
|
||||
method: TikTokDto['content_posting_method'],
|
||||
isPhoto: boolean
|
||||
): string {
|
||||
switch (method) {
|
||||
case 'UPLOAD':
|
||||
return isPhoto ? '/content/init/' : '/inbox/video/init/';
|
||||
case 'DIRECT_POST':
|
||||
default:
|
||||
return isPhoto ? '/content/init/' : '/video/init/';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,11 +240,15 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
|
|||
integration: Integration
|
||||
): Promise<PostResponse[]> {
|
||||
const [firstPost, ...comments] = postDetails;
|
||||
|
||||
const {
|
||||
data: { publish_id },
|
||||
} = await (
|
||||
await this.fetch(
|
||||
`https://open.tiktokapis.com/v2/post/publish${this.postingMethod(firstPost.settings.content_posting_method)}`,
|
||||
`https://open.tiktokapis.com/v2/post/publish${this.postingMethod(
|
||||
firstPost.settings.content_posting_method,
|
||||
(firstPost?.media?.[0]?.url?.indexOf('mp4') || -1) === -1
|
||||
)}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
|
@ -247,21 +256,44 @@ export class TiktokProvider extends SocialAbstract implements SocialProvider {
|
|||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
...(firstPost.settings.content_posting_method === 'DIRECT_POST' ? {
|
||||
post_info: {
|
||||
title: firstPost.message,
|
||||
privacy_level: firstPost.settings.privacy_level,
|
||||
disable_duet: !firstPost.settings.duet,
|
||||
disable_comment: !firstPost.settings.comment,
|
||||
disable_stitch: !firstPost.settings.stitch,
|
||||
brand_content_toggle: firstPost.settings.brand_content_toggle,
|
||||
brand_organic_toggle: firstPost.settings.brand_organic_toggle,
|
||||
}
|
||||
} : {}),
|
||||
source_info: {
|
||||
source: 'PULL_FROM_URL',
|
||||
video_url: firstPost?.media?.[0]?.url!,
|
||||
},
|
||||
...(firstPost.settings.content_posting_method === 'DIRECT_POST'
|
||||
? {
|
||||
post_info: {
|
||||
title: firstPost.message,
|
||||
privacy_level: firstPost.settings.privacy_level,
|
||||
disable_duet: !firstPost.settings.duet,
|
||||
disable_comment: !firstPost.settings.comment,
|
||||
disable_stitch: !firstPost.settings.stitch,
|
||||
brand_content_toggle:
|
||||
firstPost.settings.brand_content_toggle,
|
||||
brand_organic_toggle:
|
||||
firstPost.settings.brand_organic_toggle,
|
||||
...((firstPost?.media?.[0]?.url?.indexOf('mp4') || -1) ===
|
||||
-1
|
||||
? {
|
||||
auto_add_music:
|
||||
firstPost.settings.autoAddMusic === 'yes',
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...((firstPost?.media?.[0]?.url?.indexOf('mp4') || -1) > -1
|
||||
? {
|
||||
source_info: {
|
||||
source: 'PULL_FROM_URL',
|
||||
video_url: firstPost?.media?.[0]?.url!,
|
||||
},
|
||||
}
|
||||
: {
|
||||
source_info: {
|
||||
source: 'PULL_FROM_URL',
|
||||
photo_cover_index: 1,
|
||||
photo_images: firstPost.media?.map((p) => p.url),
|
||||
},
|
||||
post_mode: 'DIRECT_POST',
|
||||
media_type: 'PHOTO',
|
||||
}),
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,30 @@
|
|||
import { Redis } from 'ioredis';
|
||||
|
||||
export const ioRedis = new Redis(process.env.REDIS_URL!, {
|
||||
maxRetriesPerRequest: null,
|
||||
connectTimeout: 10000
|
||||
});
|
||||
// Create a mock Redis implementation for testing environments
|
||||
class MockRedis {
|
||||
private data: Map<string, any> = new Map();
|
||||
|
||||
async get(key: string) {
|
||||
return this.data.get(key);
|
||||
}
|
||||
|
||||
async set(key: string, value: any) {
|
||||
this.data.set(key, value);
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
async del(key: string) {
|
||||
this.data.delete(key);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Add other Redis methods as needed for your tests
|
||||
}
|
||||
|
||||
// Use real Redis if REDIS_URL is defined, otherwise use MockRedis
|
||||
export const ioRedis = process.env.REDIS_URL
|
||||
? new Redis(process.env.REDIS_URL, {
|
||||
maxRetriesPerRequest: null,
|
||||
connectTimeout: 10000
|
||||
})
|
||||
: (new MockRedis() as unknown as Redis); // Type cast to Redis to maintain interface compatibility
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
"@nx/webpack": "19.7.2",
|
||||
"@nx/workspace": "19.7.2",
|
||||
"@postiz/wallets": "^0.0.1",
|
||||
"@prisma/client": "^6.4.1",
|
||||
"@prisma/client": "^6.5.0",
|
||||
"@solana/wallet-adapter-react": "^0.15.35",
|
||||
"@solana/wallet-adapter-react-ui": "^0.9.35",
|
||||
"@swc/helpers": "0.5.13",
|
||||
|
|
@ -198,10 +198,12 @@
|
|||
"jest": "29.7.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jest-environment-node": "^29.4.1",
|
||||
"jest-junit": "^16.0.0",
|
||||
"jest-mock-extended": "^4.0.0-beta1",
|
||||
"jsdom": "~22.1.0",
|
||||
"postcss": "8.4.38",
|
||||
"prettier": "^2.6.2",
|
||||
"prisma": "^5.8.1",
|
||||
"prisma": "^6.5.0",
|
||||
"react-refresh": "^0.10.0",
|
||||
"sass": "1.62.1",
|
||||
"ts-jest": "^29.1.0",
|
||||
|
|
@ -11335,9 +11337,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@prisma/client": {
|
||||
"version": "6.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.4.1.tgz",
|
||||
"integrity": "sha512-A7Mwx44+GVZVexT5e2GF/WcKkEkNNKbgr059xpr5mn+oUm2ZW1svhe+0TRNBwCdzhfIZ+q23jEgsNPvKD9u+6g==",
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.5.0.tgz",
|
||||
"integrity": "sha512-M6w1Ql/BeiGoZmhMdAZUXHu5sz5HubyVcKukbLs3l0ELcQb8hTUJxtGEChhv4SVJ0QJlwtLnwOLgIRQhpsm9dw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
|
|
@ -11356,49 +11358,65 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/config": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.5.0.tgz",
|
||||
"integrity": "sha512-sOH/2Go9Zer67DNFLZk6pYOHj+rumSb0VILgltkoxOjYnlLqUpHPAN826vnx8HigqnOCxj9LRhT6U7uLiIIWgw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"esbuild": ">=0.12 <1",
|
||||
"esbuild-register": "3.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/debug": {
|
||||
"version": "5.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz",
|
||||
"integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==",
|
||||
"devOptional": true
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.5.0.tgz",
|
||||
"integrity": "sha512-fc/nusYBlJMzDmDepdUtH9aBsJrda2JNErP9AzuHbgUEQY0/9zQYZdNlXmKoIWENtio+qarPNe/+DQtrX5kMcQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/engines": {
|
||||
"version": "5.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz",
|
||||
"integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==",
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.5.0.tgz",
|
||||
"integrity": "sha512-FVPQYHgOllJklN9DUyujXvh3hFJCY0NX86sDmBErLvoZjy2OXGiZ5FNf3J/C4/RZZmCypZBYpBKEhx7b7rEsdw==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.22.0",
|
||||
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
||||
"@prisma/fetch-engine": "5.22.0",
|
||||
"@prisma/get-platform": "5.22.0"
|
||||
"@prisma/debug": "6.5.0",
|
||||
"@prisma/engines-version": "6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60",
|
||||
"@prisma/fetch-engine": "6.5.0",
|
||||
"@prisma/get-platform": "6.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/engines-version": {
|
||||
"version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz",
|
||||
"integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==",
|
||||
"devOptional": true
|
||||
"version": "6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60.tgz",
|
||||
"integrity": "sha512-iK3EmiVGFDCmXjSpdsKGNqy9hOdLnvYBrJB61far/oP03hlIxrb04OWmDjNTwtmZ3UZdA5MCvI+f+3k2jPTflQ==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@prisma/fetch-engine": {
|
||||
"version": "5.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz",
|
||||
"integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==",
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.5.0.tgz",
|
||||
"integrity": "sha512-3LhYA+FXP6pqY8FLHCjewyE8pGXXJ7BxZw2rhPq+CZAhvflVzq4K8Qly3OrmOkn6wGlz79nyLQdknyCG2HBTuA==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.22.0",
|
||||
"@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2",
|
||||
"@prisma/get-platform": "5.22.0"
|
||||
"@prisma/debug": "6.5.0",
|
||||
"@prisma/engines-version": "6.5.0-73.173f8d54f8d52e692c7e27e72a88314ec7aeff60",
|
||||
"@prisma/get-platform": "6.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@prisma/get-platform": {
|
||||
"version": "5.22.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz",
|
||||
"integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==",
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.5.0.tgz",
|
||||
"integrity": "sha512-xYcvyJwNMg2eDptBYFqFLUCfgi+wZLcj6HDMsj0Qw0irvauG4IKmkbywnqwok0B+k+W+p+jThM2DKTSmoPCkzw==",
|
||||
"devOptional": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/debug": "5.22.0"
|
||||
"@prisma/debug": "6.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@project-serum/sol-wallet-adapter": {
|
||||
|
|
@ -24666,6 +24684,19 @@
|
|||
"@esbuild/win32-x64": "0.21.5"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild-register": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz",
|
||||
"integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"esbuild": ">=0.12 <1"
|
||||
}
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
|
|
@ -30651,6 +30682,45 @@
|
|||
"fsevents": "^2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-junit": {
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-16.0.0.tgz",
|
||||
"integrity": "sha512-A94mmw6NfJab4Fg/BlvVOUXzXgF0XIH6EmTgJ5NDPp4xoKq0Kr7sErb+4Xs9nZvu58pJojz5RFGpqnZYJTrRfQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"mkdirp": "^1.0.4",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"uuid": "^8.3.2",
|
||||
"xml": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-junit/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-junit/node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-leak-detector": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
|
||||
|
|
@ -30763,6 +30833,21 @@
|
|||
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-mock-extended": {
|
||||
"version": "4.0.0-beta1",
|
||||
"resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-4.0.0-beta1.tgz",
|
||||
"integrity": "sha512-MYcI0wQu3ceNhqKoqAJOdEfsVMamAFqDTjoLN5Y45PAG3iIm4WGnhOu0wpMjlWCexVPO71PMoNir9QrGXrnIlw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ts-essentials": "^10.0.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@jest/globals": "^28.0.0 || ^29.0.0",
|
||||
"jest": "^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0 || ^28.0.0 || ^29.0.0",
|
||||
"typescript": "^3.0.0 || ^4.0.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-pnp-resolver": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
|
||||
|
|
@ -39832,22 +39917,32 @@
|
|||
"dev": true
|
||||
},
|
||||
"node_modules/prisma": {
|
||||
"version": "5.22.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz",
|
||||
"integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==",
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/prisma/-/prisma-6.5.0.tgz",
|
||||
"integrity": "sha512-yUGXmWqv5F4PByMSNbYFxke/WbnyTLjnJ5bKr8fLkcnY7U5rU9rUTh/+Fja+gOrRxEgtCbCtca94IeITj4j/pg==",
|
||||
"devOptional": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@prisma/engines": "5.22.0"
|
||||
"@prisma/config": "6.5.0",
|
||||
"@prisma/engines": "6.5.0"
|
||||
},
|
||||
"bin": {
|
||||
"prisma": "build/index.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.13"
|
||||
"node": ">=18.18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=5.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/prismjs": {
|
||||
|
|
@ -45762,6 +45857,21 @@
|
|||
"node": ">=6.10"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-essentials": {
|
||||
"version": "10.0.4",
|
||||
"resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-10.0.4.tgz",
|
||||
"integrity": "sha512-lwYdz28+S4nicm+jFi6V58LaAIpxzhg9rLdgNC1VsdP/xiFBseGhF1M/shwCk6zMmwahBZdXcl34LVHrEang3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.5.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ts-interface-checker": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||
|
|
@ -48277,6 +48387,13 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/xml": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
||||
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/xml-name-validator": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
|
||||
|
|
|
|||
13
package.json
13
package.json
|
|
@ -29,7 +29,8 @@
|
|||
"prisma-reset": "cd ./libraries/nestjs-libraries/src/database/prisma && npx prisma db push --force-reset && npx prisma db push",
|
||||
"docker-build": "./var/docker/docker-build.sh",
|
||||
"docker-create": "./var/docker/docker-create.sh",
|
||||
"postinstall": "npm run update-plugins && npm run prisma-generate"
|
||||
"postinstall": "npm run update-plugins && npm run prisma-generate",
|
||||
"test": "jest --coverage --detectOpenHandles --reporters=default --reporters=jest-junit"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
|
@ -71,7 +72,7 @@
|
|||
"@nx/webpack": "19.7.2",
|
||||
"@nx/workspace": "19.7.2",
|
||||
"@postiz/wallets": "^0.0.1",
|
||||
"@prisma/client": "^6.4.1",
|
||||
"@prisma/client": "^6.5.0",
|
||||
"@solana/wallet-adapter-react": "^0.15.35",
|
||||
"@solana/wallet-adapter-react-ui": "^0.9.35",
|
||||
"@swc/helpers": "0.5.13",
|
||||
|
|
@ -221,10 +222,12 @@
|
|||
"jest": "29.7.0",
|
||||
"jest-environment-jsdom": "29.7.0",
|
||||
"jest-environment-node": "^29.4.1",
|
||||
"jest-junit": "^16.0.0",
|
||||
"jest-mock-extended": "^4.0.0-beta1",
|
||||
"jsdom": "~22.1.0",
|
||||
"postcss": "8.4.38",
|
||||
"prettier": "^2.6.2",
|
||||
"prisma": "^5.8.1",
|
||||
"prisma": "^6.5.0",
|
||||
"react-refresh": "^0.10.0",
|
||||
"sass": "1.62.1",
|
||||
"ts-jest": "^29.1.0",
|
||||
|
|
@ -235,5 +238,9 @@
|
|||
},
|
||||
"volta": {
|
||||
"node": "20.17.0"
|
||||
},
|
||||
"jest-junit": {
|
||||
"outputDirectory": "./reports",
|
||||
"outputName": "junit.xml"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuites name="jest tests" tests="11" failures="0" errors="0" time="31.615">
|
||||
<testsuite name="PermissionsService" errors="0" failures="0" skipped="0" timestamp="2025-03-24T05:53:33" time="31.496" tests="11">
|
||||
<testcase classname="PermissionsService check() Verification Bypass (64) Bypass for Empty List" name="PermissionsService check() Verification Bypass (64) Bypass for Empty List" time="0.01">
|
||||
</testcase>
|
||||
<testcase classname="PermissionsService check() Verification Bypass (64) Bypass for Missing Stripe" name="PermissionsService check() Verification Bypass (64) Bypass for Missing Stripe" time="0.003">
|
||||
</testcase>
|
||||
<testcase classname="PermissionsService check() Verification Bypass (64) No Bypass" name="PermissionsService check() Verification Bypass (64) No Bypass" time="0.002">
|
||||
</testcase>
|
||||
<testcase classname="PermissionsService check() Channel Permission (82/87) All Conditions True" name="PermissionsService check() Channel Permission (82/87) All Conditions True" time="0.003">
|
||||
</testcase>
|
||||
<testcase classname="PermissionsService check() Channel Permission (82/87) Channel With Option Limit" name="PermissionsService check() Channel Permission (82/87) Channel With Option Limit" time="0.003">
|
||||
</testcase>
|
||||
<testcase classname="PermissionsService check() Channel Permission (82/87) Channel With Subscription Limit" name="PermissionsService check() Channel Permission (82/87) Channel With Subscription Limit" time="0.002">
|
||||
</testcase>
|
||||
<testcase classname="PermissionsService check() Channel Permission (82/87) Channel Without Available Limits" name="PermissionsService check() Channel Permission (82/87) Channel Without Available Limits" time="0.003">
|
||||
</testcase>
|
||||
<testcase classname="PermissionsService check() Channel Permission (82/87) Section Different from Channel" name="PermissionsService check() Channel Permission (82/87) Section Different from Channel" time="0.003">
|
||||
</testcase>
|
||||
<testcase classname="PermissionsService check() Monthly Posts Permission (97/110) Posts Within Limit" name="PermissionsService check() Monthly Posts Permission (97/110) Posts Within Limit" time="0.008">
|
||||
</testcase>
|
||||
<testcase classname="PermissionsService check() Monthly Posts Permission (97/110) Posts Exceed Limit" name="PermissionsService check() Monthly Posts Permission (97/110) Posts Exceed Limit" time="0.003">
|
||||
</testcase>
|
||||
<testcase classname="PermissionsService check() Monthly Posts Permission (97/110) Section Different with Posts Within Limit" name="PermissionsService check() Monthly Posts Permission (97/110) Section Different with Posts Within Limit" time="0.003">
|
||||
</testcase>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
|
@ -0,0 +1 @@
|
|||
v1.38.1
|
||||
Loading…
Reference in New Issue