diff --git a/backend/.env.development b/backend/.env.development index 8d07c55e..099fe4eb 100644 --- a/backend/.env.development +++ b/backend/.env.development @@ -16,12 +16,8 @@ SENTRY_DSN="ADD YOUR SENTRY DSN" PORT=8001 UPLOAD_PATH=./../tmp/uploads -CLOUDINARY_CLOUD_NAME="ADD YOUR CLOUDINARY INFO" -CLOUDINARY_API_KEY="ADD YOUR CLOUDINARY INFO" -CLOUDINARY_API_SECRET="ADD YOUR CLOUDINARY INFO" - -DIGITAL_OCEAN_KEY_ID= -DIGITAL_OCEAN_SECRET_ACCESS_KEY= +DIGITAL_OCEAN_BUCKET_ACCESS_KEY= +DIGITAL_OCEAN_BUCKET_SECRET_KEY= DIGITAL_OCEAN_BUCKET_NAME= DIGITAL_OCEAN_BUCKET_ORIGIN_ENDPOINT= DIGITAL_OCEAN_BUCKET_CDN_ENDPOINT= diff --git a/backend/package.json b/backend/package.json index 56a4b128..e844f782 100644 --- a/backend/package.json +++ b/backend/package.json @@ -9,7 +9,7 @@ "scripts": { "build": "tsc", "dev": "nodemon src/index.ts", - "start": "node dist/index.js", + "start": "node build/index.js", "test": "jest --detectOpenHandles --coverage" }, "dependencies": { @@ -19,7 +19,6 @@ "@sentry/profiling-node": "^8.26.0", "axios": "^1.7.4", "body-parser": "^1.20.2", - "cloudinary": "^2.4.0", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^16.4.5", diff --git a/backend/src/controllers/reviewFeedbackController.ts b/backend/src/controllers/reviewFeedbackController.ts index 305247c6..02d0d87a 100644 --- a/backend/src/controllers/reviewFeedbackController.ts +++ b/backend/src/controllers/reviewFeedbackController.ts @@ -1,10 +1,8 @@ import { Request, Response } from "express"; import * as reviewFeedbackService from "../services/reviewFeedback.service"; -import { uploadImage } from "../services/misc/image.service"; import { IReviewFeedback } from "../types"; -import { env } from "../utils/env"; import logger from "../config/loggingConfig"; export const getReviews = async (req: Request, res: Response) => { @@ -56,9 +54,10 @@ export const addReview = async (req: Request, res: Response) => { return res.status(400).json({ message: "Self review is prohibited" }); } - // image file handling + // image file handling (have to ts-ignore because tsc thinks the file can't have a location property, even though it can and does) const file = req.file; - const image = file ? await uploadImage(authUser.pi_uid, file, 'review-feedback') : ''; + //@ts-ignore + const image = file ? file.location : ''; const newReview = await reviewFeedbackService.addReviewFeedback(authUser, formData, image); logger.info(`Added new review by user ${authUser.pi_uid} for receiver ID ${newReview.review_receiver_id}`); diff --git a/backend/src/controllers/sellerController.ts b/backend/src/controllers/sellerController.ts index ffa3de5e..f955516f 100644 --- a/backend/src/controllers/sellerController.ts +++ b/backend/src/controllers/sellerController.ts @@ -1,6 +1,5 @@ import { Request, Response } from "express"; import * as sellerService from "../services/seller.service"; -import { uploadImage } from "../services/misc/image.service"; import logger from "../config/loggingConfig"; import { ISeller } from "../types"; @@ -76,11 +75,9 @@ export const registerSeller = async (req: Request, res: Response) => { return res.status(401).json({ message: "Unauthorized user" }); } - // image file handling - const file = req.file; - const image = file ? await uploadImage(authUser.pi_uid, file, 'seller-registration') : ''; - - formData.image = image; + // image file handling (have to ts-ignore because tsc thinks the file can't have a location property, even though it can and does) + //@ts-ignore + const image = req.file ? req.file.location : ''; const registeredSeller = await sellerService.registerOrUpdateSeller(authUser, formData, image); logger.info(`Registered or updated seller for user ${authUser.pi_uid}`); diff --git a/backend/src/controllers/userPreferencesController.ts b/backend/src/controllers/userPreferencesController.ts index 07faf505..c66cdd51 100644 --- a/backend/src/controllers/userPreferencesController.ts +++ b/backend/src/controllers/userPreferencesController.ts @@ -1,7 +1,6 @@ import { Request, Response } from "express"; import * as userSettingsService from "../services/userSettings.service"; -import { uploadImage } from "../services/misc/image.service"; import { IUserSettings } from "../types"; import logger from "../config/loggingConfig"; @@ -56,9 +55,10 @@ export const addUserPreferences = async (req: Request, res: Response) => { return res.status(401).json({ message: "Unauthorized user" }); } - // image file handling + // image file handling (have to ts-ignore because tsc thinks the file can't have a location property, even though it can and does) const file = req.file; - const image = file ? await uploadImage(authUser.pi_uid, file, 'user-preferences') : ''; + //@ts-ignore + const image = file ? file.location : ''; const userPreferences = await userSettingsService.addOrUpdateUserSettings(authUser, formData, image); logger.info(`Added or updated User Preferences for user with ID: ${authUser.pi_uid}`); diff --git a/backend/src/helpers/imageUploader.ts b/backend/src/helpers/imageUploader.ts deleted file mode 100644 index eb2d14bb..00000000 --- a/backend/src/helpers/imageUploader.ts +++ /dev/null @@ -1,47 +0,0 @@ -import cloudinary from "../utils/cloudinary"; - -import logger from '../config/loggingConfig'; - -export const uploadMultipleImages = async (files: any) => { - try { - const uploadedImages = []; - logger.info(`Starting upload of ${files.length} images`); - - for (const file of files) { - const result = await cloudinary.uploader.upload(file.path, { - folder: "uploads", - use_filename: true, - }); - logger.debug(`Uploaded image: ${result.secure_url}`); - uploadedImages.push(result.secure_url); - } - logger.info(`Successfully uploaded ${uploadedImages.length} images`); - return uploadedImages; - } catch (error: any) { - logger.error('Failed to upload multiple images to Cloudinary:', { - message: error.message, - config: error.config, - stack: error.stack - }); - throw new Error('Failed to upload images; please try again'); - } -}; - -export const uploadSingleImage = async (file: any) => { - try { - logger.info(`Starting upload of single image: ${file.path}`); - const result = await cloudinary.uploader.upload(file.path, { - folder: "uploads", - use_filename: true, - }); - logger.info(`Successfully uploaded single image: ${result.secure_url}`); - return result.secure_url; - } catch (error: any) { - logger.error(`Failed to upload single image to Cloudinary:`, { - message: error.message, - config: error.config, - stack: error.stack - }); - throw new Error('Failed to upload single image; please try again'); - } -}; diff --git a/backend/src/services/misc/image.service.ts b/backend/src/services/misc/image.service.ts deleted file mode 100644 index f4b7a5b0..00000000 --- a/backend/src/services/misc/image.service.ts +++ /dev/null @@ -1,22 +0,0 @@ -import cloudinary from '../../utils/cloudinary'; -import logger from '../../config/loggingConfig'; - -export const uploadImage = async (publicId: string, file: Express.Multer.File, folder: string) => { - try { - const result = await cloudinary.uploader.upload(file.path, { - folder: folder, - public_id: publicId, - resource_type: 'image', - overwrite: true - }); - logger.info('Image has been uploaded successfully'); - return result.secure_url; - } catch (error: any) { - logger.error('Failed to upload image:', { - message: error.message, - config: error.config, - stack: error.stack - }); - throw new Error('Failed to upload image; please try again later'); - } -}; \ No newline at end of file diff --git a/backend/src/utils/cloudinary.ts b/backend/src/utils/cloudinary.ts deleted file mode 100644 index 6ba97911..00000000 --- a/backend/src/utils/cloudinary.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { v2 as cloudinary } from 'cloudinary'; -import { env } from './env'; - -cloudinary.config({ - cloud_name: env.CLOUDINARY_CLOUD_NAME, - api_key: env.CLOUDINARY_API_KEY, - api_secret: env.CLOUDINARY_API_SECRET, -}); - -export default cloudinary; diff --git a/backend/src/utils/env.ts b/backend/src/utils/env.ts index c674c6cc..9107b1aa 100644 --- a/backend/src/utils/env.ts +++ b/backend/src/utils/env.ts @@ -9,8 +9,8 @@ export const env = { JWT_SECRET: process.env.JWT_SECRET || 'default_secret', DIGITAL_OCEAN_BUCKET_ORIGIN_ENDPOINT: process.env.DIGITAL_OCEAN_BUCKET_ORIGIN_ENDPOINT || '', DIGITAL_OCEAN_BUCKET_CDN_ENDPOINT: process.env.DIGITAL_OCEAN_BUCKET_CDN_ENDPOINT || '', - DIGITAL_OCEAN_KEY_ID: process.env.DIGITAL_OCEAN_KEY_ID || '', - DIGITAL_OCEAN_SECRET_ACCESS_KEY: process.env.DIGITAL_OCEAN_SECRET_ACCESS_KEY || '', + DIGITAL_OCEAN_BUCKET_ACCESS_KEY: process.env.DIGITAL_OCEAN_BUCKET_ACCESS_KEY || '', + DIGITAL_OCEAN_BUCKET_SECRET_KEY: process.env.DIGITAL_OCEAN_BUCKET_SECRET_KEY || '', DIGITAL_OCEAN_BUCKET_NAME: process.env.DIGITAL_OCEAN_BUCKET_NAME || '', LOG_LEVEL: process.env.LOG_LEVEL || '', PI_API_KEY: process.env.PI_API_KEY || '', @@ -23,9 +23,6 @@ export const env = { MONGODB_APP_DATABASE_NAME: process.env.MONGODB_APP_DATABASE_NAME || '', MONGODB_OPTION_PARAMS: process.env.MONGODB_OPTION_PARAMS || '', SENTRY_DSN: process.env.SENTRY_DSN || '', - CLOUDINARY_CLOUD_NAME: process.env.CLOUDINARY_CLOUD_NAME || '', - CLOUDINARY_API_KEY: process.env.CLOUDINARY_API_KEY || '', - CLOUDINARY_API_SECRET: process.env.CLOUDINARY_API_SECRET || '', DEVELOPMENT_URL: process.env.DEVELOPMENT_URL || '', PRODUCTION_URL: process.env.PRODUCTION_URL || '', CORS_ORIGIN_URL: process.env.CORS_ORIGIN_URL || '' diff --git a/backend/src/utils/multer.ts b/backend/src/utils/multer.ts index 14634bbc..96c1f5fd 100644 --- a/backend/src/utils/multer.ts +++ b/backend/src/utils/multer.ts @@ -12,8 +12,8 @@ const s3 = new S3({ endpoint: env.DIGITAL_OCEAN_BUCKET_ORIGIN_ENDPOINT, region: "us-east-1", credentials: { - accessKeyId: env.DIGITAL_OCEAN_KEY_ID, - secretAccessKey: env.DIGITAL_OCEAN_SECRET_ACCESS_KEY + accessKeyId: env.DIGITAL_OCEAN_BUCKET_ACCESS_KEY, + secretAccessKey: env.DIGITAL_OCEAN_BUCKET_SECRET_KEY } }); @@ -21,6 +21,7 @@ const storage = multerS3({ s3, bucket: env.DIGITAL_OCEAN_BUCKET_NAME, acl: 'public-read', + contentType: multerS3.AUTO_CONTENT_TYPE, key: function (request: any, file: any, callback: any) { callback(null, file.originalname); } diff --git a/backend/tsconfig.json b/backend/tsconfig.json index e5cfce6b..5ff8da17 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "es2016", "module": "commonjs", - "rootDir": ".", + "rootDir": "./src", "outDir": "./build", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, diff --git a/backend/yarn.lock b/backend/yarn.lock index f9f18a76..d173f54e 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -2846,14 +2846,6 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" -cloudinary@^2.4.0: - version "2.5.0" - resolved "https://registry.npmjs.org/cloudinary/-/cloudinary-2.5.0.tgz" - integrity sha512-gPkyylFpyBAXiErAnMgZBlpwztHuZoik/OTLQM9oswjlzYHLMtQGoh0oisPBQvVHQxHmBUjcgwsAi393HDedqQ== - dependencies: - lodash "^4.17.21" - q "^1.5.1" - co@^4.6.0: version "4.6.0" resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz" @@ -4320,11 +4312,6 @@ lodash.once@^4.0.0: resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz" integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== -lodash@^4.17.21: - version "4.17.21" - resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - logform@^2.6.0, logform@^2.6.1: version "2.6.1" resolved "https://registry.npmjs.org/logform/-/logform-2.6.1.tgz" @@ -4920,11 +4907,6 @@ pure-rand@^6.0.0: resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz" integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA== -q@^1.5.1: - version "1.5.1" - resolved "https://registry.npmjs.org/q/-/q-1.5.1.tgz" - integrity sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw== - qs@6.13.0, qs@^6.11.0: version "6.13.0" resolved "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz" diff --git a/frontend/.env.development b/frontend/.env.development index 7aa4898a..fa64833b 100644 --- a/frontend/.env.development +++ b/frontend/.env.development @@ -4,6 +4,7 @@ # Your environment-specific values should be set in your .env.local file, which is not tracked in version control. NODE_ENV=development +IMAGE_BUCKET_HOST=ADD-YOUR-IMAGE-HOST-HERE.com NEXT_PUBLIC_BACKEND_URL=http://localhost:8001 NEXT_PUBLIC_IMAGE_PLACEHOLDER_URL="ADD YOUR IMAGE PLACEHOLDER URL" NEXT_PUBLIC_SENTRY_DSN=https://e7a6a9d1d2ac4ab65091a8cb55535688@o4507779280732160.ingest.us.sentry.io/4507779383361536 diff --git a/frontend/.env.staging b/frontend/.env.staging index adabfaa5..0e599669 100644 --- a/frontend/.env.staging +++ b/frontend/.env.staging @@ -1,4 +1,5 @@ NODE_ENV=production +IMAGE_BUCKET_HOST=mapofpi-staging.sfo3.digitaloceanspaces.com NEXT_PUBLIC_BACKEND_URL=https://backend.mapofpi-heuwx64a8iqc29xo.staging.piappengine.com -NEXT_PUBLIC_SENTRY_DSN=https://b729b3a46999a192129cec0202930bba@o4507937361625088.ingest.us.sentry.io/4507937365819392 +NEXT_PUBLIC_SENTRY_DSN=https://af0c1f20356a70c1ffedf8bdde40992a@o4507986464276480.ingest.us.sentry.io/4507986486362112 NEXT_PUBLIC_PI_SDK_URL=https://staging-sdk.socialchainapp.com/pi-sdk.js diff --git a/frontend/next.config.mjs b/frontend/next.config.mjs index d8786def..0c107f0f 100644 --- a/frontend/next.config.mjs +++ b/frontend/next.config.mjs @@ -10,12 +10,6 @@ const nextConfig = { }, images: { remotePatterns: [ - { - protocol: 'https', - hostname: 'example.com', - port: '', - pathname: '/**', - }, { protocol: 'http', hostname: 'localhost', @@ -24,16 +18,10 @@ const nextConfig = { }, { protocol: 'https', - hostname: 'tse3.mm.bing.net', + hostname: process.env.IMAGE_BUCKET_HOST, port: '', pathname: '/**', }, - { - protocol: 'https', - hostname: 'res.cloudinary.com', - port: '', - pathname: '/**', - } ], }, async rewrites() { diff --git a/frontend/src/components/shared/Review/emojipicker.tsx b/frontend/src/components/shared/Review/emojipicker.tsx index 7d842520..b3f9478c 100644 --- a/frontend/src/components/shared/Review/emojipicker.tsx +++ b/frontend/src/components/shared/Review/emojipicker.tsx @@ -127,8 +127,8 @@ export default function EmojiPicker(props: any) { logger.warn('Unable to submit review; user not authenticated.'); toast.error(t('SHARED.VALIDATION.SUBMISSION_FAILED_USER_NOT_AUTHENTICATED')); } - } catch (error) { - logger.error('Error saving review:', { error }); + } catch (error: any) { + logger.error('Error saving review:', { error: error?.message }); } }; diff --git a/frontend/src/components/shared/sidebar/sidebar.tsx b/frontend/src/components/shared/sidebar/sidebar.tsx index c1d75ed9..e7c93cb8 100644 --- a/frontend/src/components/shared/sidebar/sidebar.tsx +++ b/frontend/src/components/shared/sidebar/sidebar.tsx @@ -2,6 +2,7 @@ import styles from './sidebar.module.css'; import { useTranslations } from 'next-intl'; import { useTheme } from 'next-themes'; +import dynamic from 'next/dynamic'; import Image from 'next/image'; import Link from 'next/link'; import { usePathname, useRouter } from 'next/navigation'; @@ -10,7 +11,6 @@ import { useRef, useState, useContext, useEffect } from 'react'; import { FaChevronDown } from 'react-icons/fa6'; import { toast } from 'react-toastify'; -import MapCenter from '../map/MapCenter'; import InfoModel from '@/components/shared/About/Info/Info'; import PrivacyPolicyModel from '@/components/shared/About/privacy-policy/PrivacyPolicy'; import TermsOfServiceModel from '@/components/shared/About/terms-of-service/TermsOfService'; @@ -51,6 +51,11 @@ function isLanguageMenuItem(item: MenuItem): item is LanguageMenuItem { } function Sidebar(props: any) { + // Dynamically import the MapCenter component + const MapCenter = dynamic(() => import('../map/MapCenter'), { + ssr: false, + }); + const t = useTranslations(); const pathname = usePathname(); const router = useRouter(); @@ -248,8 +253,8 @@ function Sidebar(props: any) { logger.info('User Settings saved successfully:', { data }); toast.success(t('SIDE_NAVIGATION.VALIDATION.SUCCESSFUL_PREFERENCES_SUBMISSION')); } - } catch (error) { - logger.error('Error saving user settings:', { error }); + } catch (error: any) { + logger.error('Error saving user settings:', { error: error?.message }); toast.error(t('SIDE_NAVIGATION.VALIDATION.UNSUCCESSFUL_PREFERENCES_SUBMISSION')); } } diff --git a/frontend/src/constants/types.ts b/frontend/src/constants/types.ts index 6ef67670..490da3f0 100644 --- a/frontend/src/constants/types.ts +++ b/frontend/src/constants/types.ts @@ -52,4 +52,4 @@ export interface IReviewFeedback { export type PartialUserSettings = Pick; // Combined interface representing a seller with selected user settings -export interface ISellerWithSettings extends ISeller, PartialUserSettings {} \ No newline at end of file +export interface ISellerWithSettings extends ISeller, PartialUserSettings {}