feat: translation

This commit is contained in:
Nevo David 2025-05-31 21:44:16 +07:00
parent 92662e1624
commit ac649424c6
420 changed files with 53000 additions and 23744 deletions

View File

@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within

View File

@ -21,6 +21,7 @@ As a general rule;
## Types of Contributions
Contributions can include:
- **Code improvements:** Fixing bugs or adding new features.
- **Documentation updates:** Enhancing clarity or adding missing information.
- **Feature requests:** Suggesting new capabilities or integrations.
@ -39,17 +40,15 @@ This project follows a Fork/Feature Branch/Pull Request model. If you're not fam
```bash
git checkout -b feature/your-feature-name
```
6. **Make your changes**: Implement the changes you wish to contribute.
7. **Push your changes**: Upload your changes to your fork.
4. **Make your changes**: Implement the changes you wish to contribute.
5. **Push your changes**: Upload your changes to your fork.
```bash
git push -u origin feature/your-feature-name
```
9. **Create a pull request**: Propose your changes **to the main branch**.
6. **Create a pull request**: Propose your changes **to the main branch**.
# Need Help?
Again, do check the [developer guide](https://docs.postiz.com/developer-guide). Much of what you probably need to know is in there.
If you encounter any issues, please visit our [support page](https://docs.postiz.com/support) or check the community forums. Your contributions help make Postiz better!

View File

@ -13,7 +13,6 @@
</a>
</p>
<p align="center">
<a href="https://opensource.org/licenses/Apache-2.0">
<img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License">
@ -28,7 +27,6 @@
Postiz offers everything you need to manage your social media posts,<br />build an audience, capture leads, and grow your business.
</div>
<div class="flex" align="center">
<br />
<img alt="Instagram" src="https://postiz.com/svgs/socials/Instagram.svg" width="32">
@ -77,7 +75,7 @@
## ✨ Features
| ![Image 1](https://github.com/user-attachments/assets/a27ee220-beb7-4c7e-8c1b-2c44301f82ef) | ![Image 2](https://github.com/user-attachments/assets/eb5f5f15-ed90-47fc-811c-03ccba6fa8a2) |
|--------------------------------|--------------------------------|
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| ![Image 3](https://github.com/user-attachments/assets/d51786ee-ddd8-4ef8-8138-5192e9cfe7c3) | ![Image 4](https://github.com/user-attachments/assets/91f83c89-22f6-43d6-b7aa-d2d3378289fb) |
# Intro
@ -98,9 +96,11 @@
- Resend (email notifications)
## Quick Start
To have the project up and running, please follow the [Quick Start Guide](https://docs.postiz.com/quickstart)
## Invest in the Postiz Coin :)
DMsTbeCfX1crgAse5tver98KAMarPWeP3d6U3Gmmpump
# License
@ -112,5 +112,3 @@ This repository's source code is available under the [AGPL-3.0 license](LICENSE)
<p align="center">
<a href="https://www.g2.com/products/postiz/take_survey" target="blank"><img alt="g2" src="https://github.com/user-attachments/assets/892cb74c-0b49-4589-b2f5-fbdbf7a98f66" /></a>
</p>

View File

@ -8,14 +8,14 @@ The Postiz app is committed to ensuring the security and integrity of our users'
If you discover a security vulnerability in the Postiz app, please report it to us privately via email to one of the maintainers:
* @nevo-david
* @egelhaus ([email](mailto:gelhausenno@outlook.de))
- @nevo-david
- @egelhaus ([email](mailto:gelhausenno@outlook.de))
When reporting a security vulnerability, please provide as much detail as possible, including:
* A clear description of the vulnerability
* Steps to reproduce the vulnerability
* Any relevant code or configuration files
- A clear description of the vulnerability
- Steps to reproduce the vulnerability
- Any relevant code or configuration files
## Supported Versions
@ -31,10 +31,10 @@ We will not publicly disclose security vulnerabilities until a patch or fix is a
We take security vulnerabilities seriously and will respond promptly to reports of vulnerabilities. Our response process includes:
* Investigating the report and verifying the vulnerability.
* Developing a patch or fix for the vulnerability.
* Releasing the patch or fix as soon as possible.
* Notifying users of the vulnerability and the patch or fix.
- Investigating the report and verifying the vulnerability.
- Developing a patch or fix for the vulnerability.
- Releasing the patch or fix as soon as possible.
- Notifying users of the vulnerability and the patch or fix.
## Template Attribution

View File

@ -11,4 +11,4 @@
"keywords": [],
"author": "",
"license": "ISC"
}
}

View File

@ -74,7 +74,7 @@ const authenticatedController = [
TrackService,
ShortLinkService,
Nowpayments,
McpService
McpService,
],
get exports() {
return [...this.imports, ...this.providers];

View File

@ -21,7 +21,7 @@ import { IntegrationManager } from '@gitroom/nestjs-libraries/integrations/integ
export class AnalyticsController {
constructor(
private _starsService: StarsService,
private _integrationService: IntegrationService,
private _integrationService: IntegrationService
) {}
@Get('/')
async getStars(@GetOrgFromRequest() org: Organization) {

View File

@ -13,9 +13,12 @@ export class CopilotController {
constructor(private _subscriptionService: SubscriptionService) {}
@Post('/chat')
chat(@Req() req: Request, @Res() res: Response) {
if (process.env.OPENAI_API_KEY === undefined || process.env.OPENAI_API_KEY === '') {
if (
process.env.OPENAI_API_KEY === undefined ||
process.env.OPENAI_API_KEY === ''
) {
Logger.warn('OpenAI API key not set, chat functionality will not work');
return
return;
}
const copilotRuntimeHandler = copilotRuntimeNestEndpoint({

View File

@ -87,35 +87,34 @@ export class IntegrationsController {
async getIntegrationList(@GetOrgFromRequest() org: Organization) {
return {
integrations: await Promise.all(
(await this._integrationService.getIntegrationsList(org.id)).map(
async (p) => {
const findIntegration =
this._integrationManager.getSocialIntegration(
p.providerIdentifier
);
return {
name: p.name,
id: p.id,
internalId: p.internalId,
disabled: p.disabled,
picture: p.picture || '/no-picture.jpg',
identifier: p.providerIdentifier,
inBetweenSteps: p.inBetweenSteps,
refreshNeeded: p.refreshNeeded,
isCustomFields: !!findIntegration.customFields,
...(findIntegration.customFields
? { customFields: await findIntegration.customFields() }
: {}),
display: p.profile,
type: p.type,
time: JSON.parse(p.postingTimes),
changeProfilePicture: !!findIntegration?.changeProfilePicture,
changeNickName: !!findIntegration?.changeNickname,
customer: p.customer,
additionalSettings: p.additionalSettings || '[]',
};
}
)
(
await this._integrationService.getIntegrationsList(org.id)
).map(async (p) => {
const findIntegration = this._integrationManager.getSocialIntegration(
p.providerIdentifier
);
return {
name: p.name,
id: p.id,
internalId: p.internalId,
disabled: p.disabled,
picture: p.picture || '/no-picture.jpg',
identifier: p.providerIdentifier,
inBetweenSteps: p.inBetweenSteps,
refreshNeeded: p.refreshNeeded,
isCustomFields: !!findIntegration.customFields,
...(findIntegration.customFields
? { customFields: await findIntegration.customFields() }
: {}),
display: p.profile,
type: p.type,
time: JSON.parse(p.postingTimes),
changeProfilePicture: !!findIntegration?.changeProfilePicture,
changeNickName: !!findIntegration?.changeNickname,
customer: p.customer,
additionalSettings: p.additionalSettings || '[]',
};
})
),
};
}

View File

@ -1,4 +1,11 @@
import { Body, Controller, HttpException, Param, Post, Sse } from '@nestjs/common';
import {
Body,
Controller,
HttpException,
Param,
Post,
Sse,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { McpService } from '@gitroom/nestjs-libraries/mcp/mcp.service';
import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';

View File

@ -26,7 +26,12 @@ export class MessagesController {
@Param('groupId') groupId: string,
@Param('page') page: string
) {
return this._messagesService.getMessages(user.id, organization.id, groupId, +page);
return this._messagesService.getMessages(
user.id,
organization.id,
groupId,
+page
);
}
@Post('/:groupId')
createMessage(
@ -35,6 +40,11 @@ export class MessagesController {
@Param('groupId') groupId: string,
@Body() message: AddMessageDto
) {
return this._messagesService.createMessage(user.id, organization.id, groupId, message);
return this._messagesService.createMessage(
user.id,
organization.id,
groupId,
message
);
}
}

View File

@ -3,7 +3,7 @@ import { GetUserFromRequest } from '@gitroom/nestjs-libraries/user/user.from.req
import { Organization, User } from '@prisma/client';
import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';
import { NotificationService } from '@gitroom/nestjs-libraries/database/prisma/notifications/notification.service';
import {ApiTags} from "@nestjs/swagger";
import { ApiTags } from '@nestjs/swagger';
@ApiTags('Notifications')
@Controller('/notifications')

View File

@ -8,8 +8,8 @@ import {
Sections,
} from '@gitroom/backend/services/auth/permissions/permissions.service';
import { OrganizationService } from '@gitroom/nestjs-libraries/database/prisma/organizations/organization.service';
import {AddTeamMemberDto} from "@gitroom/nestjs-libraries/dtos/settings/add.team.member.dto";
import {ApiTags} from "@nestjs/swagger";
import { AddTeamMemberDto } from '@gitroom/nestjs-libraries/dtos/settings/add.team.member.dto';
import { ApiTags } from '@nestjs/swagger';
@ApiTags('Settings')
@Controller('/settings')
@ -105,25 +105,34 @@ export class SettingsController {
}
@Get('/team')
@CheckPolicies([AuthorizationActions.Create, Sections.TEAM_MEMBERS], [AuthorizationActions.Create, Sections.ADMIN])
@CheckPolicies(
[AuthorizationActions.Create, Sections.TEAM_MEMBERS],
[AuthorizationActions.Create, Sections.ADMIN]
)
async getTeam(@GetOrgFromRequest() org: Organization) {
return this._organizationService.getTeam(org.id);
}
@Post('/team')
@CheckPolicies([AuthorizationActions.Create, Sections.TEAM_MEMBERS], [AuthorizationActions.Create, Sections.ADMIN])
@CheckPolicies(
[AuthorizationActions.Create, Sections.TEAM_MEMBERS],
[AuthorizationActions.Create, Sections.ADMIN]
)
async inviteTeamMember(
@GetOrgFromRequest() org: Organization,
@Body() body: AddTeamMemberDto,
@GetOrgFromRequest() org: Organization,
@Body() body: AddTeamMemberDto
) {
return this._organizationService.inviteTeamMember(org.id, body);
}
@Delete('/team/:id')
@CheckPolicies([AuthorizationActions.Create, Sections.TEAM_MEMBERS], [AuthorizationActions.Create, Sections.ADMIN])
@CheckPolicies(
[AuthorizationActions.Create, Sections.TEAM_MEMBERS],
[AuthorizationActions.Create, Sections.ADMIN]
)
deleteTeamMember(
@GetOrgFromRequest() org: Organization,
@Param('id') id: string
@GetOrgFromRequest() org: Organization,
@Param('id') id: string
) {
return this._organizationService.deleteTeamMember(org, id);
}

View File

@ -62,8 +62,7 @@ export class UsersController {
// @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'),
tier: organization?.subscription?.subscriptionTier || (!process.env.STRIPE_PUBLISHABLE_KEY ? 'ULTIMATE' : 'FREE'),
// @ts-ignore
role: organization?.users[0]?.role,
// @ts-ignore
@ -72,7 +71,7 @@ export class UsersController {
impersonate: !!impersonate,
allowTrial: organization?.allowTrial,
// @ts-ignore
publicApi: organization?.users[0]?.role === 'SUPERADMIN' || organization?.users[0]?.role === 'ADMIN' ? organization?.apiKey : '',
publicApi: organization?.users[0]?.role === 'SUPERADMIN' || organization?.users[0]?.role === 'ADMIN' ? organization?.apiKey : '',
};
}

View File

@ -11,16 +11,10 @@ import { CodesService } from '@gitroom/nestjs-libraries/services/codes.service';
import { PublicIntegrationsController } from '@gitroom/backend/public-api/routes/v1/public.integrations.controller';
import { PublicAuthMiddleware } from '@gitroom/backend/services/auth/public.auth.middleware';
const authenticatedController = [
PublicIntegrationsController
];
const authenticatedController = [PublicIntegrationsController];
@Module({
imports: [
UploadModule,
],
controllers: [
...authenticatedController,
],
imports: [UploadModule],
controllers: [...authenticatedController],
providers: [
AuthService,
StripeService,

View File

@ -1,5 +1,14 @@
import {
Body, Controller, Delete, Get, HttpException, Param, Post, Query, UploadedFile, UseInterceptors
Body,
Controller,
Delete,
Get,
HttpException,
Param,
Post,
Query,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { GetOrgFromRequest } from '@gitroom/nestjs-libraries/user/org.from.request';

View File

@ -1,6 +1,10 @@
import {SetMetadata} from "@nestjs/common";
import {AuthorizationActions, Sections} from "@gitroom/backend/services/auth/permissions/permissions.service";
import { SetMetadata } from '@nestjs/common';
import {
AuthorizationActions,
Sections,
} from '@gitroom/backend/services/auth/permissions/permissions.service';
export const CHECK_POLICIES_KEY = 'check_policy';
export type AbilityPolicy = [AuthorizationActions, Sections];
export const CheckPolicies = (...handlers: AbilityPolicy[]) => SetMetadata(CHECK_POLICIES_KEY, handlers);
export const CheckPolicies = (...handlers: AbilityPolicy[]) =>
SetMetadata(CHECK_POLICIES_KEY, handlers);

View File

@ -1,29 +1,37 @@
import {CanActivate, ExecutionContext, Injectable} from "@nestjs/common";
import {Reflector} from "@nestjs/core";
import {AppAbility, PermissionsService} from "@gitroom/backend/services/auth/permissions/permissions.service";
import {AbilityPolicy, CHECK_POLICIES_KEY} from "@gitroom/backend/services/auth/permissions/permissions.ability";
import {Organization} from "@prisma/client";
import {SubscriptionException} from "@gitroom/backend/services/auth/permissions/subscription.exception";
import {Request} from "express";
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import {
AppAbility,
PermissionsService,
} from '@gitroom/backend/services/auth/permissions/permissions.service';
import {
AbilityPolicy,
CHECK_POLICIES_KEY,
} from '@gitroom/backend/services/auth/permissions/permissions.ability';
import { Organization } from '@prisma/client';
import { SubscriptionException } from '@gitroom/backend/services/auth/permissions/subscription.exception';
import { Request } from 'express';
@Injectable()
export class PoliciesGuard implements CanActivate {
constructor(
private _reflector: Reflector,
private _authorizationService: PermissionsService,
private _authorizationService: PermissionsService
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request: Request = context.switchToHttp().getRequest();
if (request.path.indexOf('/auth') > -1 || request.path.indexOf('/stripe') > -1) {
if (
request.path.indexOf('/auth') > -1 ||
request.path.indexOf('/stripe') > -1
) {
return true;
}
const policyHandlers =
this._reflector.get<AbilityPolicy[]>(
CHECK_POLICIES_KEY,
context.getHandler(),
context.getHandler()
) || [];
if (!policyHandlers || !policyHandlers.length) {
@ -32,19 +40,19 @@ export class PoliciesGuard implements CanActivate {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
const { org } : {org: Organization} = request;
const { org }: { org: Organization } = request;
// @ts-ignore
const ability = await this._authorizationService.check(org.id, org.createdAt, org.users[0].role, policyHandlers);
const item = policyHandlers.find((handler) =>
!this.execPolicyHandler(handler, ability),
const item = policyHandlers.find(
(handler) => !this.execPolicyHandler(handler, ability)
);
if (item) {
throw new SubscriptionException({
section: item[1],
action: item[0]
action: item[0],
});
}

View File

@ -62,7 +62,7 @@ describe('PermissionsService', () => {
image_generation_count: 50,
public_api: true,
webhooks: 10,
autoPost: true // Added the missing property
autoPost: true, // Added the missing property
};
const baseIntegration = {
@ -101,7 +101,6 @@ describe('PermissionsService', () => {
describe('check()', () => {
describe('Verification Bypass (64)', () => {
it('Bypass for Empty List', async () => {
// Setup: STRIPE_PUBLISHABLE_KEY exists and requestedPermission is empty
@ -114,16 +113,18 @@ describe('PermissionsService', () => {
);
// Verification: not requested, no authorization
expect(result.cannot(AuthorizationActions.Create, Sections.CHANNEL)).toBe(true);
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 }
]);
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,
@ -132,7 +133,7 @@ describe('PermissionsService', () => {
// List of requested permissions
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
[AuthorizationActions.Read, Sections.CHANNEL],
[AuthorizationActions.Create, Sections.AI]
[AuthorizationActions.Create, Sections.AI],
];
// Execution: call the check method
const result = await service.check(
@ -142,7 +143,9 @@ describe('PermissionsService', () => {
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.Read, Sections.CHANNEL)).toBe(
true
);
expect(result.can(AuthorizationActions.Create, Sections.AI)).toBe(true);
});
@ -150,7 +153,7 @@ describe('PermissionsService', () => {
// List of requested permissions
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
[AuthorizationActions.Read, Sections.CHANNEL],
[AuthorizationActions.Create, Sections.AI]
[AuthorizationActions.Create, Sections.AI],
];
// Mock of getPackageOptions to force a scenario without permissions
jest.spyOn(service, 'getPackageOptions').mockResolvedValue({
@ -158,13 +161,13 @@ describe('PermissionsService', () => {
options: {
...baseOptions,
channel: 0,
ai: false
ai: false,
},
});
// Mock of getIntegrationsList for the channel scenario
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
{ ...baseIntegration, refreshNeeded: false }
]);
jest
.spyOn(mockIntegrationService, 'getIntegrationsList')
.mockResolvedValue([{ ...baseIntegration, refreshNeeded: false }]);
// Execution: call the check method
const result = await service.check(
'mock-org-id',
@ -173,8 +176,12 @@ describe('PermissionsService', () => {
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);
expect(result.can(AuthorizationActions.Read, Sections.CHANNEL)).toBe(
false
);
expect(result.can(AuthorizationActions.Create, Sections.AI)).toBe(
false
);
});
});
@ -187,15 +194,17 @@ describe('PermissionsService', () => {
});
// Mock of getIntegrationsList to set existing channels
jest.spyOn(mockIntegrationService, 'getIntegrationsList').mockResolvedValue([
{ ...baseIntegration, refreshNeeded: false },
{ ...baseIntegration, refreshNeeded: false },
{ ...baseIntegration, refreshNeeded: false },
]);
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]
[AuthorizationActions.Create, Sections.CHANNEL],
];
// Execution: call the check method
@ -206,7 +215,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should allow the requested action
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(true);
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
true
);
});
it('Channel With Option Limit', async () => {
@ -216,14 +227,16 @@ describe('PermissionsService', () => {
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 },
]);
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]
[AuthorizationActions.Create, Sections.CHANNEL],
];
// Execution: call the check method
const result = await service.check(
@ -233,7 +246,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should allow the requested action
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(true);
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
true
);
});
it('Channel With Subscription Limit', async () => {
@ -243,15 +258,17 @@ describe('PermissionsService', () => {
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 },
]);
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]
[AuthorizationActions.Create, Sections.CHANNEL],
];
// Execution: call the check method
const result = await service.check(
@ -261,7 +278,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should allow the requested action
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(true);
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
true
);
});
it('Channel Without Available Limits', async () => {
// Mock of getPackageOptions to set channel limits
@ -270,14 +289,16 @@ describe('PermissionsService', () => {
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 },
]);
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]
[AuthorizationActions.Create, Sections.CHANNEL],
];
// Execution: call the check method
const result = await service.check(
@ -287,7 +308,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should not allow the requested action
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(false);
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
false
);
});
it('Section Different from Channel', async () => {
// Mock of getPackageOptions to set channel limits
@ -296,14 +319,16 @@ describe('PermissionsService', () => {
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 },
]);
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
[AuthorizationActions.Create, Sections.AI], // Requesting permission for AI instead of CHANNEL
];
// Execution: call the check method
const result = await service.check(
@ -313,7 +338,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should not allow the requested action in CHANNEL
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(false);
expect(result.can(AuthorizationActions.Create, Sections.CHANNEL)).toBe(
false
);
});
});
describe('Monthly Posts Permission (97/110)', () => {
@ -324,15 +351,17 @@ describe('PermissionsService', () => {
options: { ...baseOptions, posts_per_month: 100 },
});
// Mock of getSubscription
jest.spyOn(mockSubscriptionService, 'getSubscription').mockResolvedValue({
...baseSubscription,
createdAt: new Date(),
});
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]
[AuthorizationActions.Create, Sections.POSTS_PER_MONTH],
];
// Execution: call the check method
const result = await service.check(
@ -342,7 +371,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should allow the requested action
expect(result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)).toBe(true);
expect(
result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)
).toBe(true);
});
it('Posts Exceed Limit', async () => {
// Mock of getPackageOptions to set post limits
@ -351,16 +382,20 @@ describe('PermissionsService', () => {
options: { ...baseOptions, posts_per_month: 100 },
});
// Mock of getSubscription
jest.spyOn(mockSubscriptionService, 'getSubscription').mockResolvedValue({
...baseSubscription,
createdAt: new Date(),
});
jest
.spyOn(mockSubscriptionService, 'getSubscription')
.mockResolvedValue({
...baseSubscription,
createdAt: new Date(),
});
// Mock of countPostsFromDay to return quantity above the limit
jest.spyOn(mockPostsService, 'countPostsFromDay').mockResolvedValue(150);
jest
.spyOn(mockPostsService, 'countPostsFromDay')
.mockResolvedValue(150);
// List of requested permissions
const requestedPermissions: Array<[AuthorizationActions, Sections]> = [
[AuthorizationActions.Create, Sections.POSTS_PER_MONTH]
[AuthorizationActions.Create, Sections.POSTS_PER_MONTH],
];
// Execution: call the check method
const result = await service.check(
@ -370,7 +405,9 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should not allow the requested action
expect(result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)).toBe(false);
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
@ -379,15 +416,17 @@ describe('PermissionsService', () => {
options: { ...baseOptions, posts_per_month: 100 },
});
// Mock of getSubscription
jest.spyOn(mockSubscriptionService, 'getSubscription').mockResolvedValue({
...baseSubscription,
createdAt: new Date(),
});
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
[AuthorizationActions.Create, Sections.AI], // Requesting permission for AI instead of POSTS_PER_MONTH
];
// Execution: call the check method
const result = await service.check(
@ -397,8 +436,10 @@ describe('PermissionsService', () => {
requestedPermissions
);
// Verification: should not allow the requested action in POSTS_PER_MONTH
expect(result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)).toBe(false);
expect(
result.can(AuthorizationActions.Create, Sections.POSTS_PER_MONTH)
).toBe(false);
});
});
});
});
});

View File

@ -34,7 +34,7 @@ export class PermissionsService {
private _subscriptionService: SubscriptionService,
private _postsService: PostsService,
private _integrationService: IntegrationService,
private _webhooksService: WebhooksService,
private _webhooksService: WebhooksService
) {}
async getPackageOptions(orgId: string) {
const subscription =
@ -85,7 +85,7 @@ export class PermissionsService {
if (section === Sections.CHANNEL) {
const totalChannels = (
await this._integrationService.getIntegrationsList(orgId)
).filter(f => !f.refreshNeeded).length;
).filter((f) => !f.refreshNeeded).length;
if (
(options.channel && options.channel > totalChannels) ||

View File

@ -1,5 +1,7 @@
export interface ProvidersInterface {
generateLink(query?: any): Promise<string> | string;
getToken(code: string): Promise<string>;
getUser(providerToken: string): Promise<{email: string, id: string}> | false;
}
generateLink(query?: any): Promise<string> | string;
getToken(code: string): Promise<string>;
getUser(
providerToken: string
): Promise<{ email: string; id: string }> | false;
}

View File

@ -12,7 +12,7 @@ export class FarcasterProvider implements ProvidersInterface {
async getToken(code: string) {
const data = JSON.parse(Buffer.from(code, 'base64').toString());
const status = await client.lookupSigner({signerUuid: data.signer_uuid});
const status = await client.lookupSigner({ signerUuid: data.signer_uuid });
if (status.status === 'approved') {
return data.signer_uuid;
}
@ -21,7 +21,7 @@ export class FarcasterProvider implements ProvidersInterface {
}
async getUser(providerToken: string) {
const status = await client.lookupSigner({signerUuid: providerToken});
const status = await client.lookupSigner({ signerUuid: providerToken });
if (status.status !== 'approved') {
return {
id: '',
@ -29,7 +29,6 @@ export class FarcasterProvider implements ProvidersInterface {
};
}
// const { client, oauth2 } = clientAndYoutube();
// client.setCredentials({ access_token: providerToken });
// const user = oauth2(client);

View File

@ -17,16 +17,18 @@ export class PublicAuthMiddleware implements NestMiddleware {
const org = await this._organizationService.getOrgByApiKey(auth);
if (!org) {
res.status(HttpStatus.UNAUTHORIZED).json({ msg: 'Invalid API key' });
return ;
return;
}
if (!!process.env.STRIPE_SECRET_KEY && !org.subscription) {
res.status(HttpStatus.UNAUTHORIZED).json({ msg: 'No subscription found' });
return ;
res
.status(HttpStatus.UNAUTHORIZED)
.json({ msg: 'No subscription found' });
return;
}
// @ts-ignore
req.org = {...org, users: [{users: {role: 'SUPERADMIN'}}]};
req.org = { ...org, users: [{ users: { role: 'SUPERADMIN' } }] };
} catch (err) {
throw new HttpForbiddenException();
}

View File

@ -10,4 +10,4 @@
"keywords": [],
"author": "",
"license": "ISC"
}
}

View File

@ -9,12 +9,7 @@ import { AgentRun } from './tasks/agent.run';
import { AgentModule } from '@gitroom/nestjs-libraries/agent/agent.module';
@Module({
imports: [
ExternalCommandModule,
DatabaseModule,
BullMqModule,
AgentModule,
],
imports: [ExternalCommandModule, DatabaseModule, BullMqModule, AgentModule],
controllers: [],
providers: [CheckStars, RefreshTokens, ConfigurationTask, AgentRun],
get exports() {

View File

@ -1,19 +1,16 @@
import { NestFactory } from '@nestjs/core';
import {CommandModule} from "./command.module";
import {CommandService} from "nestjs-command";
import { CommandModule } from './command.module';
import { CommandService } from 'nestjs-command';
async function bootstrap() {
// some comment again
const app = await NestFactory.createApplicationContext(CommandModule, {
logger: ['error']
logger: ['error'],
});
try {
await app
.select(CommandModule)
.get(CommandService)
.exec();
await app.close()
await app.select(CommandModule).get(CommandService).exec();
await app.close();
} catch (error) {
console.error(error);
await app.close();

View File

@ -10,21 +10,23 @@ export class ConfigurationTask {
})
create() {
const checker = new ConfigurationChecker();
checker.readEnvFromProcess()
checker.check()
checker.readEnvFromProcess();
checker.check();
if (checker.hasIssues()) {
for (const issue of checker.getIssues()) {
console.warn("Configuration issue:", issue)
console.warn('Configuration issue:', issue);
}
console.error("Configuration check complete, issues: ", checker.getIssuesCount())
console.error(
'Configuration check complete, issues: ',
checker.getIssuesCount()
);
} else {
console.log("Configuration check complete, no issues found.")
console.log('Configuration check complete, no issues found.');
}
console.log("Press Ctrl+C to exit.");
return true
console.log('Press Ctrl+C to exit.');
return true;
}
}

View File

@ -11,4 +11,4 @@
"keywords": [],
"author": "",
"license": "ISC"
}
}

View File

@ -1,5 +1,5 @@
import { NestFactory } from '@nestjs/core';
import {CronModule} from "./cron.module";
import { CronModule } from './cron.module';
async function bootstrap() {
// some comment again

View File

@ -4,9 +4,7 @@ import { BullMqClient } from '@gitroom/nestjs-libraries/bull-mq-transport-new/cl
@Injectable()
export class SyncTrending {
constructor(
private _workerServiceProducer: BullMqClient
) {}
constructor(private _workerServiceProducer: BullMqClient) {}
@Cron('0 * * * *')
async syncTrending() {
this._workerServiceProducer.emit('sync_trending', {}).subscribe();

View File

@ -3,39 +3,46 @@ import { resolve } from 'path';
import type { PluginOption } from 'vite';
// plugin to remove dev icons from prod build
export function stripDevIcons (isDev: boolean) {
if (isDev) return null
export function stripDevIcons(isDev: boolean) {
if (isDev) return null;
return {
name: 'strip-dev-icons',
resolveId (source: string) {
return source === 'virtual-module' ? source : null
resolveId(source: string) {
return source === 'virtual-module' ? source : null;
},
renderStart (outputOptions: any, inputOptions: any) {
const outDir = outputOptions.dir
fs.rm(resolve(outDir, 'dev-icon-32.png'), () => console.log(`Deleted dev-icon-32.png from prod build`))
fs.rm(resolve(outDir, 'dev-icon-128.png'), () => console.log(`Deleted dev-icon-128.png from prod build`))
}
}
renderStart(outputOptions: any, inputOptions: any) {
const outDir = outputOptions.dir;
fs.rm(resolve(outDir, 'dev-icon-32.png'), () =>
console.log(`Deleted dev-icon-32.png from prod build`)
);
fs.rm(resolve(outDir, 'dev-icon-128.png'), () =>
console.log(`Deleted dev-icon-128.png from prod build`)
);
},
};
}
// plugin to support i18n
export function crxI18n (options: { localize: boolean, src: string }): PluginOption {
if (!options.localize) return null
// plugin to support i18n
export function crxI18n(options: {
localize: boolean;
src: string;
}): PluginOption {
if (!options.localize) return null;
const getJsonFiles = (dir: string): Array<string> => {
const files = fs.readdirSync(dir, {recursive: true}) as string[]
return files.filter(file => !!file && file.endsWith('.json'))
}
const entry = resolve(__dirname, options.src)
const localeFiles = getJsonFiles(entry)
const files = localeFiles.map(file => {
const files = fs.readdirSync(dir, { recursive: true }) as string[];
return files.filter((file) => !!file && file.endsWith('.json'));
};
const entry = resolve(__dirname, options.src);
const localeFiles = getJsonFiles(entry);
const files = localeFiles.map((file) => {
return {
id: '',
fileName: file,
source: fs.readFileSync(resolve(entry, file))
}
})
source: fs.readFileSync(resolve(entry, file)),
};
});
return {
name: 'crx-i18n',
enforce: 'pre',
@ -43,14 +50,14 @@ export function crxI18n (options: { localize: boolean, src: string }): PluginOpt
order: 'post',
handler() {
files.forEach((file) => {
const refId = this.emitFile({
type: 'asset',
source: file.source,
fileName: '_locales/'+file.fileName
})
file.id = refId
})
}
}
}
}
const refId = this.emitFile({
type: 'asset',
source: file.source,
fileName: '_locales/' + file.fileName,
});
file.id = refId;
});
},
},
};
}

View File

@ -8,11 +8,7 @@
},
"web_accessible_resources": [
{
"resources": [
"contentStyle.css",
"dev-icon-128.png",
"dev-icon-32.png"
],
"resources": ["contentStyle.css", "dev-icon-128.png", "dev-icon-32.png"],
"matches": []
}
]

View File

@ -11,8 +11,6 @@
"manifest.dev.json"
],
"ext": "tsx,css,html,ts,json",
"ignore": [
"src/**/*.spec.ts"
],
"ignore": ["src/**/*.spec.ts"],
"exec": "vite build --config vite.config.chrome.ts --mode development"
}

View File

@ -11,8 +11,6 @@
"manifest.dev.json"
],
"ext": "tsx,css,html,ts,json",
"ignore": [
"src/**/*.spec.ts"
],
"ignore": ["src/**/*.spec.ts"],
"exec": "vite build --config vite.config.firefox.ts --mode development"
}

View File

@ -3,11 +3,11 @@
@tailwind utilities;
@theme {
--animate-spin-slow: spin 20s linear infinite;
--animate-spin-slow: spin 20s linear infinite;
@keyframes spin {
to {
transform: rotate(360deg);
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
}
}

View File

@ -7,4 +7,4 @@
"message": "description in src/locales/en/messages.json",
"description": "Extension description"
}
}
}

View File

@ -1,32 +1,27 @@
import { fetchRequestUtil } from "@gitroom/extension/utils/request.util";
import { fetchRequestUtil } from '@gitroom/extension/utils/request.util';
const isDevelopment = process.env.NODE_ENV === "development";
const isDevelopment = process.env.NODE_ENV === 'development';
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.action === "makeHttpRequest") {
if (request.action === 'makeHttpRequest') {
fetchRequestUtil(request).then((response) => {
sendResponse(response);
});
}
if (request.action === "loadStorage") {
chrome.storage.local.get([request.key],
function (storage) {
sendResponse(storage[request.key]);
},
);
if (request.action === 'loadStorage') {
chrome.storage.local.get([request.key], function (storage) {
sendResponse(storage[request.key]);
});
}
if (request.action === "saveStorage") {
chrome.storage.local.set(
{ [request.key]: request.value },
function () {
sendResponse({ success: true });
}
);
if (request.action === 'saveStorage') {
chrome.storage.local.set({ [request.key]: request.value }, function () {
sendResponse({ success: true });
});
}
if (request.action === "loadCookie") {
if (request.action === 'loadCookie') {
chrome.cookies.get(
{
url: import.meta.env?.FRONTEND_URL || process?.env?.FRONTEND_URL,
@ -34,7 +29,7 @@ chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
},
function (cookies) {
sendResponse(cookies?.value);
},
}
);
}

View File

@ -1,11 +1,11 @@
import { createRoot } from "react-dom/client";
import "./style.css";
import { MainContent } from "@gitroom/extension/pages/content/main.content";
const div = document.createElement("div");
div.id = "__root";
import { createRoot } from 'react-dom/client';
import './style.css';
import { MainContent } from '@gitroom/extension/pages/content/main.content';
const div = document.createElement('div');
div.id = '__root';
document.body.appendChild(div);
const rootContainer = document.querySelector("#__root");
const rootContainer = document.querySelector('#__root');
if (!rootContainer) throw new Error("Can't find Content root element");
const root = createRoot(rootContainer);
root.render(<MainContent />);

View File

@ -157,7 +157,12 @@ export const MainContentInner: FC = (props) => {
actionType={actionEl.actionType}
provider={provider}
wrap={true}
selector={stringToABC(provider.element.split(',').map(z => z.trim()).find(p => actionEl.element.matches(p)) || '')}
selector={stringToABC(
provider.element
.split(',')
.map((z) => z.trim())
.find((p) => actionEl.element.matches(p)) || ''
)}
/>,
actionEl.element
)}

View File

@ -3,25 +3,25 @@
@tailwind utilities;
.my-wrapper {
left: 0 !important;
top: 0 !important;
position: fixed !important;
width: 100% !important;
height: 100% !important;
z-index: 999999 !important;
display: flex !important;
justify-content: center !important;
background: rgba(0, 0, 0, 0.5) !important;
left: 0 !important;
top: 0 !important;
position: fixed !important;
width: 100% !important;
height: 100% !important;
z-index: 999999 !important;
display: flex !important;
justify-content: center !important;
background: rgba(0, 0, 0, 0.5) !important;
}
.my-wrapper > div {
background: white !important;
width: 600px !important;
height: 300px !important;
border-radius: 10px !important;
display: flex !important;
flex-direction: column !important;
justify-items: center !important;
margin-top: 100px !important;
color: black !important;
}
background: white !important;
width: 600px !important;
height: 300px !important;
border-radius: 10px !important;
display: flex !important;
flex-direction: column !important;
justify-items: center !important;
margin-top: 100px !important;
color: black !important;
}

View File

@ -1,12 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Options</title>
</head>
<head>
<meta charset="UTF-8" />
<title>Options</title>
</head>
<body>
<div id="__root"></div>
<script type="module" src="./index.tsx"></script>
</body>
<body>
<div id="__root"></div>
<script type="module" src="./index.tsx"></script>
</body>
</html>

View File

@ -1,10 +1,10 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import '@gitroom/extension/pages/options/index.css';
import Options from "@gitroom/extension/pages/options/Options";
import Options from '@gitroom/extension/pages/options/Options';
function init() {
const rootContainer = document.querySelector("#__root");
const rootContainer = document.querySelector('#__root');
if (!rootContainer) throw new Error("Can't find Options root element");
const root = createRoot(rootContainer);
root.render(<Options />);

View File

@ -4,4 +4,4 @@ body {
.container {
color: #ffffff;
}
}

View File

@ -1,12 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Devtools Panel</title>
</head>
<head>
<meta charset="UTF-8" />
<title>Devtools Panel</title>
</head>
<body>
<div id="__root"></div>
<script type="module" src="./index.tsx"></script>
</body>
<body>
<div id="__root"></div>
<script type="module" src="./index.tsx"></script>
</body>
</html>

View File

@ -5,7 +5,7 @@ import '@pages/panel/index.css';
import '@assets/styles/tailwind.css';
function init() {
const rootContainer = document.querySelector("#__root");
const rootContainer = document.querySelector('#__root');
if (!rootContainer) throw new Error("Can't find Panel root element");
const root = createRoot(rootContainer);
root.render(<Panel />);

View File

@ -1,6 +1,6 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from "react";
import { ProviderList } from "@gitroom/extension/providers/provider.list";
import { fetchCookie } from "@gitroom/extension/utils/load.cookie";
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { ProviderList } from '@gitroom/extension/providers/provider.list';
import { fetchCookie } from '@gitroom/extension/utils/load.cookie';
export const PopupContainerContainer: FC = () => {
const [url, setUrl] = useState<string | null>(null);
@ -11,7 +11,9 @@ export const PopupContainerContainer: FC = () => {
}, []);
if (!url) {
return <div className="text-4xl">This website is not supported by Postiz</div>;
return (
<div className="text-4xl">This website is not supported by Postiz</div>
);
}
return <PopupContainer url={url} />;
@ -54,7 +56,9 @@ export const PopupContainer: FC<{ url: string }> = (props) => {
}
if (!provider) {
return <div className="text-4xl">This website is not supported by Postiz</div>;
return (
<div className="text-4xl">This website is not supported by Postiz</div>
);
}
if (!isLoggedIn) {

View File

@ -1,12 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Popup</title>
</head>
<head>
<meta charset="UTF-8" />
<title>Popup</title>
</head>
<body>
<div id="__root"></div>
<script type="module" src="./index.tsx"></script>
</body>
<body>
<div id="__root"></div>
<script type="module" src="./index.tsx"></script>
</body>
</html>

View File

@ -2,10 +2,10 @@ import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import '@gitroom/extension/assets/styles/tailwind.css';
import Popup from "@gitroom/extension/pages/popup/Popup";
import Popup from '@gitroom/extension/pages/popup/Popup';
function init() {
const rootContainer = document.querySelector("#__root");
const rootContainer = document.querySelector('#__root');
if (!rootContainer) throw new Error("Can't find Popup root element");
const root = createRoot(rootContainer);
root.render(<Popup />);

View File

@ -1,12 +1,12 @@
import { ProviderInterface } from "@gitroom/extension/providers/provider.interface";
import { ProviderInterface } from '@gitroom/extension/providers/provider.interface';
export class LinkedinProvider implements ProviderInterface {
identifier = "linkedin";
baseUrl = "https://www.linkedin.com";
identifier = 'linkedin';
baseUrl = 'https://www.linkedin.com';
element = `.share-box-feed-entry__closed-share-box`;
attachTo = `[role="main"]`;
style = "light" as "light";
style = 'light' as 'light';
findIdentifier = (element: HTMLElement) => {
return element.closest('[data-urn]').getAttribute("data-urn");
return element.closest('[data-urn]').getAttribute('data-urn');
};
}

View File

@ -5,7 +5,7 @@ export class XProvider implements ProviderInterface {
baseUrl = 'https://x.com';
element = `[data-testid="primaryColumn"]:has([data-testid="toolBar"]) [data-testid="tweetTextarea_0_label"], [data-testid="SideNav_NewTweet_Button"]`;
attachTo = `#react-root`;
style = "dark" as "dark";
style = 'dark' as 'dark';
findIdentifier = (element: HTMLElement) => {
return (
Array.from(

View File

@ -1,13 +1,13 @@
export const fetchCookie = (cookieName: string) => {
return chrome.runtime.sendMessage({
action: "loadCookie",
action: 'loadCookie',
cookieName,
});
};
export const getCookie = async (
cookies: chrome.cookies.Cookie[],
cookie: string,
cookie: string
) => {
// return cookies.find((c) => c.name === cookie).value;
};

View File

@ -1,6 +1,6 @@
export const fetchStorage = (key: string) => {
return chrome.runtime.sendMessage({
action: "loadStorage",
action: 'loadStorage',
key,
});
};

View File

@ -1,12 +1,12 @@
const isDev = process.env.NODE_ENV === "development";
const isDev = process.env.NODE_ENV === 'development';
export const sendRequest = (
auth: string,
url: string,
method: "GET" | "POST",
body?: string,
method: 'GET' | 'POST',
body?: string
) => {
return chrome.runtime.sendMessage({
action: "makeHttpRequest",
action: 'makeHttpRequest',
url,
method,
body,
@ -17,16 +17,17 @@ export const sendRequest = (
export const fetchRequestUtil = async (request: any) => {
return (
await fetch(
(import.meta.env?.FRONTEND_URL || process?.env?.FRONTEND_URL) + request.url,
(import.meta.env?.FRONTEND_URL || process?.env?.FRONTEND_URL) +
request.url,
{
method: request.method || "GET",
method: request.method || 'GET',
headers: {
"Content-Type": "application/json",
'Content-Type': 'application/json',
Authorization: request.auth,
// Add any auth headers here if needed
},
...(request.body ? { body: request.body } : {}),
},
}
)
).json();
};

View File

@ -1,7 +1,7 @@
export const saveStorage = (key: string, value: any) => {
return chrome.runtime.sendMessage({
action: "saveStorage",
action: 'saveStorage',
key,
value,
});
};
};

View File

@ -15,7 +15,7 @@
"moduleResolution": "bundler",
"resolveJsonModule": true,
"noEmit": true,
"jsx": "react-jsx",
"jsx": "react-jsx"
},
"include": [
"src",
@ -23,5 +23,5 @@
"vite.config.base.ts",
"vite.config.chrome.ts",
"vite.config.firefox.ts"
],
]
}

View File

@ -1,16 +1,16 @@
import react from "@vitejs/plugin-react";
import { resolve } from "path";
import { ManifestV3Export } from "@crxjs/vite-plugin";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig, BuildOptions } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
import { stripDevIcons, crxI18n } from "./custom-vite-plugins";
import manifest from "./manifest.json";
import devManifest from "./manifest.dev.json";
import pkg from "./package.json";
import { ProviderList } from "./src/providers/provider.list";
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
import { ManifestV3Export } from '@crxjs/vite-plugin';
import tailwindcss from '@tailwindcss/vite';
import { defineConfig, BuildOptions } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
import { stripDevIcons, crxI18n } from './custom-vite-plugins';
import manifest from './manifest.json';
import devManifest from './manifest.dev.json';
import pkg from './package.json';
import { ProviderList } from './src/providers/provider.list';
const isDev = process.env.NODE_ENV === "development";
const isDev = process.env.NODE_ENV === 'development';
// set this flag to true, if you want localization support
const localize = false;
@ -20,17 +20,15 @@ const { matches, ...rest } = manifest?.content_scripts?.[0] || {};
export const baseManifest = {
...manifest,
host_permissions: [
...ProviderList.map((p) => p.baseUrl + "/"),
...ProviderList.map((p) => p.baseUrl + '/'),
import.meta.env?.FRONTEND_URL || process?.env?.FRONTEND_URL + '/*',
],
permissions: [...(manifest.permissions || [])],
content_scripts: [
{
matches: ProviderList.reduce(
(all, p) => [...all, p.baseUrl + "/*"],
[
import.meta.env?.FRONTEND_URL || process?.env?.FRONTEND_URL + '/*',
],
(all, p) => [...all, p.baseUrl + '/*'],
[import.meta.env?.FRONTEND_URL || process?.env?.FRONTEND_URL + '/*']
),
...rest,
},
@ -39,9 +37,9 @@ export const baseManifest = {
...merge,
...(localize
? {
name: "__MSG_extName__",
description: "__MSG_extDescription__",
default_locale: "en",
name: '__MSG_extName__',
description: '__MSG_extDescription__',
default_locale: 'en',
}
: {}),
} as ManifestV3Export;
@ -52,13 +50,13 @@ export const baseBuildOptions: BuildOptions = {
};
export default defineConfig({
envPrefix: ["NEXT_PUBLIC_", "FRONTEND_URL"],
envPrefix: ['NEXT_PUBLIC_', 'FRONTEND_URL'],
plugins: [
tailwindcss(),
tsconfigPaths(),
react(),
stripDevIcons(isDev),
crxI18n({ localize, src: "./src/locales" }),
crxI18n({ localize, src: './src/locales' }),
],
publicDir: resolve(__dirname, "public"),
publicDir: resolve(__dirname, 'public'),
});

View File

@ -1,11 +1,11 @@
import { resolve } from "path";
import { mergeConfig, defineConfig } from "vite";
import { crx, ManifestV3Export } from "@crxjs/vite-plugin";
import baseConfig, { baseManifest, baseBuildOptions } from "./vite.config.base";
import hotReloadExtension from "hot-reload-extension-vite";
import { resolve } from 'path';
import { mergeConfig, defineConfig } from 'vite';
import { crx, ManifestV3Export } from '@crxjs/vite-plugin';
import baseConfig, { baseManifest, baseBuildOptions } from './vite.config.base';
import hotReloadExtension from 'hot-reload-extension-vite';
const outDir = resolve(__dirname, "dist");
const isDev = process.env.NODE_ENV === "development";
const outDir = resolve(__dirname, 'dist');
const isDev = process.env.NODE_ENV === 'development';
export default mergeConfig(
baseConfig,
@ -15,11 +15,11 @@ export default mergeConfig(
manifest: {
...baseManifest,
background: {
service_worker: "src/pages/background/index.ts",
type: "module",
service_worker: 'src/pages/background/index.ts',
type: 'module',
},
} as ManifestV3Export,
browser: "chrome",
browser: 'chrome',
contentScripts: {
injectCss: true,
},
@ -28,7 +28,7 @@ export default mergeConfig(
? [
hotReloadExtension({
log: true,
backgroundPath: "src/pages/background/index.ts",
backgroundPath: 'src/pages/background/index.ts',
}),
]
: []),
@ -40,13 +40,13 @@ export default mergeConfig(
? {
rollupOptions: {
output: {
entryFileNames: "assets/[name].js",
chunkFileNames: "assets/[name].js",
assetFileNames: "assets/[name][extname]",
entryFileNames: 'assets/[name].js',
chunkFileNames: 'assets/[name].js',
assetFileNames: 'assets/[name][extname]',
},
},
}
: {}),
},
}),
})
);

View File

@ -1,7 +1,7 @@
import { resolve } from 'path';
import { mergeConfig, defineConfig } from 'vite';
import { crx, ManifestV3Export } from '@crxjs/vite-plugin';
import baseConfig, { baseManifest, baseBuildOptions } from './vite.config.base'
import baseConfig, { baseManifest, baseBuildOptions } from './vite.config.base';
const outDir = resolve(__dirname, 'dist_firefox');
@ -13,19 +13,19 @@ export default mergeConfig(
manifest: {
...baseManifest,
background: {
scripts: [ 'src/pages/background/index.ts' ]
scripts: ['src/pages/background/index.ts'],
},
} as ManifestV3Export,
browser: 'firefox',
contentScripts: {
injectCss: true,
}
})
},
}),
],
build: {
...baseBuildOptions,
outDir
outDir,
},
publicDir: resolve(__dirname, 'public'),
})
)
);

View File

@ -40,5 +40,4 @@ const nextConfig = {
];
},
};
export default nextConfig;

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,5 @@
import { ReactNode } from 'react';
import { PreviewWrapper } from '@gitroom/frontend/components/preview/preview.wrapper';
export default async function AppLayout({ children }: { children: ReactNode }) {
return (
<div className="bg-[#000000] min-h-screen">

View File

@ -1,7 +1,5 @@
import { internalFetch } from '@gitroom/helpers/utils/internal.fetch';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
import Image from 'next/image';
@ -11,30 +9,32 @@ import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { VideoOrImage } from '@gitroom/react/helpers/video.or.image';
import { CopyClient } from '@gitroom/frontend/components/preview/copy.client';
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
dayjs.extend(utc);
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Preview`,
description: '',
};
export default async function Auth({
params: { id },
searchParams,
}: {
params: { id: string };
searchParams?: { share?: string };
params: {
id: string;
};
searchParams?: {
share?: string;
};
}) {
const post = await (await internalFetch(`/public/posts/${id}`)).json();
const t = await getT();
if (!post.length) {
return (
<div className="text-white fixed left-0 top-0 w-full h-full flex justify-center items-center text-[20px]">
Post not found
{t('post_not_found', 'Post not found')}
</div>
);
}
return (
<div>
<div className="mx-auto w-full max-w-[1346px] py-3 text-white">
@ -91,7 +91,7 @@ export default async function Auth({
</div>
)}
<div className="flex-1">
Publication Date:{' '}
{t('publication_date', 'Publication Date:')}
{dayjs
.utc(post[0].createdAt)
.local()
@ -140,7 +140,9 @@ export default async function Auth({
<div className="flex flex-col gap-[20px]">
<div
className="text-sm whitespace-pre-wrap"
dangerouslySetInnerHTML={{ __html: p.content }}
dangerouslySetInnerHTML={{
__html: p.content,
}}
/>
<div className="flex w-full gap-[10px]">
{JSON.parse(p?.image || '[]').map((p: any) => (
@ -148,7 +150,11 @@ export default async function Auth({
key={p.name}
className="flex-1 rounded-[10px] max-h-[500px] overflow-hidden"
>
<VideoOrImage isContain={true} src={p.path} autoplay={true} />
<VideoOrImage
isContain={true}
src={p.path}
autoplay={true}
/>
</div>
))}
</div>

View File

@ -1,15 +1,12 @@
export const dynamic = 'force-dynamic';
import { AnalyticsComponent } from '@gitroom/frontend/components/analytics/analytics.component';
import { Metadata } from 'next';
import { PlatformAnalytics } from '@gitroom/frontend/components/platform-analytics/platform.analytics';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Analytics`,
description: '',
};
export default async function Index() {
return (
<>

View File

@ -1,15 +1,11 @@
import { LifetimeDeal } from '@gitroom/frontend/components/billing/lifetime.deal';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Lifetime deal`,
description: '',
};
export default async function Page() {
return <LifetimeDeal />;
}

View File

@ -1,15 +1,11 @@
export const dynamic = 'force-dynamic';
import { BillingComponent } from '@gitroom/frontend/components/billing/billing.component';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Billing`,
description: '',
};
export default async function Page() {
return <BillingComponent />;
}

View File

@ -1,12 +1,17 @@
import {Metadata} from "next";
import { Metadata } from 'next';
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
export const metadata: Metadata = {
title: 'Error',
description: '',
}
};
export default async function Page() {
return (
<div>We are experiencing some difficulty, try to refresh the page</div>
)
}
const t = await getT();
return (
<div>
{t(
'we_are_experiencing_some_difficulty_try_to_refresh_the_page',
'We are experiencing some difficulty, try to refresh the page'
)}
</div>
);
}

View File

@ -1,18 +1,19 @@
import { HttpStatusCode } from 'axios';
export const dynamic = 'force-dynamic';
import { internalFetch } from '@gitroom/helpers/utils/internal.fetch';
import { redirect } from 'next/navigation';
import { Redirect } from '@gitroom/frontend/components/layout/redirect';
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
export default async function Page({
params: { provider },
searchParams,
}: {
params: { provider: string };
params: {
provider: string;
};
searchParams: any;
}) {
const t = await getT();
if (provider === 'x') {
searchParams = {
...searchParams,
@ -21,25 +22,21 @@ export default async function Page({
refresh: searchParams.refresh || '',
};
}
if (provider === 'vk') {
searchParams = {
...searchParams,
state: searchParams.state || '',
code: searchParams.code + '&&&&' + searchParams.device_id
code: searchParams.code + '&&&&' + searchParams.device_id,
};
}
const data = await internalFetch(`/integrations/social/${provider}/connect`, {
method: 'POST',
body: JSON.stringify(searchParams),
});
if (data.status === HttpStatusCode.NotAcceptable) {
const { msg } = await data.json();
return redirect(`/launches?msg=${msg}`);
}
if (
data.status !== HttpStatusCode.Ok &&
data.status !== HttpStatusCode.Created
@ -47,20 +44,17 @@ export default async function Page({
return (
<>
<div className="mt-[50px] text-[50px]">
Could not add provider.
{t('could_not_add_provider', 'Could not add provider.')}
<br />
You are being redirected back
{t('you_are_being_redirected_back', 'You are being redirected back')}
</div>
<Redirect url="/launches" delay={3000} />
</>
);
}
const { inBetweenSteps, id } = await data.json();
if (inBetweenSteps && !searchParams.refresh) {
return redirect(`/launches?added=${provider}&continue=${id}`);
}
return redirect(`/launches?added=${provider}&msg=Channel Updated`);
}

View File

@ -1,12 +1,12 @@
import { IntegrationRedirectComponent } from '@gitroom/frontend/components/launches/integration.redirect.component';
export const dynamic = 'force-dynamic';
export default async function Page({
params: { provider },
searchParams,
}: {
params: { provider: string };
params: {
provider: string;
};
searchParams: any;
}) {
return <IntegrationRedirectComponent />;

View File

@ -1,13 +1,16 @@
import { ReactNode } from 'react';
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
export default async function IntegrationLayout({
children,
}: {
children: ReactNode;
}) {
const t = await getT();
return (
<div className="text-6xl text-center mt-[50px]">
Adding channel, Redirecting You{children}
{t('adding_channel_redirecting_you', 'Adding channel, Redirecting You')}
{children}
</div>
);
}

View File

@ -1,17 +1,11 @@
export const dynamic = 'force-dynamic';
import {LaunchesComponent} from "@gitroom/frontend/components/launches/launches.component";
import {Metadata} from "next";
import { LaunchesComponent } from '@gitroom/frontend/components/launches/launches.component';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz Calendar' : 'Gitroom Launches'}`,
description: '',
}
};
export default async function Index() {
return (
<LaunchesComponent />
);
return <LaunchesComponent />;
}

View File

@ -1,9 +1,8 @@
import {LayoutSettings} from "@gitroom/frontend/components/layout/layout.settings";
export default async function Layout({ children }: { children: React.ReactNode }) {
return (
<LayoutSettings>
{children}
</LayoutSettings>
);
import { LayoutSettings } from '@gitroom/frontend/components/layout/layout.settings';
export default async function Layout({
children,
}: {
children: React.ReactNode;
}) {
return <LayoutSettings>{children}</LayoutSettings>;
}

View File

@ -1,9 +1,7 @@
import { Buyer } from '@gitroom/frontend/components/marketplace/buyer';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Marketplace`,
description: '',
@ -11,7 +9,9 @@ export const metadata: Metadata = {
export default async function Index({
searchParams,
}: {
searchParams: { code: string };
searchParams: {
code: string;
};
}) {
return <Buyer />;
}

View File

@ -1,11 +1,10 @@
import { BuyerSeller } from '@gitroom/frontend/components/marketplace/buyer.seller';
import { ReactNode } from 'react';
export default function Layout({ children }: { children: ReactNode }) {
return (
<>
<BuyerSeller />
{children}
</>
)
}
);
}

View File

@ -1,10 +1,8 @@
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Marketplace`,
description: '',
@ -12,8 +10,12 @@ export const metadata: Metadata = {
export default async function Index({
searchParams,
}: {
searchParams: { code: string };
searchParams: {
code: string;
};
}) {
const currentCookie = cookies()?.get('marketplace')?.value;
return redirect(currentCookie === 'buyer' ? '/marketplace/buyer' : '/marketplace/seller');
return redirect(
currentCookie === 'buyer' ? '/marketplace/buyer' : '/marketplace/seller'
);
}

View File

@ -1,9 +1,7 @@
import { Seller } from '@gitroom/frontend/components/marketplace/seller';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Marketplace`,
description: '',
@ -11,7 +9,9 @@ export const metadata: Metadata = {
export default async function Index({
searchParams,
}: {
searchParams: { code: string };
searchParams: {
code: string;
};
}) {
return <Seller />;
}

View File

@ -1,15 +1,11 @@
import { Messages } from '@gitroom/frontend/components/messages/messages';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Messages`,
description: '',
};
export default async function Index() {
return <Messages />;
}

View File

@ -1,9 +1,10 @@
import { Layout } from '@gitroom/frontend/components/messages/layout';
export const dynamic = 'force-dynamic';
import { ReactNode } from 'react';
export default async function LayoutWrapper({children}: {children: ReactNode}) {
return (
<Layout renderChildren={children} />
);
export default async function LayoutWrapper({
children,
}: {
children: ReactNode;
}) {
return <Layout renderChildren={children} />;
}

View File

@ -1,21 +1,24 @@
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
export const dynamic = 'force-dynamic';
import {Metadata} from "next";
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Messages`,
description: '',
}
};
export default async function Index() {
const t = await getT();
return (
<div className="bg-customColor3 h-[951px] flex flex-col rounded-[4px] border border-customColor6">
<div className="bg-customColor8 h-[64px]" />
<div className="flex-1 flex justify-center items-center text-[20px]">
Select a conversation and chat away.
</div>
</div>
<div className="bg-customColor3 h-[951px] flex flex-col rounded-[4px] border border-customColor6">
<div className="bg-customColor8 h-[64px]" />
<div className="flex-1 flex justify-center items-center text-[20px]">
{t(
'select_a_conversation_and_chat_away',
'Select a conversation and chat away.'
)}
</div>
</div>
);
}

View File

@ -1,15 +1,11 @@
import { Plugs } from '@gitroom/frontend/components/plugs/plugs';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Plugs`,
description: '',
};
export default async function Index() {
return (
<>

View File

@ -1,10 +1,7 @@
import { SettingsPopup } from '@gitroom/frontend/components/layout/settings.component';
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Settings`,
description: '',
@ -12,7 +9,9 @@ export const metadata: Metadata = {
export default async function Index({
searchParams,
}: {
searchParams: { code: string };
searchParams: {
code: string;
};
}) {
return <SettingsPopup />;
}

View File

@ -2,18 +2,15 @@ import { NextRequest, NextResponse } from 'next/server';
import { createReadStream, statSync } from 'fs';
// @ts-ignore
import mime from 'mime';
async function* nodeStreamToIterator(stream: any) {
for await (const chunk of stream) {
yield chunk;
}
}
function iteratorToStream(iterator: any) {
return new ReadableStream({
async pull(controller) {
const { value, done } = await iterator.next();
if (done) {
controller.close();
} else {
@ -22,25 +19,29 @@ function iteratorToStream(iterator: any) {
},
});
}
export const GET = (
request: NextRequest,
context: { params: { path: string[] } }
context: {
params: {
path: string[];
};
}
) => {
const filePath =
process.env.UPLOAD_DIRECTORY + '/' + context.params.path.join('/');
const response = createReadStream(filePath);
const fileStats = statSync(filePath);
const contentType = mime.getType(filePath) || 'application/octet-stream';
const iterator = nodeStreamToIterator(response);
const webStream = iteratorToStream(iterator);
return new Response(webStream, {
headers: {
'Content-Type': contentType, // Set the appropriate content-type header
'Content-Length': fileStats.size.toString(), // Set the content-length header
'Last-Modified': fileStats.mtime.toUTCString(), // Set the last-modified header
'Content-Type': contentType,
// Set the appropriate content-type header
'Content-Length': fileStats.size.toString(),
// Set the content-length header
'Last-Modified': fileStats.mtime.toUTCString(),
// Set the last-modified header
'Cache-Control': 'public, max-age=31536000, immutable', // Example cache-control header
},
});

View File

@ -1,15 +1,13 @@
export const dynamic = 'force-dynamic';
import { Metadata } from 'next';
import { AfterActivate } from '@gitroom/frontend/components/auth/after.activate';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} - Activate your account`,
title: `${
isGeneralServerSide() ? 'Postiz' : 'Gitroom'
} - Activate your account`,
description: '',
};
export default async function Auth() {
return <AfterActivate />;
}

View File

@ -1,17 +1,13 @@
export const dynamic = 'force-dynamic';
import {Metadata} from "next";
import { Metadata } from 'next';
import { Activate } from '@gitroom/frontend/components/auth/activate';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} - Activate your account`,
title: `${
isGeneralServerSide() ? 'Postiz' : 'Gitroom'
} - Activate your account`,
description: '',
};
export default async function Auth() {
return (
<Activate />
);
return <Activate />;
}

View File

@ -1,14 +1,15 @@
export const dynamic = 'force-dynamic';
import { ForgotReturn } from '@gitroom/frontend/components/auth/forgot-return';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Forgot Password`,
description: '',
};
export default async function Auth(params: { params: { token: string } }) {
export default async function Auth(params: {
params: {
token: string;
};
}) {
return <ForgotReturn token={params.params.token} />;
}

View File

@ -1,17 +1,11 @@
export const dynamic = 'force-dynamic';
import {Forgot} from "@gitroom/frontend/components/auth/forgot";
import {Metadata} from "next";
import { Forgot } from '@gitroom/frontend/components/auth/forgot';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Forgot Password`,
description: '',
};
export default async function Auth() {
return (
<Forgot />
);
return <Forgot />;
}

View File

@ -1,17 +1,19 @@
export const dynamic = 'force-dynamic';
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
export const dynamic = 'force-dynamic';
import { ReactNode } from 'react';
import Image from 'next/image';
import clsx from 'clsx';
import loadDynamic from 'next/dynamic';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
const ReturnUrlComponent = loadDynamic(() => import('./return.url.component'));
export default async function AuthLayout({
children,
}: {
children: ReactNode;
}) {
const t = await getT();
return (
<div className="dark !bg-black">
<ReturnUrlComponent />
@ -27,7 +29,9 @@ export default async function AuthLayout({
alt="Logo"
/>
<div
className={clsx(!isGeneralServerSide() ? 'mt-[12px]' : 'min-w-[80px]')}
className={clsx(
!isGeneralServerSide() ? 'mt-[12px]' : 'min-w-[80px]'
)}
>
{isGeneralServerSide() ? (
<svg
@ -55,7 +59,7 @@ export default async function AuthLayout({
/>
</svg>
) : (
<div className="text-[40px]">Gitroom</div>
<div className="text-[40px]">{t('gitroom', 'Gitroom')}</div>
)}
</div>
</div>

View File

@ -1,17 +1,11 @@
export const dynamic = 'force-dynamic';
import {Login} from "@gitroom/frontend/components/auth/login";
import {Metadata} from "next";
import { Login } from '@gitroom/frontend/components/auth/login';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Login`,
description: '',
};
export default async function Auth() {
return (
<Login />
);
return <Login />;
}

View File

@ -1,18 +1,17 @@
import { internalFetch } from '@gitroom/helpers/utils/internal.fetch';
export const dynamic = 'force-dynamic';
import { Register } from '@gitroom/frontend/components/auth/register';
import { Metadata } from 'next';
import { isGeneralServerSide } from '@gitroom/helpers/utils/is.general.server.side';
import Link from 'next/link';
import { getT } from '@gitroom/react/translation/get.translation.service.backend';
export const metadata: Metadata = {
title: `${isGeneralServerSide() ? 'Postiz' : 'Gitroom'} Register`,
description: '',
};
export default async function Auth() {
const t = await getT();
if (process.env.DISABLE_REGISTRATION) {
const canRegister = (
await (await internalFetch('/auth/can-register')).json()
@ -20,13 +19,14 @@ export default async function Auth() {
if (!canRegister) {
return (
<div className="text-center">
Registration is disabled
{t('registration_is_disabled', 'Registration is disabled')}
<br />
<Link className="underline hover:font-bold" href="/auth/login">Login instead</Link>
<Link className="underline hover:font-bold" href="/auth/login">
{t('login_instead', 'Login instead')}
</Link>
</div>
);
}
}
return <Register />;
}

View File

@ -1,7 +1,7 @@
'use client';
import { useSearchParams } from 'next/navigation';
import { FC, useCallback, useEffect } from 'react';
const ReturnUrlComponent: FC = () => {
const params = useSearchParams();
const url = params.get('returnUrl');
@ -10,18 +10,15 @@ const ReturnUrlComponent: FC = () => {
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;
}, []),
};
};
export default ReturnUrlComponent;

View File

@ -3,7 +3,6 @@ export const dynamic = 'force-dynamic';
import '../global.scss';
import 'react-tooltip/dist/react-tooltip.css';
import '@copilotkit/react-ui/styles.css';
import LayoutContext from '@gitroom/frontend/components/layout/layout.context';
import { ReactNode } from 'react';
import { Chakra_Petch } from 'next/font/google';
@ -17,15 +16,15 @@ import { ToltScript } from '@gitroom/frontend/components/layout/tolt.script';
import { FacebookComponent } from '@gitroom/frontend/components/layout/facebook.component';
import { headers } from 'next/headers';
import { headerName } from '@gitroom/react/translation/i18n.config';
const chakra = Chakra_Petch({ weight: '400', subsets: ['latin'] });
const chakra = Chakra_Petch({
weight: '400',
subsets: ['latin'],
});
export default async function AppLayout({ children }: { children: ReactNode }) {
const allHeaders = headers();
const Plausible = !!process.env.STRIPE_PUBLISHABLE_KEY
? PlausibleProvider
: Fragment;
return (
<html className={interClass}>
<head>

View File

@ -3,7 +3,6 @@ export const dynamic = 'force-dynamic';
import '../global.scss';
import 'react-tooltip/dist/react-tooltip.css';
import '@copilotkit/react-ui/styles.css';
import LayoutContext from '@gitroom/frontend/components/layout/layout.context';
import { ReactNode } from 'react';
import { Chakra_Petch } from 'next/font/google';
@ -12,9 +11,10 @@ import clsx from 'clsx';
import { VariableContextComponent } from '@gitroom/react/helpers/variable.context';
import { Fragment } from 'react';
import UtmSaver from '@gitroom/helpers/utils/utm.saver';
const chakra = Chakra_Petch({ weight: '400', subsets: ['latin'] });
const chakra = Chakra_Petch({
weight: '400',
subsets: ['latin'],
});
export default async function AppLayout({ children }: { children: ReactNode }) {
return (
<html className={interClass}>
@ -23,6 +23,7 @@ export default async function AppLayout({ children }: { children: ReactNode }) {
</head>
<body className={clsx(chakra.className, 'dark text-primary !bg-primary')}>
<VariableContextComponent
language="en"
storageProvider={
process.env.STORAGE_PROVIDER! as 'local' | 'cloudflare'
}

View File

@ -1,7 +1,7 @@
'use client';
import { StandaloneModal } from '@gitroom/frontend/components/standalone-modal/standalone.modal';
import { usePathname } from 'next/navigation';
export default async function Modal() {
return (
<div className="text-textColor">

View File

@ -1,6 +1,5 @@
import { ReactNode } from 'react';
import { AppLayout } from '@gitroom/frontend/components/launches/layout.standalone';
export default async function AppLayoutIn({
children,
}: {

View File

@ -99,10 +99,10 @@
--color-custom17: #000;
--color-custom18: #000;
--color-custom19: #f97066;
--color-custom20: #F5F5F5;
--color-custom20: #f5f5f5;
--color-custom21: #506490;
--color-custom22: #b91c1c;
--color-custom23: #F5F5F5;
--color-custom23: #f5f5f5;
--color-custom24: #eaff00;
--color-custom25: #2e3336;
--color-custom26: #1d9bf0;
@ -110,7 +110,7 @@
--color-custom28: #b69dec;
--color-custom29: #291259;
--color-custom30: #efefef;
--color-custom31: #E0E0E0;
--color-custom31: #e0e0e0;
--color-custom32: #181818;
--color-custom33: #f2f2f2;
--color-custom34: #334155;

View File

@ -465,4 +465,4 @@ div div .set-font-family {
.hideCopilot .copilotKitPopup {
display: none !important;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -6,14 +6,11 @@ import { StarsTableComponent } from '@gitroom/frontend/components/analytics/star
import useSWR from 'swr';
import { useFetch } from '@gitroom/helpers/utils/custom.fetch';
import { LoadingComponent } from '@gitroom/frontend/components/layout/loading';
export const AnalyticsComponent: FC = () => {
const fetch = useFetch();
const load = useCallback(async (path: string) => {
return await (await fetch(path)).json();
}, []);
const { isLoading: isLoadingAnalytics, data: analytics } = useSWR(
'/analytics',
load
@ -22,11 +19,9 @@ export const AnalyticsComponent: FC = () => {
'/analytics/trending',
load
);
if (isLoadingAnalytics || isLoadingTrending) {
return <LoadingComponent />;
}
return (
<div className="flex gap-[24px] flex-1">
<div className="flex flex-col gap-[24px] flex-1">

View File

@ -1,9 +1,9 @@
'use client';
import { FC, useEffect, useMemo, useRef } from 'react';
import DrawChart from 'chart.js/auto';
import { TotalList } from '@gitroom/frontend/components/analytics/stars.and.forks.interface';
import { chunk } from 'lodash';
function mergeDataPoints(data: TotalList[], numPoints: number): TotalList[] {
const res = chunk(data, Math.ceil(data.length / numPoints));
return res.map((row) => {
@ -13,13 +13,13 @@ function mergeDataPoints(data: TotalList[], numPoints: number): TotalList[] {
};
});
}
export const ChartSocial: FC<{ data: TotalList[] }> = (props) => {
export const ChartSocial: FC<{
data: TotalList[];
}> = (props) => {
const { data } = props;
const list = useMemo(() => {
return mergeDataPoints(data, 7);
}, [data]);
const ref = useRef<any>(null);
const chart = useRef<null | DrawChart>(null);
useEffect(() => {

View File

@ -1,4 +1,5 @@
'use client';
import { FC, useEffect, useRef } from 'react';
import DrawChart from 'chart.js/auto';
import {
@ -6,8 +7,9 @@ import {
StarsList,
} from '@gitroom/frontend/components/analytics/stars.and.forks.interface';
import dayjs from 'dayjs';
export const Chart: FC<{ list: StarsList[] | ForksList[] }> = (props) => {
export const Chart: FC<{
list: StarsList[] | ForksList[];
}> = (props) => {
const { list } = props;
const ref = useRef<any>(null);
const chart = useRef<null | DrawChart>(null);

View File

@ -2,12 +2,10 @@ export interface StarsList {
totalStars: number;
date: string;
}
export interface TotalList {
total: number;
date: string;
}
export interface ForksList {
totalForks: number;
date: string;
@ -19,7 +17,6 @@ export interface Stars {
login: string;
date: string;
}
export interface StarsAndForksInterface {
list: Array<{
login: string;

Some files were not shown because too many files have changed in this diff Show More