Skip to content

Commit 004f89c

Browse files
committed
Merge branch 'master' of github.com:codecon-dev/codecodes-api
2 parents 5b29800 + 7907d73 commit 004f89c

18 files changed

+467
-67
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ node_modules
22
.env
33
dist
44
swagger.json
5-
uploads
5+
uploads
6+
.DS_Store

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"csv-parser": "^3.0.0",
1919
"dotenv": "^10.0.0",
2020
"express": "^4.17.1",
21+
"express-rate-limit": "^7.4.0",
2122
"luxon": "^3.3.0",
2223
"mongoose": "^5.13.7",
2324
"morgan": "^1.10.0",

scripts/tokens.js

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
require('dotenv').config()
2+
const mongoose = require('mongoose')
3+
const fs = require('fs').promises
4+
const path = require('path')
5+
const { Schema, model } = mongoose
6+
7+
// Define schemas
8+
const UserSchema = new Schema({
9+
userId: String,
10+
tag: String,
11+
username: String,
12+
score: Number,
13+
tokens: Array,
14+
softDeleted: Boolean
15+
})
16+
17+
const TokenSchema = new Schema({
18+
code: String,
19+
description: String,
20+
value: Number,
21+
decreaseValue: Number,
22+
minimumValue: Number,
23+
totalClaims: Number,
24+
remainingClaims: Number,
25+
claimedBy: Array,
26+
createdBy: String,
27+
createdAt: String,
28+
expireAt: String
29+
})
30+
31+
// Define models
32+
const UserModel = model('User', UserSchema)
33+
const TokenModel = model('Token', TokenSchema)
34+
35+
// List of target users
36+
const targetUsers = []
37+
38+
// Flag to determine whether to delete the tokens
39+
const shouldDeleteTokens = false // Set this to true if you want to delete the tokens
40+
41+
async function connectToDatabase() {
42+
const mongoAddress = `${process.env.MONGODB_URI}?retryWrites=true&w=majority`
43+
await mongoose.connect(mongoAddress, {
44+
useNewUrlParser: true,
45+
useUnifiedTopology: true,
46+
useFindAndModify: false
47+
})
48+
}
49+
50+
async function getExclusiveTokens() {
51+
try {
52+
await connectToDatabase()
53+
54+
const targetDate = new Date('2024-09-06T16:00:00.000Z')
55+
56+
// Get all tokens
57+
const allTokens = await TokenModel.find({}).lean()
58+
59+
// Filter tokens that were only claimed by target users and created after the target date
60+
const exclusiveTokens = allTokens.filter((token) => {
61+
if (token.claimedBy.length === 0) return false
62+
if (new Date(token.createdAt) <= targetDate) return false
63+
return token.claimedBy.every((claim) => targetUsers.includes(claim.id))
64+
})
65+
66+
// Extract only the token codes
67+
const exclusiveTokenCodes = exclusiveTokens.map((token) => token.code)
68+
69+
// Save the JSON stringified array of token codes to a file
70+
const filePath = path.join(__dirname, 'exclusive_tokens.json')
71+
await fs.writeFile(filePath, JSON.stringify(exclusiveTokenCodes, null, 2))
72+
73+
console.log(`Exclusive tokens saved to ${filePath}`)
74+
75+
// Delete tokens if the flag is set to true
76+
if (shouldDeleteTokens) {
77+
console.log('Deleting tokens...')
78+
await TokenModel.deleteMany({ code: { $in: exclusiveTokenCodes } })
79+
console.log(`${exclusiveTokenCodes.length} tokens have been deleted.`)
80+
}
81+
} catch (error) {
82+
console.error('Error:', error)
83+
} finally {
84+
await mongoose.connection.close()
85+
}
86+
}
87+
88+
getExclusiveTokens()

src/config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export default {
22
claim: {
3-
enabled: true,
3+
enabled: false,
44
disabledMessage: 'Resgates estão desabilitados. Obrigado por participar!'
55
}
66
}

src/controllers/token.ts

+37-19
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,36 @@
11
import { Request as ExpressRequest } from 'express'
22
import {
3+
Body,
34
Controller,
4-
Post,
55
Get,
6-
Route,
7-
Body,
8-
Security,
9-
Response,
10-
Request,
6+
Path,
7+
Post,
118
Put,
12-
Path
9+
Request,
10+
Response,
11+
Route,
12+
Security
1313
} from 'tsoa'
14+
import claimService from '../services/claim'
1415
import {
16+
batchCreate,
17+
createDatabaseToken,
18+
getDatabaseTokenByCode,
19+
getDatabaseTokens,
20+
getNonClaimedTokensByUser,
21+
importTokens,
22+
revertUserTokenClaims,
23+
updateDatabaseToken
24+
} from '../services/token'
25+
import {
26+
ClaimRequestResult,
1527
ErrorResponseModel,
1628
ITokenClaimPayload,
1729
ITokenPayload,
18-
RequestResult,
19-
ClaimRequestResult,
2030
NonClaimedTokensRequestResult,
31+
RequestResult,
2132
Token
2233
} from '../types'
23-
import {
24-
getNonClaimedTokensByUser,
25-
getDatabaseTokenByCode,
26-
getDatabaseTokens,
27-
createDatabaseToken,
28-
updateDatabaseToken,
29-
importTokens,
30-
batchCreate
31-
} from '../services/token'
32-
import claimService from '../services/claim'
3334

3435
export interface MulterRequest extends ExpressRequest {
3536
file: Express.Multer.File
@@ -174,4 +175,21 @@ export class TokenController extends Controller {
174175
console.log(error)
175176
}
176177
}
178+
179+
@Security('api_key')
180+
@Post('/revert-user-claims')
181+
public async revertUserClaims(@Body() body: { userId: string }): Promise<RequestResult> {
182+
try {
183+
const { userId } = body
184+
const result = await revertUserTokenClaims(userId)
185+
return result
186+
} catch (error) {
187+
console.log(error)
188+
return {
189+
status: 'error',
190+
message: 'An error occurred while reverting user claims',
191+
statusCode: 500
192+
}
193+
}
194+
}
177195
}

src/controllers/user.ts

+38-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
import { Body, Controller, Get, Post, Response, Route, Security } from 'tsoa'
2+
import { getRankService } from '../services/rank'
3+
import { checkForSuspiciousActivity, getDatabaseUserById, getDatabaseUsers, softDeleteUserById } from '../services/user'
14
import {
25
ErrorResponseModel,
36
RankRequestResult,
47
RequestResult,
8+
SoftDeleteResult,
59
User
610
} from '../types'
7-
import { Controller, Get, Route, Security, Response } from 'tsoa'
8-
import { getRankService } from '../services/rank'
9-
import { getDatabaseUsers, getDatabaseUserById } from '../services/user'
1011

1112
@Route('/user')
1213
@Security('api_key')
@@ -43,4 +44,38 @@ export class UserController extends Controller {
4344
console.log(error)
4445
}
4546
}
47+
48+
@Response<ErrorResponseModel>('500', 'Internal Server Error', {
49+
statusCode: 500,
50+
message: 'An error occurred while processing the request'
51+
})
52+
@Security('api_key')
53+
@Get('/susp/check')
54+
public async getSuspiciousActivity(): Promise<any[]> {
55+
try {
56+
const suspiciousUsers = await checkForSuspiciousActivity()
57+
return suspiciousUsers
58+
} catch (error) {
59+
console.log(error)
60+
this.setStatus(500)
61+
return []
62+
}
63+
}
64+
65+
@Response<ErrorResponseModel>('404', 'Not Found', {
66+
statusCode: 404,
67+
message: 'Usuário não encontrado'
68+
})
69+
@Security('api_key')
70+
@Post('/susp/softdelete')
71+
public async softDeleteUser(@Body() body: { userId: string }): Promise<SoftDeleteResult> {
72+
try {
73+
const { userId } = body
74+
const result = await softDeleteUserById(userId)
75+
return result
76+
} catch (error) {
77+
console.log(error)
78+
return { success: false, message: 'An error occurred while soft deleting the user' }
79+
}
80+
}
4681
}

src/models/user.ts

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ const UserSchema = new Schema<User>({
2020
tokens: {
2121
type: Array,
2222
required: true
23+
},
24+
softDeleted: {
25+
type: Boolean,
26+
default: false
2327
}
2428
})
2529

src/routes/token.ts

+85-8
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,77 @@
1-
import { NextFunction, Request, Response, Router } from 'express'
1+
import { Router } from 'express'
2+
import rateLimit from 'express-rate-limit'
23
import multer from 'multer'
4+
import { parseResponseResult } from '../common/parseResponseResult'
35
import { MulterRequest, TokenController } from '../controllers/token'
46
import middlewares from '../middlewares'
7+
import { ITokenClaimPayload } from '../types'
58

69
const router = Router()
710
const upload = multer({ dest: 'uploads/' })
811

12+
// Custom store for rate limiting based on email
13+
class EmailStore {
14+
private store: any
15+
16+
constructor() {
17+
this.store = new Map()
18+
}
19+
20+
incr(key: string, cb: any): void {
21+
const now = Date.now()
22+
const record = this.store.get(key) || { count: 0, resetTime: now + 60000 }
23+
24+
if (now > record.resetTime) {
25+
record.count = 1
26+
record.resetTime = now + 60000
27+
} else {
28+
record.count++
29+
}
30+
31+
this.store.set(key, record)
32+
cb(null, record.count)
33+
}
34+
35+
decrement(key: string): void {
36+
const record = this.store.get(key)
37+
if (record) {
38+
record.count = Math.max(0, record.count - 1)
39+
}
40+
}
41+
42+
resetKey(key: string): void {
43+
this.store.delete(key)
44+
}
45+
46+
resetAll(): void {
47+
this.store.clear()
48+
}
49+
}
50+
51+
const emailStore = new EmailStore()
52+
53+
const claimLimiter = rateLimit({
54+
windowMs: 1 * 60 * 1000, // 1 minute
55+
max: 10, // Limit each email to 20 requests per windowMs
56+
handler: (req: any, res: any) => {
57+
const email = req.body.email || 'Unknown'
58+
console.log(`[RATE-LIMIT] User ${email} hit the rate limit`)
59+
const result = parseResponseResult('error', 'Too many attempts, please try again later.', 429)
60+
res.status(result.statusCode).json(result)
61+
},
62+
standardHeaders: true,
63+
legacyHeaders: false,
64+
keyGenerator: (req: any) => {
65+
const body = req.body as ITokenClaimPayload
66+
return body.email || req.ip // Fallback to IP if email is not provided
67+
},
68+
store: emailStore as any
69+
})
70+
971
router.post(
1072
'/claim',
1173
middlewares.authentication,
74+
claimLimiter,
1275
async (request, response, next) => {
1376
try {
1477
const controller = new TokenController()
@@ -120,16 +183,30 @@ router.post(
120183
}
121184
)
122185

186+
// router.post(
187+
// '/partner',
188+
// [middlewares.setHasPartnerAuth, middlewares.authentication],
189+
// async (_request: Request, response: Response, next: NextFunction) => {
190+
// try {
191+
// const controller = new TokenController()
192+
// const requestResult = await controller.createByPartner()
193+
// return response
194+
// .status(requestResult.statusCode || 200)
195+
// .send(requestResult)
196+
// } catch (error) {
197+
// next(error)
198+
// }
199+
// }
200+
// )
201+
123202
router.post(
124-
'/partner',
125-
[middlewares.setHasPartnerAuth, middlewares.authentication],
126-
async (_request: Request, response: Response, next: NextFunction) => {
203+
'/revert-user-claims',
204+
middlewares.authentication,
205+
async (request, response, next) => {
127206
try {
128207
const controller = new TokenController()
129-
const requestResult = await controller.createByPartner()
130-
return response
131-
.status(requestResult.statusCode || 200)
132-
.send(requestResult)
208+
const result = await controller.revertUserClaims(request.body)
209+
return response.status(result.statusCode || 200).send(result)
133210
} catch (error) {
134211
next(error)
135212
}

0 commit comments

Comments
 (0)