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 86b412f5..67f2b0f6 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 @@ -33,6 +33,9 @@ import axios from 'axios'; import { Readable } from 'stream'; import { lookup } from 'mime-types'; import * as Sentry from '@sentry/nestjs'; +import { checkAuth } from '@gitroom/nestjs-libraries/chat/auth.context'; +import { socialIntegrationList } from '@gitroom/nestjs-libraries/integrations/integration.manager'; +import { getValidationSchemas } from '@gitroom/nestjs-libraries/chat/validation.schemas.helper'; @ApiTags('Public API') @Controller('/public/v1') @@ -213,4 +216,42 @@ export class PublicIntegrationsController { body.params ); } + + @Get('/integration-settings/:id') + async getIntegrationSettings( + @GetOrgFromRequest() org: Organization, + @Param('providerName') providerName: string + ) { + const loadIntegration = await this._integrationService.getIntegrationById( + org.id, + providerName + ); + + const verified = + JSON.parse(loadIntegration.additionalSettings || '[]')?.find( + (p: any) => p?.title === 'Verified' + )?.value || false; + + const integration = socialIntegrationList.find( + (p) => p.identifier === loadIntegration.providerIdentifier + )!; + + if (!integration) { + return { + output: { rules: '', maxLength: 0, settings: {}, tools: [] as any[] }, + }; + } + + const maxLength = integration.maxLength(verified); + const schemas = !integration.dto + ? false + : getValidationSchemas()[integration.dto.name]; + + return { + output: { + maxLength, + settings: !schemas ? 'No additional settings required' : schemas, + }, + }; + } } diff --git a/apps/cli/SUPPORTED_FILE_TYPES.md b/apps/cli/SUPPORTED_FILE_TYPES.md new file mode 100644 index 00000000..e0610013 --- /dev/null +++ b/apps/cli/SUPPORTED_FILE_TYPES.md @@ -0,0 +1,305 @@ +# Supported File Types for Upload + +The Postiz CLI now correctly detects and uploads various media types. + +## How It Works + +The CLI automatically detects the MIME type based on the file extension: + +```bash +postiz upload video.mp4 +# ✅ Detected as: video/mp4 + +postiz upload image.png +# ✅ Detected as: image/png + +postiz upload audio.mp3 +# ✅ Detected as: audio/mpeg +``` + +## Supported File Types + +### Images + +| Extension | MIME Type | Supported | +|-----------|-----------|-----------| +| `.png` | `image/png` | ✅ Yes | +| `.jpg`, `.jpeg` | `image/jpeg` | ✅ Yes | +| `.gif` | `image/gif` | ✅ Yes | +| `.webp` | `image/webp` | ✅ Yes | +| `.svg` | `image/svg+xml` | ✅ Yes | +| `.bmp` | `image/bmp` | ✅ Yes | +| `.ico` | `image/x-icon` | ✅ Yes | + +**Examples:** +```bash +postiz upload photo.jpg +postiz upload logo.png +postiz upload animation.gif +postiz upload icon.svg +``` + +### Videos + +| Extension | MIME Type | Supported | +|-----------|-----------|-----------| +| `.mp4` | `video/mp4` | ✅ Yes | +| `.mov` | `video/quicktime` | ✅ Yes | +| `.avi` | `video/x-msvideo` | ✅ Yes | +| `.mkv` | `video/x-matroska` | ✅ Yes | +| `.webm` | `video/webm` | ✅ Yes | +| `.flv` | `video/x-flv` | ✅ Yes | +| `.wmv` | `video/x-ms-wmv` | ✅ Yes | +| `.m4v` | `video/x-m4v` | ✅ Yes | +| `.mpeg`, `.mpg` | `video/mpeg` | ✅ Yes | +| `.3gp` | `video/3gpp` | ✅ Yes | + +**Examples:** +```bash +postiz upload video.mp4 +postiz upload clip.mov +postiz upload recording.webm +postiz upload movie.mkv +``` + +### Audio + +| Extension | MIME Type | Supported | +|-----------|-----------|-----------| +| `.mp3` | `audio/mpeg` | ✅ Yes | +| `.wav` | `audio/wav` | ✅ Yes | +| `.ogg` | `audio/ogg` | ✅ Yes | +| `.aac` | `audio/aac` | ✅ Yes | +| `.flac` | `audio/flac` | ✅ Yes | +| `.m4a` | `audio/mp4` | ✅ Yes | + +**Examples:** +```bash +postiz upload podcast.mp3 +postiz upload song.wav +postiz upload audio.ogg +``` + +### Documents + +| Extension | MIME Type | Supported | +|-----------|-----------|-----------| +| `.pdf` | `application/pdf` | ✅ Yes | +| `.doc` | `application/msword` | ✅ Yes | +| `.docx` | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` | ✅ Yes | + +**Examples:** +```bash +postiz upload document.pdf +postiz upload report.docx +``` + +### Other Files + +For file types not listed above, the CLI uses: +- MIME type: `application/octet-stream` +- This is a generic binary file type + +## Usage Examples + +### Upload an Image + +```bash +postiz upload ./images/photo.jpg +``` + +Response: +```json +{ + "id": "upload-123", + "path": "https://cdn.postiz.com/uploads/photo.jpg", + "url": "https://cdn.postiz.com/uploads/photo.jpg" +} +``` + +### Upload a Video (MP4) + +```bash +postiz upload ./videos/promo.mp4 +``` + +Response: +```json +{ + "id": "upload-456", + "path": "https://cdn.postiz.com/uploads/promo.mp4", + "url": "https://cdn.postiz.com/uploads/promo.mp4" +} +``` + +### Upload and Use in Post + +```bash +# 1. Upload the file +RESULT=$(postiz upload video.mp4) +echo $RESULT + +# 2. Extract the path (you'll need jq or similar) +PATH=$(echo $RESULT | jq -r '.path') + +# 3. Use in a post +postiz posts:create \ + -c "Check out my video!" \ + -m "$PATH" \ + -i "tiktok-123" +``` + +### Upload Multiple Files + +```bash +# Upload images +postiz upload image1.jpg +postiz upload image2.png +postiz upload image3.gif + +# Upload videos +postiz upload video1.mp4 +postiz upload video2.mov +``` + +## What Changed (Fix) + +### Before (❌ Bug) + +```bash +postiz upload video.mp4 +# ❌ Was detected as: image/jpeg (WRONG!) +``` + +The problem: The CLI defaulted to `image/jpeg` for any unknown file type. + +### After (✅ Fixed) + +```bash +postiz upload video.mp4 +# ✅ Correctly detected as: video/mp4 + +postiz upload audio.mp3 +# ✅ Correctly detected as: audio/mpeg + +postiz upload document.pdf +# ✅ Correctly detected as: application/pdf +``` + +## Platform-Specific Notes + +### TikTok +- Supports: MP4, MOV, WEBM +- Recommended: MP4 + +### YouTube +- Supports: MP4, MOV, AVI, WMV, FLV, 3GP, WEBM +- Recommended: MP4 + +### Instagram +- Images: JPG, PNG +- Videos: MP4, MOV +- Recommended: MP4 for videos, JPG for images + +### Twitter/X +- Images: PNG, JPG, GIF, WEBP +- Videos: MP4, MOV +- Max video size: 512MB + +### LinkedIn +- Images: PNG, JPG, GIF +- Videos: MP4, MOV, AVI +- Documents: PDF, DOC, DOCX, PPT + +## Troubleshooting + +### "Upload failed: Unsupported file type" + +Some platforms may not accept certain file types. Check the platform's documentation. + +**Solution:** Convert the file to a supported format: + +```bash +# Convert video to MP4 +ffmpeg -i video.avi video.mp4 + +# Then upload +postiz upload video.mp4 +``` + +### File Size Limits + +Different platforms have different file size limits: + +- **Twitter/X**: Max 512MB for videos +- **Instagram**: Max 100MB for videos +- **TikTok**: Max 287.6MB for videos +- **YouTube**: Max 128GB (but 256GB for verified) + +### "MIME type mismatch" + +If you renamed a file with the wrong extension: + +```bash +# ❌ Wrong: PNG file renamed to .jpg +mv image.png image.jpg +postiz upload image.jpg # Might fail + +# ✅ Correct: Keep original extension +postiz upload image.png +``` + +## Testing File Upload + +```bash +# Set API key +export POSTIZ_API_KEY=your_key + +# Test image upload +postiz upload test-image.jpg + +# Test video upload +postiz upload test-video.mp4 + +# Test audio upload +postiz upload test-audio.mp3 +``` + +## Error Messages + +### File Not Found +``` +❌ ENOENT: no such file or directory +``` + +**Solution:** Check the file path is correct. + +### No Permission +``` +❌ EACCES: permission denied +``` + +**Solution:** Check file permissions: +```bash +chmod 644 your-file.mp4 +``` + +### Invalid API Key +``` +❌ Upload failed (401): Unauthorized +``` + +**Solution:** Set your API key: +```bash +export POSTIZ_API_KEY=your_key +``` + +## Summary + +✅ **30+ file types supported** +✅ **Automatic MIME type detection** +✅ **Images, videos, audio, documents** +✅ **Correct handling of MP4, MOV, MP3, etc.** +✅ **No more defaulting to JPEG!** + +**The upload bug is fixed!** 🎉 diff --git a/apps/cli/package.json b/apps/cli/package.json index b171f162..34afd55d 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "postiz", - "version": "1.0.0", + "version": "2.0.2", "description": "Postiz CLI - Command line interface for the Postiz social media scheduling API", "main": "dist/index.js", "bin": { @@ -10,7 +10,7 @@ "dev": "tsup --watch", "build": "tsup", "start": "node ./dist/index.js", - "publish": "tsup && pnpm publish --access public" + "publish": "tsup && pnpm publish --access public --no-git-checks" }, "files": [ "dist", diff --git a/apps/cli/src/api.ts b/apps/cli/src/api.ts index b9c9c7d6..c32c0d87 100644 --- a/apps/cli/src/api.ts +++ b/apps/cli/src/api.ts @@ -73,16 +73,48 @@ export class PostizAPI { async upload(file: Buffer, filename: string) { const formData = new FormData(); - const extension = filename.split('.').pop() || 'jpg'; + const extension = filename.split('.').pop()?.toLowerCase() || ''; - const type = - extension === 'png' - ? 'image/png' - : extension === 'jpg' || extension === 'jpeg' - ? 'image/jpeg' - : extension === 'gif' - ? 'image/gif' - : 'image/jpeg'; + // Determine MIME type based on file extension + const mimeTypes: Record = { + // Images + 'png': 'image/png', + 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'gif': 'image/gif', + 'webp': 'image/webp', + 'svg': 'image/svg+xml', + 'bmp': 'image/bmp', + 'ico': 'image/x-icon', + + // Videos + 'mp4': 'video/mp4', + 'mov': 'video/quicktime', + 'avi': 'video/x-msvideo', + 'mkv': 'video/x-matroska', + 'webm': 'video/webm', + 'flv': 'video/x-flv', + 'wmv': 'video/x-ms-wmv', + 'm4v': 'video/x-m4v', + 'mpeg': 'video/mpeg', + 'mpg': 'video/mpeg', + '3gp': 'video/3gpp', + + // Audio + 'mp3': 'audio/mpeg', + 'wav': 'audio/wav', + 'ogg': 'audio/ogg', + 'aac': 'audio/aac', + 'flac': 'audio/flac', + 'm4a': 'audio/mp4', + + // Documents + 'pdf': 'application/pdf', + 'doc': 'application/msword', + 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + }; + + const type = mimeTypes[extension] || 'application/octet-stream'; const blob = new Blob([file], { type }); formData.append('file', blob, filename); diff --git a/apps/cli/src/commands/posts.ts b/apps/cli/src/commands/posts.ts index feaef7f4..8131d0b9 100644 --- a/apps/cli/src/commands/posts.ts +++ b/apps/cli/src/commands/posts.ts @@ -118,10 +118,23 @@ export async function listPosts(args: any) { const config = getConfig(); const api = new PostizAPI(config); - const filters: any = {}; - if (args.page) filters.page = args.page; - if (args.limit) filters.limit = args.limit; - if (args.search) filters.search = args.search; + // Set default date range: last 30 days to 30 days in the future + const defaultStartDate = new Date(); + defaultStartDate.setDate(defaultStartDate.getDate() - 30); + + const defaultEndDate = new Date(); + defaultEndDate.setDate(defaultEndDate.getDate() + 30); + + // Only send fields that are in GetPostsDto + const filters: any = { + startDate: args.startDate || defaultStartDate.toISOString(), + endDate: args.endDate || defaultEndDate.toISOString(), + }; + + // customer is optional in the DTO + if (args.customer) { + filters.customer = args.customer; + } try { const result = await api.listPosts(filters); diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index d2dd4d7c..e59a4b7e 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -111,26 +111,27 @@ yargs(hideBin(process.argv)) 'List all posts', (yargs: Argv) => { return yargs - .option('page', { - alias: 'p', - describe: 'Page number', - type: 'number', - default: 1, - }) - .option('limit', { - alias: 'l', - describe: 'Number of posts per page', - type: 'number', - default: 10, - }) - .option('search', { - alias: 's', - describe: 'Search query', + .option('startDate', { + describe: 'Start date (ISO 8601 format). Default: 30 days ago', type: 'string', }) - .example('$0 posts:list', 'List all posts') - .example('$0 posts:list -p 2 -l 20', 'List posts with pagination') - .example('$0 posts:list -s "hello"', 'Search posts'); + .option('endDate', { + describe: 'End date (ISO 8601 format). Default: 30 days from now', + type: 'string', + }) + .option('customer', { + describe: 'Customer ID (optional)', + type: 'string', + }) + .example('$0 posts:list', 'List all posts (last 30 days to next 30 days)') + .example( + '$0 posts:list --startDate "2024-01-01T00:00:00Z" --endDate "2024-12-31T23:59:59Z"', + 'List posts for a specific date range' + ) + .example( + '$0 posts:list --customer "customer-id"', + 'List posts for a specific customer' + ); }, listPosts as any )