Skip to content

Commit 84fa34f

Browse files
authored
Merge pull request #218 from layerx-labs/dev
BEPRO 2.27
2 parents 5b71765 + 66b888b commit 84fa34f

File tree

16 files changed

+510
-29
lines changed

16 files changed

+510
-29
lines changed

.env.example

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,7 @@ GA_MEASURE_ID=
9090
# Admin > Data Streams > choose your stream > Measurement Protocol > Create
9191
GA_API_SECRET=
9292
# leave blank for https://www.google-analytics.com/mp/collect
93-
GA_BASEURL=https://www.google-analytics.com/mp/collect
93+
GA_BASEURL=https://www.google-analytics.com/mp/collect
94+
95+
96+
NEXT_INTERNAL_API_KEY=
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { Op, Sequelize, WhereOptions } from "sequelize";
2+
3+
import models from "src/db";
4+
import { generateUserProfileImage } from "src/modules/generate-images/user-profile-image";
5+
import { isIpfsEnvs } from "src/utils/ipfs-envs-verify";
6+
import logger from "src/utils/logger-handler";
7+
import ipfsService from "src/services/ipfs-service";
8+
import { HttpBadRequestError } from "src/types/errors";
9+
10+
export const name = "update-user-profile-image";
11+
export const schedule = "*/10 * * * *";
12+
export const description = "Generate User profile image for OG";
13+
export const author = "vhcsilva";
14+
15+
export async function action(query?: Record<string, string | boolean>) {
16+
if (!isIpfsEnvs) {
17+
logger.warn(`${name} Missing id, secret or baseURL, for IPFService`);
18+
return;
19+
}
20+
21+
if (query?.fromRoute && !query?.id) {
22+
logger.warn(`${name} Missing query params`, query);
23+
throw new HttpBadRequestError("Missing query params");
24+
}
25+
26+
const where: WhereOptions = {};
27+
28+
if (query?.id)
29+
where.id = +query.id;
30+
31+
const users = await models.users.findAll({
32+
where
33+
});
34+
35+
if (!users.length) {
36+
logger.info(`${name} No users to be updated`);
37+
return;
38+
}
39+
40+
for (const user of users) {
41+
try {
42+
const tasksWon = await models.issues.count({
43+
where: {
44+
state: "closed"
45+
},
46+
include: [
47+
{
48+
association: "merge_proposals",
49+
required: true,
50+
where: {
51+
contractId: {
52+
[Op.eq]: Sequelize.cast(Sequelize.col("issues.merged"), "integer")
53+
}
54+
},
55+
include: [
56+
{
57+
association: "deliverable",
58+
required: true,
59+
where: {
60+
userId: user.id
61+
}
62+
}
63+
]
64+
}
65+
]
66+
});
67+
const tasksOpened = await models.issues.count({
68+
where: {
69+
userId: user.id
70+
}
71+
});
72+
const acceptedProposals = await models.merge_proposals.count({
73+
where: Sequelize.where( Sequelize.fn("LOWER", Sequelize.col("creator")),
74+
"=",
75+
user.address?.toLowerCase())
76+
});
77+
78+
const card = await generateUserProfileImage({
79+
user,
80+
tasksWon,
81+
tasksOpened,
82+
acceptedProposals,
83+
});
84+
85+
const { hash } = await ipfsService.add(card);
86+
87+
if (!hash) {
88+
logger.warn(`${name} Failed to get hash from IPFS for user profile card ${user.address}`);
89+
return;
90+
} else
91+
await user.update({
92+
profileImage: hash,
93+
profileImageUpdatedAt: new Date()
94+
});
95+
96+
logger.debug(`${name} - profile image updated ${user.address}`);
97+
} catch(error) {
98+
logger.error(`${name} - failed ${user.address}`, {error});
99+
}
100+
}
101+
}
135 KB
Loading

src/assets/images/bepro-icon.png

5.15 KB
Loading

src/assets/images/bepro-logo.png

-10.2 KB
Loading
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
<html>
2+
<head>
3+
<meta charset="UTF-8">
4+
<title>{{handle}}</title>
5+
6+
<style>
7+
@font-face {
8+
font-family: "Space Grotesk";
9+
src: url({{fonts}}) format("woff2");
10+
font-weight: 300 800;
11+
}
12+
13+
* {
14+
margin: 0;
15+
padding: 0;
16+
box-sizing: border-box;
17+
}
18+
19+
20+
html {
21+
min-width: fit-content;
22+
min-height: fit-content;
23+
}
24+
25+
body {
26+
font-family: "Space Grotesk", sans-serif;
27+
font-feature-settings: "tnum" on, "lnum" on, "ss02" on, "ss03" on, "ss04" on;
28+
}
29+
30+
main {
31+
height: 670px;
32+
width: 1200px;
33+
padding: 80px 60px;
34+
background-image: url({{background}});
35+
background-repeat: no-repeat, repeat;
36+
background-color: #0D0F19;
37+
background-size: cover;
38+
color: white;
39+
}
40+
41+
header {
42+
display: flex;
43+
align-items: center;
44+
gap: 24px;
45+
margin-top: 50px;
46+
margin-bottom: 150px;
47+
}
48+
49+
header img {
50+
padding: 6px;
51+
border: 3px solid #4250e4;
52+
border-radius: 50%;
53+
height: 250px;
54+
}
55+
56+
header h1, h3 {
57+
margin: 0;
58+
}
59+
60+
header h1 {
61+
font-size: 60px;
62+
}
63+
64+
header h3 {
65+
font-size: 40px;
66+
color: rgba(255, 255, 255, 0.8);
67+
}
68+
69+
header .details {
70+
display: flex;
71+
flex-direction: column;
72+
gap: 12px
73+
}
74+
75+
.details p {
76+
font-size: 35px;
77+
color: rgba(255, 255, 255, 0.8);
78+
79+
overflow: hidden;
80+
text-overflow: ellipsis;
81+
display: -webkit-box;
82+
-webkit-line-clamp: 2;
83+
line-clamp: 2;
84+
word-wrap: break-word;
85+
-webkit-box-orient: vertical;
86+
}
87+
88+
footer {
89+
display: flex;
90+
align-items: center;
91+
justify-content: space-between;
92+
}
93+
94+
footer img {
95+
height: 80px;
96+
}
97+
98+
.statistics-container {
99+
display: flex;
100+
align-items: center;
101+
gap: 40px;
102+
}
103+
104+
.statistics {
105+
display: flex;
106+
flex-direction: column;
107+
}
108+
109+
.statistics .value {
110+
font-weight: 800;
111+
font-size: 38px;
112+
}
113+
114+
.statistics .label {
115+
font-weight: 300;
116+
font-size: 30px;
117+
text-transform: uppercase;
118+
color: rgba(255, 255, 255, 0.4);
119+
}
120+
</style>
121+
</head>
122+
123+
<body>
124+
<main>
125+
<header>
126+
{{#if avatar}}
127+
<img src="{{avatar}}" alt="{{handle}}">
128+
{{/if}}
129+
130+
<div class="details">
131+
<h1>{{primaryText}}</h1>
132+
133+
{{#if secondaryText}}
134+
<h3>{{secondaryText}}</h3>
135+
{{/if}}
136+
137+
<p>{{bio}}</p>
138+
</div>
139+
</header>
140+
141+
<footer>
142+
<div class="statistics-container">
143+
<div class="statistics">
144+
<span class="value">{{tasksWon}}</span>
145+
<span class="label">Tasks Won</span>
146+
</div>
147+
148+
<div class="statistics">
149+
<span class="value">{{tasksOpened}}</span>
150+
<span class="label">Tasks Opened</span>
151+
</div>
152+
153+
<div class="statistics">
154+
<span class="value">{{acceptedProposals}}</span>
155+
<span class="label">Accepted Proposals</span>
156+
</div>
157+
</div>
158+
159+
<img src="{{logo}}" />
160+
</footer>
161+
</main>
162+
</body>
163+
</html>

src/db/models/users.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,20 @@ export interface usersAttributes {
2121
isEmailConfirmed?: boolean;
2222
emailVerificationCode?: string;
2323
emailVerificationSentAt?: Date;
24-
totalPoints?: number;
2524
githubLink?: string;
2625
linkedInLink?: string;
26+
totalPoints?: number;
27+
about?: string;
28+
avatar?: string;
29+
twitterLink?: string;
30+
profileImage?: string;
31+
profileImageUpdatedAt?: Date;
32+
fullName?: string;
2733
}
2834

2935
export type usersPk = "id";
3036
export type usersId = users[usersPk];
31-
export type usersOptionalAttributes = "id" | "address" | "createdAt" | "updatedAt" | "handle" | "resetedAt" | "email" | "isEmailConfirmed" | "emailVerificationCode" | "emailVerificationSentAt" | "totalPoints" | "githubLink" | "linkedInLink";
37+
export type usersOptionalAttributes = "id" | "address" | "createdAt" | "updatedAt" | "handle" | "resetedAt" | "email" | "isEmailConfirmed" | "emailVerificationCode" | "emailVerificationSentAt" | "githubLink" | "linkedInLink" | "totalPoints" | "about" | "twitterLink" | "avatar" | "profileImage" | "profileImageUpdatedAt" | "fullName";
3238
export type usersCreationAttributes = Optional<usersAttributes, usersOptionalAttributes>;
3339

3440
export class users extends Model<usersAttributes, usersCreationAttributes> implements usersAttributes {
@@ -42,9 +48,15 @@ export class users extends Model<usersAttributes, usersCreationAttributes> imple
4248
isEmailConfirmed?: boolean;
4349
emailVerificationCode?: string;
4450
emailVerificationSentAt?: Date;
45-
totalPoints?: number;
4651
githubLink?: string;
4752
linkedInLink?: string;
53+
totalPoints?: number;
54+
about?: string;
55+
twitterLink?: string;
56+
avatar?: string;
57+
profileImage?: string;
58+
profileImageUpdatedAt?: Date;
59+
fullName?: string;
4860

4961
// users hasMany comments via userId
5062
comments!: comments[];
@@ -195,16 +207,40 @@ export class users extends Model<usersAttributes, usersCreationAttributes> imple
195207
type: DataTypes.DATE,
196208
allowNull: true
197209
},
210+
githubLink: {
211+
type: DataTypes.STRING(255),
212+
allowNull: true
213+
},
214+
linkedInLink: {
215+
type: DataTypes.STRING(255),
216+
allowNull: true
217+
},
198218
totalPoints: {
199219
type: DataTypes.DOUBLE,
200220
allowNull: true,
201221
defaultValue: 0
202222
},
203-
githubLink: {
223+
about: {
224+
type: DataTypes.STRING(512),
225+
allowNull: true
226+
},
227+
twitterLink: {
204228
type: DataTypes.STRING(255),
205229
allowNull: true
206230
},
207-
linkedInLink: {
231+
avatar: {
232+
type: DataTypes.STRING(255),
233+
allowNull: true
234+
},
235+
profileImage: {
236+
type: DataTypes.STRING(255),
237+
allowNull: true
238+
},
239+
profileImageUpdatedAt: {
240+
type: DataTypes.DATE,
241+
allowNull: true
242+
},
243+
fullName: {
208244
type: DataTypes.STRING(255),
209245
allowNull: true
210246
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { NextFunction, Request, Response } from "express";
2+
3+
const { NEXT_INTERNAL_API_KEY } = process.env;
4+
5+
const blockedActions = [
6+
"UpdateUserProfileImage"
7+
];
8+
9+
export function internalApiKey(req: Request, res: Response, next: NextFunction) {
10+
const isBlockedAction = blockedActions.some(action => req.url.toLowerCase().includes(action.toLowerCase()));
11+
12+
if (!isBlockedAction) {
13+
next();
14+
return;
15+
}
16+
17+
const headerKey = req.headers["internal-api-key"];
18+
19+
if (!headerKey || headerKey !== NEXT_INTERNAL_API_KEY) {
20+
res.status(403);
21+
res.end();
22+
return;
23+
}
24+
25+
next();
26+
}

0 commit comments

Comments
 (0)