-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* chore: events migration and model * chore: latest events route * chore: proposal created event * chore: update created event * chore: proposal update created event wip * chore: remove username from events * chore: report event creation errors * chore: create vote event on voting * chore: fetch all user names and avatars (refactor pending) * feat: show events in home page * chore: catalyst profiles refactor (#1462) * chore: get dcl profile refactor * chore: useProfiles refactor * chore: minor refactors * refactor: remove profile refetch * chore: add todo * refactor: address pr comments * refactor: avatars (#1465) * refactor: remove blockie * refactor: avatar love * refactor: avatar sizes * refactor: remove avatar src * refactor: avatar sizes * feat: improve activity ticker styles * refactor: get profile uses * refactor: remove repeated type * refactor: renames to avatar type enums * feat: update event copies * refactor: move event types to shared folder * refactor: create update service * refactor: update update service * fix: import * feat: refetch events every minute * refactor: address PR comments * refactor: only report errors to rollbar in prod env * refactor: rename * chore: delete old events job * refactor: remove comment * chore: cache users profiles for 1h and shorten addresses for users without name * feat: user address on activity ticker avatar * refactor: address pr comments --------- Co-authored-by: Andy Espagnolo <[email protected]>
- Loading branch information
Showing
65 changed files
with
860 additions
and
441 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { Model } from 'decentraland-gatsby/dist/entities/Database/model' | ||
import { SQL, table } from 'decentraland-gatsby/dist/entities/Database/utils' | ||
|
||
import { Event } from '../../shared/types/events' | ||
|
||
export default class EventModel extends Model<Event> { | ||
static tableName = 'events' | ||
static withTimestamps = false | ||
static primaryKey = 'id' | ||
|
||
static async getLatest(): Promise<Event[]> { | ||
const query = SQL` | ||
SELECT * | ||
FROM ${table(EventModel)} | ||
WHERE created_at >= NOW() - INTERVAL '7 day' | ||
ORDER BY created_at DESC | ||
` | ||
const result = await this.namedQuery<Event>('get_latest_events', query) | ||
return result | ||
} | ||
|
||
static async deleteOldEvents() { | ||
const query = SQL` | ||
DELETE | ||
FROM ${table(EventModel)} | ||
WHERE created_at < NOW() - INTERVAL '7 day' | ||
` | ||
await this.namedQuery('delete_old_events', query) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { WithAuth, auth } from 'decentraland-gatsby/dist/entities/Auth/middleware' | ||
import handleAPI from 'decentraland-gatsby/dist/entities/Route/handle' | ||
import routes from 'decentraland-gatsby/dist/entities/Route/routes' | ||
|
||
import { EventsService } from '../services/events' | ||
import { validateProposalId, validateRequiredString } from '../utils/validations' | ||
|
||
export default routes((route) => { | ||
const withAuth = auth() | ||
route.get('/events', handleAPI(getLatestEvents)) | ||
route.post('/events/voted', withAuth, handleAPI(voted)) | ||
}) | ||
|
||
async function getLatestEvents() { | ||
return await EventsService.getLatest() | ||
} | ||
|
||
async function voted(req: WithAuth) { | ||
const user = req.auth! | ||
|
||
validateProposalId(req.body.proposalId) | ||
validateRequiredString('proposalTitle', req.body.proposalTitle) | ||
validateRequiredString('choice', req.body.choice) | ||
return await EventsService.voted(req.body.proposalId, req.body.proposalTitle, req.body.choice, user) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import crypto from 'crypto' | ||
|
||
import { addressShortener } from '../../helpers' | ||
import CacheService, { TTL_1_HS } from '../../services/CacheService' | ||
import { ErrorService } from '../../services/ErrorService' | ||
import { | ||
EventType, | ||
EventWithAuthor, | ||
ProposalCreatedEvent, | ||
UpdateCreatedEvent, | ||
VotedEvent, | ||
} from '../../shared/types/events' | ||
import { DEFAULT_AVATAR_IMAGE, getProfiles } from '../../utils/Catalyst' | ||
import { DclProfile } from '../../utils/Catalyst/types' | ||
import { ErrorCategory } from '../../utils/errorCategories' | ||
import EventModel from '../models/Event' | ||
|
||
export class EventsService { | ||
static async getLatest(): Promise<EventWithAuthor[]> { | ||
try { | ||
const latestEvents = await EventModel.getLatest() | ||
const addresses = latestEvents.map((event) => event.address) | ||
|
||
const addressesToProfile = await this.getAddressesToProfiles(addresses) | ||
|
||
const latestEventsWithAuthor: EventWithAuthor[] = [] | ||
for (const event of latestEvents) { | ||
const { address } = event | ||
latestEventsWithAuthor.push({ | ||
author: addressesToProfile[address].username || addressShortener(address), | ||
avatar: addressesToProfile[address].avatar, | ||
...event, | ||
}) | ||
} | ||
|
||
return latestEventsWithAuthor | ||
} catch (error) { | ||
ErrorService.report('Error fetching events', { error, category: ErrorCategory.Events }) | ||
return [] | ||
} | ||
} | ||
|
||
private static async getAddressesToProfiles(addresses: string[]) { | ||
try { | ||
const profiles = await this.getProfilesWithCache(addresses) | ||
return profiles.reduce((acc, profile) => { | ||
acc[profile.address] = profile | ||
return acc | ||
}, {} as Record<string, DclProfile>) | ||
} catch (error) { | ||
ErrorService.report('Error fetching profiles', { error, category: ErrorCategory.Events }) | ||
return addresses.reduce((acc, address) => { | ||
acc[address] = { address, avatar: DEFAULT_AVATAR_IMAGE, username: null, hasCustomAvatar: false } | ||
return acc | ||
}, {} as Record<string, DclProfile>) | ||
} | ||
} | ||
|
||
static async proposalCreated(proposal_id: string, proposal_title: string, address: string) { | ||
try { | ||
const proposalCreatedEvent: ProposalCreatedEvent = { | ||
id: crypto.randomUUID(), | ||
address, | ||
event_type: EventType.ProposalCreated, | ||
event_data: { proposal_id, proposal_title }, | ||
created_at: new Date(), | ||
} | ||
await EventModel.create(proposalCreatedEvent) | ||
} catch (error) { | ||
this.reportEventError(error as Error, EventType.ProposalCreated, { address, proposal_id, proposal_title }) | ||
} | ||
} | ||
|
||
static async updateCreated(update_id: string, proposal_id: string, proposal_title: string, address: string) { | ||
try { | ||
const updateCreatedEvent: UpdateCreatedEvent = { | ||
id: crypto.randomUUID(), | ||
address, | ||
event_type: EventType.UpdateCreated, | ||
event_data: { update_id, proposal_id, proposal_title }, | ||
created_at: new Date(), | ||
} | ||
await EventModel.create(updateCreatedEvent) | ||
} catch (error) { | ||
this.reportEventError(error as Error, EventType.UpdateCreated, { | ||
address, | ||
update_id, | ||
proposal_id, | ||
proposal_title, | ||
}) | ||
} | ||
} | ||
|
||
static async voted(proposal_id: string, proposal_title: string, choice: string, address: string) { | ||
try { | ||
const votedEvent: VotedEvent = { | ||
id: crypto.randomUUID(), | ||
address, | ||
event_type: EventType.Voted, | ||
event_data: { proposal_id, proposal_title, choice }, | ||
created_at: new Date(), | ||
} | ||
await EventModel.create(votedEvent) | ||
} catch (error) { | ||
this.reportEventError(error as Error, EventType.Voted, { address, proposal_id, proposal_title, choice }) | ||
} | ||
} | ||
|
||
private static reportEventError(error: Error, eventType: EventType, args: Record<string, unknown>) { | ||
ErrorService.report('Error creating event', { | ||
error, | ||
event_type: eventType, | ||
...args, | ||
category: ErrorCategory.Events, | ||
}) | ||
} | ||
|
||
static async deleteOldEvents() { | ||
try { | ||
await EventModel.deleteOldEvents() | ||
} catch (error) { | ||
ErrorService.report('Error deleting old events', { error, category: ErrorCategory.Events }) | ||
} | ||
} | ||
|
||
private static getProfileCacheKey(address: string) { | ||
const cacheKey = `profile-${address.toLowerCase()}` | ||
return cacheKey | ||
} | ||
|
||
static async getProfilesWithCache(addresses: string[]): Promise<DclProfile[]> { | ||
const profiles: DclProfile[] = [] | ||
const addressesToFetch: string[] = [] | ||
|
||
for (const address of addresses) { | ||
const cachedProfile = CacheService.get<DclProfile>(this.getProfileCacheKey(address)) | ||
if (cachedProfile) { | ||
profiles.push(cachedProfile) | ||
} else { | ||
addressesToFetch.push(address) | ||
} | ||
} | ||
|
||
if (addressesToFetch.length > 0) { | ||
const dclProfiles: DclProfile[] = await getProfiles(addressesToFetch) | ||
|
||
for (const dclProfile of dclProfiles) { | ||
CacheService.set(this.getProfileCacheKey(dclProfile.address), dclProfile, TTL_1_HS) | ||
profiles.push(dclProfile) | ||
} | ||
} | ||
|
||
return profiles | ||
} | ||
} |
Oops, something went wrong.