Skip to content
Merged
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
30 changes: 28 additions & 2 deletions firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,15 @@ service cloud.firestore {
&& profileId == request.auth.uid
&& isValidProfile(incoming())
&& incoming().createdAt == request.time
&& incoming().keys().hasOnly(['userId', 'name', 'photoURL', 'github', 'linkedin', 'bio', 'primaryRole', 'secondaryRoles', 'skills', 'canvas', 'status', 'squadId', 'eventId', 'roast', 'roastBrutal', 'roastMild', 'createdAt', 'updatedAt']);
&& incoming().keys().hasOnly(['userId', 'name', 'photoURL', 'github', 'linkedin', 'bio', 'primaryRole', 'secondaryRoles', 'skills', 'canvas', 'status', 'squadId', 'eventId', 'roast', 'roastBrutal', 'roastMild', 'createdAt', 'updatedAt', 'visibility']);

allow update: if isSignedIn()
&& isValidId(profileId)
&& isValidProfile(incoming())
&& incoming().userId == existing().userId
&& incoming().createdAt == existing().createdAt
&& (
(incoming().diff(existing()).affectedKeys().hasOnly(['skills', 'canvas', 'name', 'photoURL', 'github', 'linkedin', 'bio', 'primaryRole', 'secondaryRoles', 'status', 'squadId', 'eventId', 'updatedAt', 'roast', 'roastBrutal', 'roastMild']))
(incoming().diff(existing()).affectedKeys().hasOnly(['skills', 'canvas', 'name', 'photoURL', 'github', 'linkedin', 'bio', 'primaryRole', 'secondaryRoles', 'status', 'squadId', 'eventId', 'updatedAt', 'roast', 'roastBrutal', 'roastMild', 'visibility']))
||
false
);
Expand Down Expand Up @@ -152,5 +152,31 @@ service cloud.firestore {
allow create: if isSignedIn() && userId == request.auth.uid && isValidLike(incoming());
allow delete: if isSignedIn() && userId == request.auth.uid;
}

function isValidMessage(data) {
return data.keys().hasAll(['senderId', 'senderName', 'receiverId', 'receiverName', 'text', 'createdAt', 'read'])
&& data.keys().size() == 7
&& data.senderId == request.auth.uid
&& data.senderName is string && data.senderName.size() > 0 && data.senderName.size() <= 100
&& data.receiverId is string && data.receiverId.size() > 0 && data.receiverId.size() <= 128
&& data.receiverName is string && data.receiverName.size() > 0 && data.receiverName.size() <= 100
&& data.text is string && data.text.size() > 0 && data.text.size() <= 5000
&& data.read is bool;
}

match /messages/{messageId} {
allow create: if isSignedIn()
&& isValidId(messageId)
&& isValidMessage(incoming())
&& incoming().createdAt == request.time;

allow read, delete: if isSignedIn()
&& (resource.data.receiverId == request.auth.uid || resource.data.senderId == request.auth.uid);

allow update: if isSignedIn()
&& (resource.data.receiverId == request.auth.uid)
&& incoming().diff(existing()).affectedKeys().hasOnly(['read'])
&& incoming().read is bool;
}
}
}
45 changes: 45 additions & 0 deletions scripts/dump-profiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { cert, initializeApp } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
import * as dotenv from "dotenv";

dotenv.config();

const raw = process.env.FIREBASE_SERVICE_ACCOUNT;
if (!raw) {
console.error("Missing FIREBASE_SERVICE_ACCOUNT");
process.exit(1);
}

const parsed = JSON.parse(raw) as {
project_id?: string;
projectId?: string;
client_email?: string;
clientEmail?: string;
private_key?: string;
privateKey?: string;
};

const projectId = parsed.projectId ?? parsed.project_id;
const clientEmail = parsed.clientEmail ?? parsed.client_email;
const privateKey = parsed.privateKey ?? parsed.private_key;

const app = initializeApp({
credential: cert({
projectId,
clientEmail,
privateKey: privateKey?.replace(/\\n/g, "\n"),
}),
});

const db = getFirestore(app, process.env.VITE_FIREBASE_FIRESTORE_DATABASE_ID || "ai-studio-a1333439-9ab3-4356-9f79-ac211cc82b20");

async function main() {
const snap = await db.collection("profiles").get();
console.log(`Encontrados ${snap.size} perfis no Firestore:`);
snap.forEach(doc => {
const data = doc.data();
console.log(`ID: ${doc.id}, Nome: ${data.name || data.displayName || "Sem nome"}, Visibilidade: ${data.visibility}, Status: ${data.status}`);
});
}

main().catch(console.error);
26 changes: 21 additions & 5 deletions scripts/migrate-members-to-profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,28 @@ function initAdmin() {
const raw = process.env.FIREBASE_SERVICE_ACCOUNT;
if (raw) {
const parsed = JSON.parse(raw) as {
projectId: string;
clientEmail: string;
privateKey: string;
projectId?: string;
project_id?: string;
clientEmail?: string;
client_email?: string;
privateKey?: string;
private_key?: string;
};

const projectId = parsed.projectId ?? parsed.project_id;
const clientEmail = parsed.clientEmail ?? parsed.client_email;
const privateKey = parsed.privateKey ?? parsed.private_key;

if (!projectId || !clientEmail || !privateKey) {
throw new Error("Missing projectId, clientEmail, or privateKey in FIREBASE_SERVICE_ACCOUNT env var.");
}

return initializeApp({
credential: cert({ ...parsed, privateKey: parsed.privateKey.replace(/\\n/g, "\n") }),
credential: cert({
projectId,
clientEmail,
privateKey: privateKey.replace(/\\n/g, "\n"),
}),
});
}

Expand Down Expand Up @@ -91,7 +107,7 @@ function toProfileDoc(memberId: string, data: MemberDoc): Record<string, unknown

async function migrate(dryRun: boolean) {
const app = initAdmin();
const db = getFirestore(app);
const db = getFirestore(app, process.env.VITE_FIREBASE_FIRESTORE_DATABASE_ID || "ai-studio-a1333439-9ab3-4356-9f79-ac211cc82b20");

const membersSnap = await db.collection("members").get();

Expand Down
9 changes: 8 additions & 1 deletion src/features/discover/components/ProfilesGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ import ProfileCard from "@/shared/components/ui/ProfileCard";
interface ProfilesGridProps {
profiles: Profile[];
onProfileClick: (profile: Profile) => void;
onContactClick?: (profile: Profile) => void;
currentUserId?: string;
}

export function ProfilesGrid({ profiles, onProfileClick, currentUserId }: ProfilesGridProps) {
export function ProfilesGrid({
profiles,
onProfileClick,
onContactClick,
currentUserId,
}: ProfilesGridProps) {
if (profiles.length === 0) {
return <EmptyProfilesState />;
}
Expand All @@ -25,6 +31,7 @@ export function ProfilesGrid({ profiles, onProfileClick, currentUserId }: Profil
profile={p}
colorIndex={idx}
onClick={isOwn ? () => onProfileClick(p) : undefined}
onContactClick={onContactClick ? () => onContactClick(p) : undefined}
/>
);
})}
Expand Down
11 changes: 7 additions & 4 deletions src/features/discover/hooks/useProfilesRealtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ export function useProfilesRealtime(currentUserId?: string) {
collectionName: "profiles",
});

const profiles = useMemo(
() => (currentUserId ? sortProfiles(data, currentUserId) : []),
[data, currentUserId],
);
const profiles = useMemo(() => {
console.log("useProfilesRealtime: raw data from Firestore:", data);
console.log("useProfilesRealtime: currentUserId:", currentUserId);
const sorted = currentUserId ? sortProfiles(data, currentUserId) : [];
console.log("useProfilesRealtime: sorted profiles:", sorted);
return sorted;
}, [data, currentUserId]);

return { profiles, loading, error };
}
21 changes: 21 additions & 0 deletions src/features/discover/pages/DiscoverPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AnimatePresence, motion } from "motion/react";
import { useState } from "react";

import { AccessDeniedState } from "../components/AccessDeniedState";
import { DiscoverFilters } from "../components/DiscoverFilters";
Expand All @@ -12,6 +13,7 @@ import { useRoastProfile } from "../hooks/useRoastProfile";
import { useToast } from "../hooks/useToast";

import { useAuth } from "@/contexts/useAuth";
import { SendMessageModal } from "@/features/messages/components/SendMessageModal";

export default function DiscoverPage() {
const { user } = useAuth();
Expand All @@ -23,6 +25,8 @@ export default function DiscoverPage() {

const roast = useRoastProfile({ showToast });

const [contactTarget, setContactTarget] = useState<{ id: string; name: string } | null>(null);

return (
<motion.div
initial={{ opacity: 0, y: 20 }}
Expand All @@ -48,6 +52,12 @@ export default function DiscoverPage() {
roast.openProfile(profile);
}
}}
onContactClick={(profile) => {
setContactTarget({
id: profile.id || profile.userId!,
name: profile.name || "Operador Anônimo",
});
}}
/>
</div>
)}
Expand All @@ -64,6 +74,17 @@ export default function DiscoverPage() {
/>
)}
</AnimatePresence>

<AnimatePresence>
{contactTarget && (
<SendMessageModal
receiverId={contactTarget.id}
receiverName={contactTarget.name}
onClose={() => setContactTarget(null)}
onSuccess={() => showToast("Mensagem enviada com sucesso!", "info")}
/>
)}
</AnimatePresence>
</motion.div>
);
}
37 changes: 0 additions & 37 deletions src/features/guilda/components/GuildAvatar.tsx

This file was deleted.

31 changes: 0 additions & 31 deletions src/features/guilda/components/GuildHeader.tsx

This file was deleted.

Loading
Loading