Skip to content

Commit

Permalink
Add supporter dialog
Browse files Browse the repository at this point in the history
  • Loading branch information
vicb committed Mar 27, 2024
1 parent 205cfd7 commit 266eea4
Show file tree
Hide file tree
Showing 15 changed files with 202 additions and 10 deletions.
52 changes: 52 additions & 0 deletions apps/fetcher/src/app/misc/buy-coffee.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Buy me a coffee
//
// https://developers.buymeacoffee.com/

import { SecretKeys, fetchResponse } from '@flyxc/common';

export type Supporters = {
// visible supporters
names: Set<string>;
// all supporters
numSupporters: number;
// total amount
totalAmount: number;
};

export async function fetchSupporters(): Promise<Supporters> {
const supporters: Supporters = {
names: new Set<string>(),
numSupporters: 0,
totalAmount: 0,
};
let url = `https://developers.buymeacoffee.com/api/v1/supporters`;
try {
while (url) {
const response = await fetchResponse(url, {
headers: {
Authorization: `Bearer ${SecretKeys.BUY_ME_A_COFFEE_TOKEN}`,
},
});
if (response.ok) {
const data = await response.json();
const users = data.data;
for (const user of users) {
if (user.amount <= 0) {
continue;
}
if (user.support_visibility === 1 && user.payer_name) {
supporters.names.add(user.payer_name);
}
supporters.numSupporters++;
supporters.totalAmount += user.support_coffees * user.support_coffee_price;
}
url = data.next_page_url;
} else {
url = null;
}
}
} catch (error) {
console.error('Buy me a coffee API error!', error);
}
return supporters;
}
2 changes: 2 additions & 0 deletions apps/fetcher/src/app/state/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const EXPORT_FILE_SEC = 4 * 3600;
export const EXPORT_ARCHIVE_SEC = 24 * 3600;
export const FULL_SYNC_SEC = 24 * 3600;
export const PARTIAL_SYNC_SEC = 10 * 60;
export const SUPPORTER_SYNC_SEC = 2 * 3600;

// Create the initial empty state.
export function createInitState(): protos.FetcherState {
Expand All @@ -41,6 +42,7 @@ export function createInitState(): protos.FetcherState {
nextFullSyncSec: nowSec + FULL_SYNC_SEC,
nextExportSec: nowSec + EXPORT_FILE_SEC,
nextArchiveExportSec: nowSec + EXPORT_ARCHIVE_SEC,
nextSupporterSyncSec: nowSec + SUPPORTER_SYNC_SEC,

memRssMb: 0,
memHeapMb: 0,
Expand Down
16 changes: 15 additions & 1 deletion apps/fetcher/src/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
removeBeforeFromLiveTrack,
removeDeviceFromLiveTrack,
} from '@flyxc/common';
import { getDatastore, getRedisClient } from '@flyxc/common-node';
import { getDatastore, getRedisClient, pushListCap } from '@flyxc/common-node';
import { Datastore } from '@google-cloud/datastore';
import { program } from 'commander';
import { ChainableCommander } from 'ioredis';
Expand All @@ -25,10 +25,12 @@ import {
PERIODIC_STATE_PATH,
restoreState,
SHUTDOWN_STATE_PATH,
SUPPORTER_SYNC_SEC,
} from './app/state/state';
import { syncFromDatastore } from './app/state/sync';
import { disconnectOgnClient, resfreshTrackers } from './app/trackers/refresh';
import { resfreshUfoFleets } from './app/ufos/refresh';
import { fetchSupporters } from './app/misc/buy-coffee';

const redis = getRedisClient();

Expand Down Expand Up @@ -124,6 +126,18 @@ async function tick(state: protos.FetcherState, datastore: Datastore) {
addExportLogs(pipeline, success, state.lastTickSec);
}

// Sync supporters.
if (state.lastTickSec > state.nextSupporterSyncSec) {
const supporters = await fetchSupporters();
pipeline
.del(Keys.supporterNames)
.set(Keys.supporterNum, supporters.numSupporters)
.set(Keys.supporterAmount, supporters.totalAmount);
pushListCap(pipeline, Keys.supporterNames, Array.from(supporters.names), 50, 50);

state.nextSupporterSyncSec = state.lastTickSec + SUPPORTER_SYNC_SEC;
}

await pipeline.exec();
}

Expand Down
8 changes: 6 additions & 2 deletions apps/fxc-front/src/app/components/ui/main-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import './about-modal';
import './live-modal';
import './pref-modal';
import './track-modal';
import './supporter-modal';
import { getApiKey } from '../../apikey';
import { geocode } from '@esri/arcgis-rest-geocoding';
import { setDefaultRequestOptions } from '@esri/arcgis-rest-request';
Expand Down Expand Up @@ -166,8 +167,11 @@ export class MainMenu extends connect(store)(LitElement) {
window.open(`https://www.windy.com/plugins/windy-plugin-sounding?lat=${lat}&lon=${lon}`, '_blank');
}

private handleSupport() {
window.open(`https://www.buymeacoffee.com/vic.b`, '_blank');
private async handleSupport() {
const modal = await modalController.create({
component: 'supporter-modal',
});
await modal.present();
}

private async handlePlanner() {
Expand Down
78 changes: 78 additions & 0 deletions apps/fxc-front/src/app/components/ui/supporter-modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { html, LitElement, TemplateResult } from 'lit';
import { customElement } from 'lit/decorators.js';

import { modalController } from '@ionic/core/components';
import { fetchResponse } from '@flyxc/common';

@customElement('supporter-modal')
export class SupporterModal extends LitElement {
private supporters = { names: [], amount: 0, number: 0 };

async connectedCallback(): Promise<void> {
super.connectedCallback();
try {
const response = await fetchResponse('/api/supporters.json');
if (response.ok) {
this.supporters = await response.json();
this.requestUpdate();
}
} catch (e) {
// do nothing
}
}

render(): TemplateResult {
return html`<ion-header>
<ion-toolbar color="light">
<ion-title>Support flyxc.app</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-text color="dark">
<p>
flyxc (formerly VisuGps) has always been
<a href="https://github.com/vicb/visugps" target="_blank">open-source</a> and free to use since 2007.
</p>
<p>The development effort represents hundreds of hours per year and hosting flyxc cost about $10 a month.</p>
<p>
If you can afford it, please consider supporting flyxc with a small donation by clicking the button below.
It will help keep me motivated to maintain and improve it.
</p>
</ion-text>
<a href="https://www.buymeacoffee.com/vic.b" target="_blank"
><img
src="https://cdn.buymeacoffee.com/buttons/default-orange.png"
alt="Buy Me A Coffee"
height="41"
width="174"
/></a>
<p>
<ion-text color="dark"
>Thanks to the ${this.supporters.number} supporters who have contributed a total of
$${this.supporters.amount}:
<ul>
${this.supporters.names.map((n) => html`<li>${n}</li>`)}
</ul>
</ion-text>
</p>
</ion-content>
<ion-footer>
<ion-toolbar color="light">
<ion-buttons slot="primary">
<ion-button @click=${this.dismiss}>Close</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-footer>`;
}

protected createRenderRoot(): HTMLElement {
return this;
}

private async dismiss(): Promise<void> {
const modal = await modalController.getTop();
await modal?.dismiss();
}
}
28 changes: 28 additions & 0 deletions apps/fxc-server/src/app/routes/supporters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Keys } from '@flyxc/common';
import { Request, Response, Router } from 'express';
import { Redis } from 'ioredis';

export function getSupportersRouter(redis: Redis): Router {
const router = Router();

// Returns the airspaces info for the first track in the group as JSON.
// Returns 404 if the info are not available (/not ready yet).
// eslint-disable-next-line @typescript-eslint/no-unused-vars
router.get('/supporters.json', async (req: Request, res: Response) => {
try {
const [names, number, amount] = (
await redis
.pipeline()
.lrange(Keys.supporterNames, 0, 100)
.get(Keys.supporterNum)
.get(Keys.supporterAmount)
.exec()
).map(([_, v]) => v);
return res.json({ names, number, amount });
} catch (error) {
return res.sendStatus(500);
}
});

return router;
}
4 changes: 3 additions & 1 deletion apps/fxc-server/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { getTrackRouter } from './app/routes/track';
import { getWaypointRouter } from './app/routes/waypoints';
import { getZoleoRouter } from './app/routes/zoleo';
import { environment } from './environments/environment';
import { getSupportersRouter } from './app/routes/supporters';

const redis = getRedisClient();

Expand Down Expand Up @@ -80,7 +81,8 @@ app
.use('/api/live', getTrackerRouter(redis, datastore))
.use('/api/track', getTrackRouter(datastore))
.use('/api/waypoint', getWaypointRouter())
.use('/api/zoleo', getZoleoRouter(redis));
.use('/api/zoleo', getZoleoRouter(redis))
.use('/api', getSupportersRouter(redis));

const port = process.env.PORT || 8080;
app.listen(port, () => console.info(`Started server on port ${port}.`));
4 changes: 2 additions & 2 deletions libs/common-node/src/lib/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ export function pushListCap(
list: Array<string | number>,
capacity: number,
maxLength = 400,
): void {
): ChainableCommander {
const len = list.length;
if (len == 0 || capacity == 0) {
return;
}
const elements = list.slice(len - capacity, len);
pipeline
return pipeline
.lpush(key, ...elements.map((v) => v.toString().substring(0, maxLength)))
.ltrim(key, 0, Math.max(0, capacity - 1));
}
1 change: 1 addition & 0 deletions libs/common/src/lib/keys.ts.dist
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ export enum SecretKeys {
ZOLEO_PUSH_USER = `...`,
ZOLEO_PUSH_PWD = `...`,
XCONTEXT_JWT = `...`,
BUY_ME_A_COFFE_TOKEN = `...`,
}
5 changes: 5 additions & 0 deletions libs/common/src/lib/redis-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ export enum Keys {
// [List]
elevationNumRetrieved = 'f:elev:retrieved',

// [List]
supporterNames = 'f:supporters',
supporterNum = 'f:supporters:num',
supporterAmount = 'f:supporters:amount',

// [List]
proxyInreach = 'p:inreach:logs',

Expand Down
1 change: 1 addition & 0 deletions libs/common/src/protos/fetcher-state.proto
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ message FetcherState {
int64 next_full_sync_sec = 10;
int64 next_export_sec = 11;
int64 next_archive_export_sec = 12;
int64 next_supporter_sync_sec = 20;

int64 mem_rss_mb = 13;
int64 mem_heap_mb = 14;
Expand Down
7 changes: 6 additions & 1 deletion libs/common/src/protos/fetcher-state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @generated by protobuf-ts 2.9.1 with parameter long_type_number,force_optimize_code_size
// @generated by protobuf-ts 2.9.4 with parameter long_type_number,force_optimize_code_size
// @generated from protobuf file "fetcher-state.proto" (syntax proto3)
// tslint:disable
//
Expand Down Expand Up @@ -78,6 +78,10 @@ export interface FetcherState {
* @generated from protobuf field: int64 next_archive_export_sec = 12;
*/
nextArchiveExportSec: number;
/**
* @generated from protobuf field: int64 next_supporter_sync_sec = 20;
*/
nextSupporterSyncSec: number;
/**
* @generated from protobuf field: int64 mem_rss_mb = 13;
*/
Expand Down Expand Up @@ -274,6 +278,7 @@ class FetcherState$Type extends MessageType<FetcherState> {
{ no: 10, name: 'next_full_sync_sec', kind: 'scalar', T: 3 /*ScalarType.INT64*/, L: 2 /*LongType.NUMBER*/ },
{ no: 11, name: 'next_export_sec', kind: 'scalar', T: 3 /*ScalarType.INT64*/, L: 2 /*LongType.NUMBER*/ },
{ no: 12, name: 'next_archive_export_sec', kind: 'scalar', T: 3 /*ScalarType.INT64*/, L: 2 /*LongType.NUMBER*/ },
{ no: 20, name: 'next_supporter_sync_sec', kind: 'scalar', T: 3 /*ScalarType.INT64*/, L: 2 /*LongType.NUMBER*/ },
{ no: 13, name: 'mem_rss_mb', kind: 'scalar', T: 3 /*ScalarType.INT64*/, L: 2 /*LongType.NUMBER*/ },
{ no: 14, name: 'mem_heap_mb', kind: 'scalar', T: 3 /*ScalarType.INT64*/, L: 2 /*LongType.NUMBER*/ },
{ no: 15, name: 'in_tick', kind: 'scalar', T: 8 /*ScalarType.BOOL*/ },
Expand Down
2 changes: 1 addition & 1 deletion libs/common/src/protos/live-track.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @generated by protobuf-ts 2.9.1 with parameter long_type_number,force_optimize_code_size
// @generated by protobuf-ts 2.9.4 with parameter long_type_number,force_optimize_code_size
// @generated from protobuf file "live-track.proto" (syntax proto3)
// tslint:disable
import { MessageType } from '@protobuf-ts/runtime';
Expand Down
2 changes: 1 addition & 1 deletion libs/common/src/protos/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @generated by protobuf-ts 2.9.1 with parameter long_type_number,force_optimize_code_size
// @generated by protobuf-ts 2.9.4 with parameter long_type_number,force_optimize_code_size
// @generated from protobuf file "proxy.proto" (syntax proto3)
// tslint:disable
import { MessageType } from '@protobuf-ts/runtime';
Expand Down
2 changes: 1 addition & 1 deletion libs/common/src/protos/track.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// @generated by protobuf-ts 2.9.1 with parameter long_type_number,force_optimize_code_size
// @generated by protobuf-ts 2.9.4 with parameter long_type_number,force_optimize_code_size
// @generated from protobuf file "track.proto" (syntax proto3)
// tslint:disable
import { MessageType } from '@protobuf-ts/runtime';
Expand Down

0 comments on commit 266eea4

Please sign in to comment.