Repository navigation
Connect GamerZ Hub database read load to Appwrite with verified fallback and sync #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| import prisma from '../config/database'; | ||
| import { appwriteService } from './appwrite.service'; | ||
| import { AppError } from '../utils/errors'; | ||
|
|
||
| export interface FreeFireProfileInput { | ||
|
|
@@ -77,7 +78,7 @@ export class FreeFireService { | |
| * Upsert Free Fire profile information | ||
| */ | ||
| async updateProfile(userId: string, data: FreeFireProfileInput) { | ||
| return (prisma as any).freeFireProfile.upsert({ | ||
| const profile = await (prisma as any).freeFireProfile.upsert({ | ||
| where: { userId }, | ||
| create: { | ||
| userId, | ||
|
|
@@ -104,7 +105,40 @@ export class FreeFireService { | |
| teammatePreference: data.teammatePreference, | ||
| isVerified: false, // Ensure users cannot self-set verified | ||
| }, | ||
| include: { | ||
| user: { | ||
| select: { | ||
| profile: { | ||
| select: { | ||
| username: true, | ||
| displayName: true, | ||
| avatar: true, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| }); | ||
|
|
||
| // Write-through sync to Appwrite teammate discovery read model | ||
| appwriteService.syncTeammateDiscovery({ | ||
| id: `freefire_${userId}`, | ||
| userId, | ||
| gameId: 'freefire', | ||
| rank: profile.rank || 'HEROIC', | ||
| playstyle: profile.playstyle || 'BALANCED', | ||
| language: profile.language || 'ENGLISH', | ||
| micPreference: profile.micPreference, | ||
| availability: profile.availability || 'EVENING', | ||
| reputationScore: profile.reputationScore || 5.0, | ||
| isVerified: false, | ||
| username: profile.user?.profile?.username || 'Gamer', | ||
| displayName: profile.user?.profile?.displayName || profile.user?.profile?.username || 'Gamer', | ||
| avatar: profile.user?.profile?.avatar || null, | ||
| updatedAt: new Date().toISOString(), | ||
| }).catch(err => console.warn('[Appwrite] Teammate discovery sync error for Free Fire:', err?.message)); | ||
|
|
||
| return profile; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -113,6 +147,49 @@ export class FreeFireService { | |
| async discoverTeammates(currentUserId: string, filters: TeammateFilterQuery) { | ||
| const currentUserProfile = await this.getProfile(currentUserId); | ||
|
|
||
| // 1. Appwrite Read Model Attempt | ||
| const cachedCandidates = await appwriteService.getTeammateCandidates('freefire', { | ||
| rank: filters.rank, | ||
| playstyle: filters.playstyle, | ||
| language: filters.language, | ||
| availability: filters.availability, | ||
| limit: filters.limit || 20, | ||
| }); | ||
|
|
||
| if (cachedCandidates && cachedCandidates.length > 0) { | ||
| // Filter out current user if returned in Appwrite results | ||
| const filteredCached = cachedCandidates.filter(c => c.userId !== currentUserId); | ||
| if (filteredCached.length > 0) { | ||
| return filteredCached.map(candidate => { | ||
| const matchDetails = this.calculateCompatibility(currentUserProfile, candidate); | ||
| return { | ||
| profile: { | ||
| userId: candidate.userId, | ||
|
Comment on lines
+163
to
+167
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Once Appwrite has any matching document, this early return skips both the PostgreSQL query and the lazy synchronization loop. Useful? React with 👍 / 👎. |
||
| rank: candidate.rank, | ||
| playstyle: candidate.playstyle, | ||
| language: candidate.language, | ||
| micPreference: candidate.micPreference, | ||
| availability: candidate.availability, | ||
| reputationScore: candidate.reputationScore, | ||
| isVerified: false, | ||
| user: { | ||
| id: candidate.userId, | ||
| profile: { | ||
| username: candidate.username, | ||
| displayName: candidate.displayName, | ||
| avatar: candidate.avatar, | ||
| }, | ||
| }, | ||
| }, | ||
| compatibility: matchDetails.score, | ||
| reasons: matchDetails.reasons, | ||
| isSelfReported: true, | ||
| }; | ||
| }).sort((a: any, b: any) => b.compatibility - a.compatibility); | ||
| } | ||
| } | ||
|
|
||
| // 2. Primary Source Fallback (Prisma PostgreSQL) | ||
| const candidates = await (prisma as any).freeFireProfile.findMany({ | ||
| where: { | ||
| userId: { not: currentUserId }, | ||
|
|
@@ -138,6 +215,26 @@ export class FreeFireService { | |
| }, | ||
| }); | ||
|
|
||
| // Asynchronously populate Appwrite cache | ||
| for (const c of candidates) { | ||
| appwriteService.syncTeammateDiscovery({ | ||
| id: `freefire_${c.userId}`, | ||
| userId: c.userId, | ||
| gameId: 'freefire', | ||
| rank: c.rank, | ||
| playstyle: c.playstyle, | ||
| language: c.language, | ||
| micPreference: c.micPreference, | ||
| availability: c.availability, | ||
| reputationScore: c.reputationScore, | ||
| isVerified: false, | ||
| username: c.user?.profile?.username || 'Gamer', | ||
| displayName: c.user?.profile?.displayName || c.user?.profile?.username || 'Gamer', | ||
| avatar: c.user?.profile?.avatar || null, | ||
| updatedAt: new Date().toISOString(), | ||
| }).catch(err => console.warn('[Appwrite] Lazy teammate sync warning:', err?.message)); | ||
| } | ||
|
|
||
| return candidates.map((candidate: any) => { | ||
| const matchDetails = this.calculateCompatibility(currentUserProfile, candidate); | ||
| return { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| import prisma from '../config/database'; | ||
| import { io } from '../index'; | ||
| import { steamService } from './steam.service'; | ||
| import { appwriteService } from './appwrite.service'; | ||
| import { AppError } from '../utils/errors'; | ||
|
|
||
| export interface SyncResult { | ||
|
|
@@ -61,6 +62,17 @@ export class GameSyncService { | |
| }, | ||
| }); | ||
|
|
||
| appwriteService.syncGameProfile({ | ||
| userId, | ||
| game: 'STEAM', | ||
|
Comment on lines
+66
to
+67
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The game-profile sync derives its Appwrite document ID as Useful? React with 👍 / 👎. |
||
| inGameUid: profile.steamId, | ||
| inGameName: profile.username, | ||
| level: profile.level, | ||
| verified: true, | ||
| syncStatus: 'SUCCESS', | ||
| updatedAt: new Date().toISOString(), | ||
| }).catch(err => console.warn('[Appwrite] Game profile sync warning on Steam sync:', err?.message)); | ||
|
|
||
| this.broadcastUpdate(userId, 'STEAM', updated); | ||
| return { success: true, platform: 'STEAM', gameAccount: updated }; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the normal
User.idvalues generated by this schema (36-character UUIDs),syncTeammateDiscoveryderives the Appwrite document ID asfreefire_${userId}, which is 45 characters. Appwrite custom document IDs are capped at 36 characters, so both the update and create attempts fail and are swallowed by the best-effort sync; with Appwrite configured, this new Free Fire read model will therefore never be populated.Useful? React with 👍 / 👎.