Skip to content

Commit e808c7e

Browse files
author
Ajit Kumar
committed
feat(sponsor)
1 parent cc8c1cf commit e808c7e

File tree

6 files changed

+195
-1
lines changed

6 files changed

+195
-1
lines changed

server/apis/sponsor.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
const { Router } = require('express');
2+
const { writeFile, mkdir } = require('node:fs/promises');
3+
const Sponsor = require('../entities/sponsor');
4+
const { resolve } = require('node:path');
5+
const { existsSync } = require('node:fs');
6+
const { google } = require('googleapis');
7+
8+
const router = Router();
9+
const androidpublisher = google.androidpublisher('v3');
10+
const sponsorImagesPath = resolve(__dirname, '../../data/sponsors');
11+
12+
router.get('/', async (req, res) => {
13+
const { page, limit } = req.query;
14+
15+
const rows = await Sponsor.get(Sponsor.safeColumns, [[Sponsor.STATUS, Sponsor.STATE_PURCHASED]], { page, limit });
16+
17+
res.send(rows);
18+
});
19+
20+
router.post('/', async (req, res) => {
21+
const { name, tier, email, image, public: show = 0, website, packageName, purchaseToken } = req.body;
22+
23+
if (!name) {
24+
return res.status(400).json({
25+
error: 'Missing required field: name',
26+
});
27+
}
28+
29+
if (!purchaseToken) {
30+
return res.status(400).json({
31+
error: 'Missing required field: purchaseToken',
32+
});
33+
}
34+
35+
if (!packageName) {
36+
return res.status(400).json({
37+
error: 'Missing required field: packageName',
38+
});
39+
}
40+
41+
try {
42+
const { data: purchase } = await androidpublisher.purchases.products.get({
43+
packageName,
44+
productId: tier,
45+
token: purchaseToken,
46+
});
47+
48+
const filename = `${crypto.randomUUID()}.png`;
49+
const path = resolve(sponsorImagesPath, filename);
50+
51+
if (!existsSync(sponsorImagesPath)) {
52+
await mkdir(sponsorImagesPath, { recursive: true });
53+
}
54+
55+
await writeFile(path, image.split(';base64,')[1], { encoding: 'base64' });
56+
await Sponsor.insert(
57+
[Sponsor.NAME, name],
58+
[Sponsor.TIER, tier],
59+
[Sponsor.EMAIL, email],
60+
[Sponsor.PUBLIC, show],
61+
[Sponsor.IMAGE, filename],
62+
[Sponsor.WEBSITE, website],
63+
[Sponsor.TOKEN, purchaseToken],
64+
[Sponsor.PACKAGE_NAME, packageName],
65+
[Sponsor.ORDER_ID, purchase.orderId],
66+
[Sponsor.STATUS, purchase.purchaseState],
67+
);
68+
69+
res.status(201).json({ message: 'Thank you for becoming a sponsor!' });
70+
} catch (error) {
71+
console.error('Error processing sponsorship:', error);
72+
res.status(403).json({ error: 'Purchase not valid' });
73+
}
74+
});
75+
76+
module.exports = router;

server/crons/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ const { CronJob } = require('cron');
22
const updateOrders = require('./updateOrders');
33
const updateEarnings = require('../lib/updateEarnings');
44
const cleanDb = require('./cleanDb');
5+
const updateSponsors = require('./updateSponsors');
56

67
const daily = new CronJob('0 1 * * *', async () => {
78
await updateOrders();
89
await cleanDb();
10+
await updateSponsors();
911
});
1012

1113
const monthly = new CronJob('0 0 16 * *', async () => {

server/crons/updateSponsors.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const { google } = require('googleapis');
2+
const Sponsor = require('../entities/sponsor');
3+
4+
const androidpublisher = google.androidpublisher('v3');
5+
6+
module.exports = async () => {
7+
const sponsors = await Sponsor.get(Sponsor.columns, []);
8+
9+
for (const sponsor of sponsors) {
10+
const { package_name: packageName, token, tier } = sponsor;
11+
12+
try {
13+
const { data: purchase } = await androidpublisher.purchases.products.get({
14+
token,
15+
packageName,
16+
productId: tier,
17+
});
18+
19+
await Sponsor.update(
20+
[
21+
[Sponsor.STATUS, purchase.purchaseState],
22+
[Sponsor.ORDER_ID, purchase.orderId],
23+
],
24+
[Sponsor.ID, sponsor.id],
25+
);
26+
} catch (error) {
27+
console.error(`Error updating sponsor ${sponsor.id}:`, error);
28+
}
29+
}
30+
};

server/entities/sponsor.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
const Entity = require('./entity');
2+
3+
const table = `create table if not exists sponsor (
4+
id integer primary key,
5+
name text not null,
6+
token text not null,
7+
email text not null,
8+
order_id text not null,
9+
amount integer,
10+
user_id integer,
11+
package_name text,
12+
website text,
13+
image text,
14+
tier_name text,
15+
tier_id text,
16+
public integer default 0,
17+
status integer default 2,
18+
created_at timestamp default current_timestamp,
19+
foreign key (user_id) references user(id) on delete set null
20+
);`;
21+
22+
class Sponsor extends Entity {
23+
ID = 'id';
24+
NAME = 'name';
25+
TIER = 'tier';
26+
TOKEN = 'token';
27+
EMAIL = 'email';
28+
IMAGE = 'image';
29+
AMOUNT = 'amount';
30+
STATUS = 'status';
31+
PUBLIC = 'public';
32+
USER_ID = 'user_id';
33+
WEBSITE = 'website';
34+
ORDER_ID = 'order_id';
35+
CREATED_AT = 'created_at';
36+
PACKAGE_NAME = 'package_name';
37+
38+
STATE_PURCHASED = 0;
39+
STATE_CANCELED = 1;
40+
STATE_PENDING = 2;
41+
42+
constructor() {
43+
super(table);
44+
}
45+
46+
get columns() {
47+
return [
48+
this.ID,
49+
this.NAME,
50+
this.TIER,
51+
this.TOKEN,
52+
this.EMAIL,
53+
this.IMAGE,
54+
this.AMOUNT,
55+
this.STATUS,
56+
this.PUBLIC,
57+
this.WEBSITE,
58+
this.USER_ID,
59+
this.ORDER_ID,
60+
this.CREATED_AT,
61+
this.PACKAGE_NAME,
62+
];
63+
}
64+
65+
get safeColumns() {
66+
return [this.ID, this.NAME, this.TIER, this.EMAIL, this.IMAGE, this.AMOUNT, this.STATUS, this.WEBSITE, this.CREATED_AT, this.PACKAGE_NAME];
67+
}
68+
}
69+
70+
module.exports = new Sponsor();

server/main.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ async function main() {
4343
},
4444
}),
4545
);
46-
app.use(express.json());
46+
app.use(
47+
express.json({
48+
limit: '50mb',
49+
}),
50+
);
4751

4852
app.get('/sitemap.xml', (_req, res) => {
4953
res.setHeader('Content-Type', 'application/xml');
@@ -97,6 +101,17 @@ async function main() {
97101
res.status(404).send({ error: 'File not found' });
98102
});
99103

104+
app.get('/sponsor/image/:filename', async (req, res) => {
105+
const { filename } = req.params;
106+
const imagePath = path.resolve(__dirname, '../data/sponsors', filename);
107+
108+
if (!fs.existsSync(imagePath)) {
109+
return res.status(404).json({ error: 'Image not found' });
110+
}
111+
112+
res.sendFile(imagePath);
113+
});
114+
100115
app.get('/manifest.json', (_req, res) => {
101116
res.sendFile(path.resolve(__dirname, './manifest.json'));
102117
});

server/routes/apis.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ apis.use('/otp', require('../apis/otp'));
1212
apis.use(['/comments', '/comment'], require('../apis/comment'));
1313
apis.use('/faqs', require('../apis/faqs'));
1414
apis.use('/admin', require('../apis/admin'));
15+
apis.use(['/sponsor', '/sponsors'], require('../apis/sponsor'));
1516
// apis.use('/completion', require('../apis/completion'));
1617

1718
apis.get('/status', (_req, res) => {

0 commit comments

Comments
 (0)