diff --git a/.github/workflows/build.yaml b/.github/workflows/build similarity index 66% rename from .github/workflows/build.yaml rename to .github/workflows/build index 41464863..d40e7f14 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build @@ -3,7 +3,6 @@ name: Build on: push: - pull_request: jobs: build: @@ -17,6 +16,9 @@ jobs: steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: @@ -48,3 +50,20 @@ jobs: - name: Build run: pnpm run build + + - name: Get Commit SHA (short) + id: get_version + run: | + # Get the short 8-character commit SHA + VERSION=$(git rev-parse --short=8 HEAD) + echo "Commit SHA is $VERSION" + echo "tag=$VERSION" >> $GITHUB_OUTPUT + + - name: SonarQube Analysis (Branch) + uses: SonarSource/sonarqube-scan-action@v6 + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} + with: + args: > + -Dsonar.projectVersion=${{ steps.get_version.outputs.tag }} diff --git a/.github/workflows/build-pr b/.github/workflows/build-pr new file mode 100644 index 00000000..513fe803 --- /dev/null +++ b/.github/workflows/build-pr @@ -0,0 +1,71 @@ +--- +name: Build + +on: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: ['20.17.0'] + # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ + + environment: + name: build-pr + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Install pnpm + uses: pnpm/action-setup@v2 + with: + version: 8 + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Setup pnpm cache + uses: actions/cache@v4 + with: + path: | + ${{ env.STORE_PATH }} + ${{ github.workspace }}/.next/cache + key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }} + restore-keys: | + ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}- + + - name: Install dependencies + run: pnpm install + + - name: Build + run: pnpm run build + + - name: Get Commit SHA (short) + id: get_version + run: | + # Get the short 8-character commit SHA + VERSION=$(git rev-parse --short=8 HEAD) + echo "Commit SHA is $VERSION" + echo "tag=$VERSION" >> $GITHUB_OUTPUT + + - name: SonarQube Analysis (Pull Request) + uses: SonarSource/sonarqube-scan-action@v6 + with: + args: > + -Dsonar.projectVersion=${{ steps.get_version.outputs.tag }} + -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} + -Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }} + -Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }} diff --git a/.github/workflows/pr-docker-build.yml b/.github/workflows/pr-docker-build.yml index adfb8348..17eb5c04 100644 --- a/.github/workflows/pr-docker-build.yml +++ b/.github/workflows/pr-docker-build.yml @@ -9,10 +9,16 @@ permissions: write-all jobs: build-and-publish: runs-on: ubuntu-latest - + + environment: + name: build-pr + steps: - name: Checkout code uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} - name: Log in to GitHub Container Registry uses: docker/login-action@v3 diff --git a/Jenkins/Build.Jenkinsfile b/Jenkins/Build.Jenkinsfile new file mode 100644 index 00000000..886ade0a --- /dev/null +++ b/Jenkins/Build.Jenkinsfile @@ -0,0 +1,96 @@ +// Declarative Pipeline for building Node.js application and running SonarQube analysis triggered by a push event. +pipeline { + // Defines the execution environment. Using 'agent any' to ensure an agent is available. + agent any + + // Global environment block removed to prevent Groovy scoping issues with manual path calculation. + + stages { + // Stage 1: Checkout the code (Relies on the initial SCM checkout done by Jenkins) + stage('Source Checkout') { + steps { + echo "Workspace already populated by the initial SCM checkout. Proceeding." + } + } + + // Stage 2: Setup Node.js v20 and install pnpm + stage('Setup Environment and Tools') { + steps { + sh ''' + echo "Ensuring required utilities and Node.js are installed..." + sudo apt-get update + sudo apt-get install -y curl unzip nodejs + + # 1. Install Node.js v20 + curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + sudo apt-get install -y nodejs + echo "Node.js version: \$(node -v)" + + # 2. Install pnpm globally (version 8) + npm install -g pnpm@8 + echo "pnpm version: \$(pnpm -v)" + ''' + } + } + + // Stage 3: Install dependencies and build the application + stage('Install and Build') { + steps { + sh 'pnpm install' + sh 'pnpm run build' + } + } + + // Stage 4: Run SonarQube analysis: Install scanner, get version, and execute. + stage('SonarQube Analysis') { + steps { + script { + // 1. Get the short 8-character commit SHA for project versioning + def commitShaShort = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim() + echo "Commit SHA (short) is: ${commitShaShort}" + + // --- 2. MANUALLY INSTALL THE SONAR SCANNER CLI LOCALLY IN THIS STAGE --- + sh """ + echo "Manually downloading and installing Sonar Scanner CLI..." + + # Download the stable scanner CLI package + curl -sS -o sonar-scanner.zip \ + "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747.zip" + + # Added -o flag to force overwrite and prevent interactive prompt failure + unzip -o -q sonar-scanner.zip -d . + """ + + // 3. Find the extracted directory name and capture the full absolute bin path in Groovy + // This is defined locally and used directly, avoiding environment variable issues. + def scannerBinPath = sh( + returnStdout: true, + script: ''' + SCANNER_DIR=$(find . -maxdepth 1 -type d -name "sonar-scanner*" | head -n 1) + # Get the full absolute path to the executable file + echo \$(pwd)/\${SCANNER_DIR}/bin/sonar-scanner + ''' + ).trim() + + echo "Scanner executable path captured: ${scannerBinPath}" + + // 4. Use withSonarQubeEnv to set up the secure variables (HOST and TOKEN) + withSonarQubeEnv(installationName: 'SonarQube-Server') { + // 5. Execute the scanner using the Groovy variable directly. + sh """ + echo "Starting SonarQube Analysis for project version: ${commitShaShort}" + + # Execute the full, absolute path captured in the Groovy variable. + '${scannerBinPath}' \\ + -Dsonar.projectVersion=${commitShaShort} \\ + -Dsonar.sources=. + + # SONAR_HOST_URL and SONAR_TOKEN are automatically passed as environment variables + # by the withSonarQubeEnv block. + """ + } + } + } + } + } +} diff --git a/Jenkins/BuildPR.Jenkinsfile b/Jenkins/BuildPR.Jenkinsfile new file mode 100644 index 00000000..6e279b0e --- /dev/null +++ b/Jenkins/BuildPR.Jenkinsfile @@ -0,0 +1,100 @@ +// Declarative Pipeline for building Node.js application and running SonarQube analysis for a Pull Request. +pipeline { + // Defines the execution environment. Using 'agent any' to ensure an agent is available. + agent any + + // Environment variables that hold PR details, provided by Jenkins Multibranch setup. + environment { + // FIX: Environment variables must be quoted or wrapped in a function call. + // We quote the 'env.CHANGE_ID' reference to fix the compilation error. + PR_KEY = "${env.CHANGE_ID}" + PR_BRANCH = "${env.CHANGE_BRANCH}" + PR_BASE = "${env.CHANGE_TARGET}" + } + + stages { + // Stage 1: Checkout the code (Relies on the initial SCM checkout done by Jenkins) + stage('Source Checkout') { + steps { + echo "Workspace already populated by the initial SCM checkout. Proceeding." + } + } + + // Stage 2: Setup Node.js v20, install pnpm, and install required tools (curl, unzip) + stage('Setup Environment and Tools') { + steps { + sh ''' + echo "Ensuring required utilities and Node.js are installed..." + sudo apt-get update + sudo apt-get install -y curl unzip nodejs + + # 1. Install Node.js v20 (closest matching the specified version '20.17.0') + curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + sudo apt-get install -y nodejs + echo "Node.js version: \$(node -v)" + + # 2. Install pnpm globally (version 8) + npm install -g pnpm@8 + echo "pnpm version: \$(pnpm -v)" + ''' + } + } + + // Stage 3: Install dependencies and build the application + stage('Install and Build') { + steps { + sh 'pnpm install' + sh 'pnpm run build' + } + } + + // Stage 4: Run SonarQube PR analysis: Install scanner locally, get version, and execute. + stage('SonarQube Pull Request Analysis') { + steps { + script { + // 1. Get the short 8-character commit SHA for project versioning + def commitShaShort = sh(returnStdout: true, script: 'git rev-parse --short=8 HEAD').trim() + echo "Commit SHA (short) is: ${commitShaShort}" + + // --- 2. MANUALLY INSTALL THE SONAR SCANNER CLI LOCALLY --- + sh """ + echo "Manually downloading and installing Sonar Scanner CLI..." + curl -sS -o sonar-scanner.zip \ + "https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.7.0.2747.zip" + unzip -o -q sonar-scanner.zip -d . + """ + + // 3. Find the extracted directory name and capture the full absolute executable path. + def scannerBinPath = sh( + returnStdout: true, + script: ''' + SCANNER_DIR=$(find . -maxdepth 1 -type d -name "sonar-scanner*" | head -n 1) + # Get the full absolute path to the executable file + echo \$(pwd)/\${SCANNER_DIR}/bin/sonar-scanner + ''' + ).trim() + + echo "Scanner executable path captured: ${scannerBinPath}" + + // 4. Use withSonarQubeEnv to set up the secure variables (HOST and TOKEN) + withSonarQubeEnv(installationName: 'SonarQube-Server') { + // 5. Execute the scanner using the Groovy variable directly with PR parameters. + sh """ + echo "Starting SonarQube Pull Request Analysis for PR #${PR_KEY}" + + '${scannerBinPath}' \\ + -Dsonar.projectVersion=${commitShaShort} \\ + -Dsonar.sources=. \\ + -Dsonar.pullrequest.key=${PR_KEY} \\ + -Dsonar.pullrequest.branch=${PR_BRANCH} \\ + -Dsonar.pullrequest.base=${PR_BASE} + + # SONAR_HOST_URL and SONAR_TOKEN are automatically passed as environment variables + # by the withSonarQubeEnv block. + """ + } + } + } + } + } +} diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index e7a614d0..00000000 --- a/Jenkinsfile +++ /dev/null @@ -1,70 +0,0 @@ -pipeline { - agent any - - environment { - NODE_VERSION = '20.17.0' - PR_NUMBER = "${env.CHANGE_ID}" // PR number comes from webhook payload - IMAGE_TAG="ghcr.io/gitroomhq/postiz-app-pr:${env.CHANGE_ID}" - } - - stages { - stage('Checkout Repository') { - steps { - checkout scm - } - } - - stage('Check Node.js and npm') { - steps { - script { - sh "node -v" - sh "npm -v" - } - } - } - - stage('Install Dependencies') { - steps { - sh 'npm ci' - } - } - - stage('Build Project') { - steps { - sh 'npm run build' - } - } - - stage('Build and Push Docker Image') { - when { - expression { return env.CHANGE_ID != null } // Only run if it's a PR - } - steps { - withCredentials([string(credentialsId: 'gh-pat', variable: 'GITHUB_PASS')]) { - // Docker login step - sh ''' - echo "$GITHUB_PASS" | docker login ghcr.io -u "egelhaus" --password-stdin - ''' - // Build Docker image - sh ''' - docker build -f Dockerfile.dev -t $IMAGE_TAG . - ''' - // Push Docker image to GitHub Container Registry - sh ''' - docker push $IMAGE_TAG - ''' - } - } - } - } - post { - success { - echo 'Build completed successfully!' - - } - failure { - echo 'Build failed!' - - } - } -} diff --git a/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts b/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts index c70b7e91..66415b75 100644 --- a/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts +++ b/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts @@ -26,6 +26,9 @@ import { } from '@gitroom/backend/services/auth/permissions/permission.exception.class'; import { VideoDto } from '@gitroom/nestjs-libraries/dtos/videos/video.dto'; import { VideoFunctionDto } from '@gitroom/nestjs-libraries/dtos/videos/video.function.dto'; +import { UploadDto } from '@gitroom/nestjs-libraries/dtos/media/upload.dto'; +import axios from 'axios'; +import { Readable } from 'stream'; @ApiTags('Public API') @Controller('/public/v1') @@ -56,6 +59,37 @@ export class PublicIntegrationsController { ); } + @Post('/upload-from-url') + async uploadFromUrl( + @GetOrgFromRequest() org: Organization, + @Body() body: UploadDto + ) { + const response = await axios.get(body.url, { + responseType: 'arraybuffer', + }); + + const buffer = Buffer.from(response.data); + + const getFile = await this.storage.uploadFile({ + buffer, + mimetype: 'image/jpeg', + size: buffer.length, + path: '', + fieldname: '', + destination: '', + stream: new Readable(), + filename: '', + originalname: '', + encoding: '', + }); + + return this._mediaService.saveFile( + org.id, + getFile.originalname, + getFile.path + ); + } + @Get('/find-slot/:id') async findSlotIntegration( @GetOrgFromRequest() org: Organization, diff --git a/libraries/nestjs-libraries/src/dtos/media/upload.dto.ts b/libraries/nestjs-libraries/src/dtos/media/upload.dto.ts new file mode 100644 index 00000000..4704c094 --- /dev/null +++ b/libraries/nestjs-libraries/src/dtos/media/upload.dto.ts @@ -0,0 +1,9 @@ +import { IsDefined, IsString, Validate } from 'class-validator'; +import { ValidUrlExtension } from '@gitroom/helpers/utils/valid.url.path'; + +export class UploadDto { + @IsString() + @IsDefined() + @Validate(ValidUrlExtension) + url: string; +} diff --git a/libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts b/libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts index 549c250c..db2010ac 100644 --- a/libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts +++ b/libraries/react-shared-libraries/src/sentry/initialize.sentry.client.ts @@ -16,8 +16,8 @@ export const initializeSentryClient = (environment: string, dsn: string) => autoInject: false, }), ], - replaysSessionSampleRate: environment === 'development' ? 1.0 : 0.5, + replaysSessionSampleRate: 1.0, replaysOnErrorSampleRate: 1.0, - profilesSampleRate: environment === 'development' ? 1.0 : 0.45, + profilesSampleRate: environment === 'development' ? 1.0 : 0.75, }); diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..5f07148d --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,7 @@ +sonar.projectKey=gitroomhq_postiz-app_bd4cd369-af44-4d19-903b-c4bdb0b66022 +sonar.projectName=Postiz App +sonar.projectDescription=An Open-Source Social Media Scheduler + +# Scan Performace +sonar.javascript.node.maxspace=24576 +sonar.exclusions=**/node_modules/**,**/dist/**,**/build/**,**/coverage/**,**/*.spec.ts,**/*.test.ts,*.png