Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion src/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "dist/server.js",
"scripts": {
"start": "node dist/server.js",
"dev": "tsc --watch & nodemon dist/server.js",
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
"build": "tsc",
"test": "echo \"Error: no test specified\" && exit 1"
},
Expand All @@ -20,6 +20,7 @@
"@aws-sdk/lib-storage": "^3.513.0",
"@aws-sdk/s3-request-presigner": "^3.513.0",
"@supabase/supabase-js": "^2.33.2",
"api": "file:",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
Expand Down
152 changes: 117 additions & 35 deletions src/api/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ const app = express();
// Rate limiting configuration
const limiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
max: 1000,
legacyHeaders: false,
standardHeaders: true,
skip: (req) => {
Expand Down Expand Up @@ -1200,6 +1200,7 @@ app.patch('/api/profile', jwtCheck, async (req: Request, res: Response): Promise
}
});


// Business Profile Routes
app.post('/api/business-profile', jwtCheck, async (req: Request, res: Response): Promise<void> => {
try {
Expand Down Expand Up @@ -1285,69 +1286,103 @@ app.post('/api/business-profile', jwtCheck, async (req: Request, res: Response):
}
});

// Get business profile
// Get business profile by id or custom url
app.get('/api/business-profile/:id', optionalJwtCheck, async (req: Request, res: Response): Promise<void> => {
try {
const { id } = req.params;
const requestingUserId = req.auth?.payload?.sub;
// Safely extract the requesting user ID if available
const requestingUserId = req.auth?.payload?.sub || null;

// Get the business profile
const { data, error } = await supabase
console.log(`Business profile request for: ${id}, requesting user: ${requestingUserId || 'unauthenticated'}`);

if (!id) {
res.status(400).json({ error: 'Business Profile ID is required' });
return;
}

// First try to find by ID
let { data, error } = await supabase
.from('business_profiles')
.select(`
*,
profiles (
name,
avatar_url,
is_public
)
`)
.select(`*`)
.eq('id', id)
.single();
.maybeSingle();

if (error) {
console.error('Error fetching business profile:', error);
console.error('Error fetching business profile by id:', {
error,
message: error.message,
details: error.details
});
res.status(500).json({
error: 'Database Error',
message: 'Failed to fetch business profile'
error: 'Database error',
message: 'An error occurred while fetching the business profile',
details: error.message
});
return;
}

// If no error but also no data, it means no record was found
if (!data) {
res.status(404).json({
error: 'Not Found',
message: 'Business profile not found'
});
return;
}
// Then try to find by custom_url
const { data: customUrlData, error: customUrlError } = await supabase
.from('business_profiles')
.select(`*`)
.eq('custom_url', id)
.maybeSingle();

if (customUrlError) {
console.error('Error fetching business profile by custom_url:', customUrlError);
res.status(500).json({
error: 'Database error',
message: 'An error occurred while fetching the business profile'
});
return;
}
console.log("Custom URL DATA: ", customUrlData);

// Check if the profile is public or if the requester is the owner
if (!data.profiles.is_public && requestingUserId !== id) {
res.status(403).json({
error: 'Access Denied',
message: 'This business profile is private'
});
return;
if (customUrlData) {
res.status(200).json(customUrlData);
return;
}

if (!customUrlData) {
console.log(`Business profile not found for ID or custom_url: ${id}`);
res.status(404).json({
error: 'Business Profile not found',
message: 'This business profile does not exist or might be private.'
});
return;
}
}

res.json(data);
res.status(200).json(data);
} catch (error: any) {
console.error('Error in GET /api/business-profile/:id:', error);
console.error('Error fetching business profile by id:', {
message: error.message || 'Unknown error',
details: error.stack || '',
hint: '',
code: error.code || ''
});
res.status(500).json({
error: 'Server Error',
message: error.message || 'An unexpected error occurred'
error: 'Server error',
message: error.message || 'An unexpected error occurred'
});
}
});

// Update business profile
app.patch('/api/business-profile/:id', jwtCheck, async (req: Request, res: Response): Promise<void> => {
console.log("PATCHING BUSINESS PROFILE FROM SERVER");
try {
const { id } = req.params;
const updates = req.body;
const updates = req.body.profile;
const userId = req.auth?.payload.sub;

const imageUrls = req.body.imageUrls;

console.log('updates', updates);
console.log('imageUrls', imageUrls);

// Verify ownership
if (id !== userId) {
res.status(403).json({
Expand Down Expand Up @@ -1383,7 +1418,23 @@ app.patch('/api/business-profile/:id', jwtCheck, async (req: Request, res: Respo
.select()
.single();

// Update business images
if (imageUrls) {
const { error: existingImagesError } = await supabase
.from('business_images')
.insert(imageUrls.map((url: string) => ({
business_id: id,
url: url
})))
.select('*')

if (existingImagesError) {
console.log('Error updating business images:', existingImagesError);
}
}

if (error) {
console.log('Error updating business profile:', error);
console.error('Error updating business profile:', error);
res.status(500).json({
error: 'Database Error',
Expand All @@ -1402,6 +1453,37 @@ app.patch('/api/business-profile/:id', jwtCheck, async (req: Request, res: Respo
}
});

// Get business images
app.get('/api/business-profile/images/:id', optionalJwtCheck, async (req: Request, res: Response): Promise<void> => {
try {
console.log(`Fetching business images FROM SERVER: ${req.params.id}`);
const { id } = req.params;
const { data, error } = await supabase
.from('business_images')
.select('url')
.eq('business_id', id);

if (error) {
console.error('Error fetching business images:', error);
res.status(500).json({
error: 'Database error',
message: 'An error occurred while fetching the business images'
});
return;
}

// Transform data to return just an array of URLs
const urls = data.map(item => item.url);
res.status(200).json(urls);
} catch (error: any) {
console.error('Error fetching business images:', error);
res.status(500).json({
error: 'Server Error',
message: error.message || 'An unexpected error occurred'
});
}
});

if (!process.env.RHINO_COMPUTE_ENDPOINT || !process.env.RHINO_COMPUTE_KEY) {
throw new Error('Rhino Compute environment variables not configured');
}
Expand Down
44 changes: 22 additions & 22 deletions src/apps/design/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
"@auth0/auth0-react": "^2.2.4",
"@aws-sdk/client-s3": "^3.369.0",
"@aws-sdk/s3-request-presigner": "^3.369.0",
"@babel/core": "^7.25.8",
"@babel/preset-env": "^7.25.8",
"@babel/preset-react": "^7.25.7",
"@babel/preset-typescript": "^7.25.8",
"@emotion/react": "^11.11.0",
"@emotion/styled": "^11.11.0",
"@mantine/core": "^7.1.0",
Expand All @@ -21,49 +25,46 @@
"@stripe/stripe-js": "^5.5.0",
"@supabase/supabase-js": "^2.47.12",
"@tabler/icons-react": "^2.30.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.13.10",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/three": "^0.169.0",
"autoprefixer": "^10.4.20",
"axios": "^1.6.8",
"babel-loader": "^9.2.1",
"clean-webpack-plugin": "^4.0.0",
"compression": "^1.7.4",
"compression-webpack-plugin": "^10.0.0",
"copy-webpack-plugin": "^12.0.2",
"cors": "^2.8.5",
"css-loader": "^7.1.2",
"design": "file:",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express-rate-limit": "^7.5.0",
"file-loader": "^6.2.0",
"helmet": "^7.1.0",
"html-webpack-plugin": "^5.6.0",
"postcss": "^8.5.3",
"postcss-loader": "^8.1.1",
"react": "^18.2.0",
"react-colorful": "^5.6.1",
"react-dom": "^18.2.0",
"react-error-boundary": "^5.0.0",
"react-hot-toast": "^2.4.1",
"react-router-dom": "^6.22.3",
"style-loader": "^4.0.0",
"tailwindcss": "^3.4.17",
"three": "^0.162.0",
"three-mesh-bvh": "^0.7.0",
"uuid": "^9.0.1",
"webpack-bundle-analyzer": "^4.10.1",
"@babel/core": "^7.25.8",
"@babel/preset-env": "^7.25.8",
"@babel/preset-react": "^7.25.7",
"@babel/preset-typescript": "^7.25.8",
"babel-loader": "^9.2.1",
"css-loader": "^7.1.2",
"file-loader": "^6.2.0",
"postcss": "^8.5.3",
"postcss-loader": "^8.1.1",
"style-loader": "^4.0.0",
"ts-loader": "^9.5.1",
"typescript": "^5.7.2",
"uuid": "^9.0.1",
"webpack": "^5.95.0",
"webpack-bundle-analyzer": "^4.10.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.1.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.13.10",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/three": "^0.169.0",
"autoprefixer": "^10.4.20",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.2"
"webpack-dev-server": "^5.1.0"
},
"scripts": {
"start": "node server.js",
Expand All @@ -80,7 +81,6 @@
"postinstall": "npm run copy-shared",
"heroku-postbuild": "npm run copy-shared && npm run build:css && webpack --mode production"
},
"devDependencies": {},
"eslintConfig": {
"extends": [
"react-app"
Expand Down
2 changes: 1 addition & 1 deletion src/apps/design/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ app.set('trust proxy', 1);
// Apply rate limiting to all requests
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
max: 1000, // limit each IP to 100 requests per windowMs
standardHeaders: true,
legacyHeaders: false,
});
Expand Down
2 changes: 1 addition & 1 deletion src/apps/marketplace/components/dashboard/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const Dashboard: React.FC<DashboardProps> = ({ children }) => {
<AppShell
header={{ height: 60 }}
navbar={{
width: 240,
width: showDashboardNavbar ? 240 : 0,
breakpoint: 'sm',
collapsed: { mobile: !opened },
}}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { Avatar, Card, Group, Image, Text } from "@mantine/core";
import { Profile } from "@shared/types/Profile";
import { BusinessProfile, Profile } from "@shared/types/Profile";
import { useNavigate } from "react-router-dom";
import fallback from '@shared/assets/fallback.jpg';

interface MarketplaceCardProps {
image?: string
user?: Profile
user?: Profile
business?: BusinessProfile
name: string
price: number
onClick?: () => void
}

const MarketplaceCard: React.FC<MarketplaceCardProps> = ({ image, user, name, price, onClick }) => {
const MarketplaceCard: React.FC<MarketplaceCardProps> = ({ image, user, business, name, price, onClick }) => {
const navigate = useNavigate();
return (
<Card shadow='sm' padding='lg' radius='md' withBorder onClick={onClick} className="cursor-pointer">
Expand All @@ -22,12 +23,12 @@ const MarketplaceCard: React.FC<MarketplaceCardProps> = ({ image, user, name, pr
<Text fw={500}>{name}</Text>
<Group className='flex items-center gap-2 py-2 cursor-pointer' onClick={(e) => {
e.stopPropagation(); // Prevent triggering the card's onClick
navigate(`/profile/${user?.id}`);
navigate(`/profile/${business?.id || user?.id}`);
}}>
<Avatar src={user?.avatar_url} alt={user?.name} radius="xl" size="sm">
{user?.name?.charAt(0)}
<Avatar src={business?.logo || user?.avatar_url || ""} alt={business?.company_name || user?.name || ""} radius="xl" size="sm">
{business?.company_name?.charAt(0) || user?.name?.charAt(0)}
</Avatar>
<Text size='sm'>Produced by {user?.name || 'Unknown'}</Text>
<Text size='sm'>Produced by {business?.company_name || user?.name || 'Unknown'}</Text>
</Group>
<Text size='sm' className="font-semibold">${price}</Text>
</Card.Section>
Expand Down
Loading