Skip to content

Commit 43e17f8

Browse files
committed
feat: implement submissions management with global state and API integration
1 parent 19f2bac commit 43e17f8

16 files changed

Lines changed: 414 additions & 11 deletions

File tree

backend/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { createFeedbackRouter } from './routes/feedback'
1111
import { createNotificationService } from './services/notification'
1212
import cors from 'cors'
1313
import path from 'path'
14+
import { createSystemRouter } from './routes/system'
1415

1516
// Determine if we're running from dist or directly from src
1617
const isDist = __dirname.includes('dist')
@@ -59,6 +60,7 @@ app.use('/api/templates', createTemplatesRouter(db, notificationService))
5960
app.use('/api/ai', createAiRouter(db))
6061
app.use('/api/telegram', createTelegramRouter(db))
6162
app.use('/api/feedback', createFeedbackRouter(db, notificationService))
63+
app.use('/api/system', createSystemRouter())
6264

6365
const port = process.env.PORT || 3001
6466
app.listen(port, () => {

backend/src/routes/apps.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { JsonSchema, validateInputData } from '../utils/input-validation'
88
import { validateJson, JsonValidationError } from '../utils/jsonValidation'
99
import { INotificationService } from '../services/notification'
1010
import { truncateText } from '../utils/text'
11+
import { globalState } from '../utils/globalState'
1112

1213
/**
1314
* Extended Request type that includes the authenticated wallet address
@@ -56,6 +57,13 @@ export function createAppsRouter(db: Knex, notificationService: INotificationSer
5657
// Create new app
5758
router.post('/my-apps', requireAuth, (async (req: Request, res: Response) => {
5859
try {
60+
// Check if submissions are enabled
61+
if (!globalState.getSubmissionsEnabled()) {
62+
return res.status(403).json({
63+
error: 'Submissions are currently disabled. Thank you for your participation in the hackathon!',
64+
})
65+
}
66+
5967
const body = req.body
6068
if (!body || typeof body !== 'object') {
6169
return res.status(400).json({ error: 'Invalid request body' })

backend/src/routes/system.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Router, Request, Response } from 'express'
2+
import { globalState } from '../utils/globalState'
3+
4+
/**
5+
* Creates and configures the system router
6+
* @returns {Router} Express router configured with system routes
7+
*/
8+
export function createSystemRouter(): Router {
9+
const router = Router()
10+
11+
// Get current submissions status
12+
router.get('/submissions-status', (req: Request, res: Response) => {
13+
try {
14+
const areSubmissionsEnabled = globalState.getSubmissionsEnabled()
15+
res.json({
16+
areSubmissionsEnabled,
17+
message: areSubmissionsEnabled ? 'Submissions are currently enabled' : 'Submissions are currently disabled',
18+
})
19+
} catch (error) {
20+
console.error('Error getting submissions status:', error)
21+
res.status(500).json({ error: 'Internal server error' })
22+
}
23+
})
24+
25+
return router
26+
}

backend/src/routes/telegram.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Router, Request, Response, RequestHandler } from 'express'
22
import { Knex } from 'knex'
33
import * as dotenv from 'dotenv'
44
import path from 'path'
5+
import { globalState } from '../utils/globalState'
56

67
// Type definition for telegram update
78
interface TelegramUpdate {
@@ -220,6 +221,33 @@ export function createTelegramRouter(db: Knex): Router {
220221
}
221222
}
222223

224+
// Command for controlling submissions
225+
if (lowerCaseMessage.startsWith('submissions:')) {
226+
const submissionsValue = message.substring('submissions:'.length).trim()
227+
228+
if (submissionsValue === '1') {
229+
globalState.setSubmissionsEnabled(true)
230+
return res.status(200).json({
231+
method: 'sendMessage',
232+
chat_id: chatId,
233+
text: 'Submissions have been enabled.',
234+
})
235+
} else if (submissionsValue === '0') {
236+
globalState.setSubmissionsEnabled(false)
237+
return res.status(200).json({
238+
method: 'sendMessage',
239+
chat_id: chatId,
240+
text: 'Submissions have been disabled.',
241+
})
242+
} else {
243+
return res.status(200).json({
244+
method: 'sendMessage',
245+
chat_id: chatId,
246+
text: 'Invalid submissions value. Use "submissions: 1" to enable or "submissions: 0" to disable.',
247+
})
248+
}
249+
}
250+
223251
// If none of the commands matched, send help message
224252
return res.status(200).json({
225253
method: 'sendMessage',
@@ -229,9 +257,11 @@ export function createTelegramRouter(db: Knex): Router {
229257
'- "public apps: 1, 2, 3" or "public apps: 1-10" - Make specified apps public\n' +
230258
'- "public templates: 1, 2, 3" or "public templates: 1-10" - Make specified templates public\n' +
231259
'- "private apps: 1, 2, 3" or "private apps: 1-10" - Make specified apps private\n' +
232-
'- "private templates: 1, 2, 3" or "private templates: 1-10" - Make specified templates private',
260+
'- "private templates: 1, 2, 3" or "private templates: 1-10" - Make specified templates private\n' +
261+
'- "submissions: 1" - Enable submissions\n' +
262+
'- "submissions: 0" - Disable submissions',
233263
})
234-
} catch (error) {
264+
} catch (error: unknown) {
235265
console.error('Error processing telegram webhook:', error)
236266
res.sendStatus(200) // Always respond with 200 to Telegram
237267
}

backend/src/routes/templates.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { validateTemplate, ValidationError } from '../utils/templateValidation'
55
import { Template, CreateTemplateDTO } from '../types/template'
66
import { INotificationService } from '../services/notification'
77
import { truncateText } from '../utils/text'
8+
import { globalState } from '../utils/globalState'
89

910
/**
1011
* Extended request interface for template creation
@@ -49,6 +50,13 @@ export function createTemplatesRouter(db: Knex, notificationService: INotificati
4950
// Create template
5051
router.post('/', requireAuth, async (req: CreateTemplateRequest, res: Response) => {
5152
try {
53+
// Check if submissions are enabled
54+
if (!globalState.getSubmissionsEnabled()) {
55+
return res.status(403).json({
56+
error: 'Submissions are currently disabled. Thank you for your participation in the hackathon!',
57+
})
58+
}
59+
5260
const { address, signature } = req.body
5361
const templateData: CreateTemplateDTO = {
5462
title: req.body.title,

backend/src/tests/apps.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { type PrivateKeyAccount } from 'viem/accounts'
66
import { createWalletClient } from 'viem'
77
import { TestDb } from './utils/testDb'
88
import { MockNotificationService } from './__mocks__/notification'
9+
import { globalState } from '../utils/globalState'
910

1011
const quizSchema = {
1112
type: 'object',
@@ -400,6 +401,39 @@ describe('Apps API', () => {
400401
expect(response.status).toBe(400)
401402
expect(response.body.error).toBe('Missing required fields')
402403
})
404+
405+
it('should reject app creation when submissions are disabled', async () => {
406+
// Disable submissions
407+
globalState.setSubmissionsEnabled(false)
408+
409+
const name = 'Test App'
410+
const message = `Create app: ${name}`
411+
const signature = await walletClient.signMessage({
412+
message,
413+
account: testAccount,
414+
})
415+
416+
const response = await request(expressApp)
417+
.post('/api/my-apps')
418+
.set('x-wallet-address', testAccount.address)
419+
.send({
420+
name: name,
421+
description: 'Test Description',
422+
signature,
423+
template_id: templateId,
424+
json_data: quizData,
425+
})
426+
427+
expect(response.status).toBe(403)
428+
expect(response.body.error).toBe('Submissions are currently disabled. Thank you for your participation in the hackathon!')
429+
430+
// Verify no app was created
431+
const apps = await db('apps').where('owner_address', testAccount.address)
432+
expect(apps).toHaveLength(0)
433+
434+
// Re-enable submissions for other tests
435+
globalState.setSubmissionsEnabled(true)
436+
})
403437
})
404438
})
405439

backend/src/tests/system.test.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import request from 'supertest'
2+
import express from 'express'
3+
import { createSystemRouter } from '../routes/system'
4+
import { globalState } from '../utils/globalState'
5+
6+
describe('System API', () => {
7+
let app: express.Express
8+
9+
beforeEach(() => {
10+
app = express()
11+
app.use(express.json())
12+
app.use('/', createSystemRouter())
13+
14+
// Reset global state to default
15+
globalState.setSubmissionsEnabled(true)
16+
})
17+
18+
describe('GET /submissions-status', () => {
19+
it('should return submissions enabled status', async () => {
20+
globalState.setSubmissionsEnabled(true)
21+
22+
const response = await request(app).get('/submissions-status')
23+
24+
expect(response.status).toBe(200)
25+
expect(response.body).toEqual({
26+
areSubmissionsEnabled: true,
27+
message: 'Submissions are currently enabled',
28+
})
29+
})
30+
31+
it('should return submissions disabled status', async () => {
32+
globalState.setSubmissionsEnabled(false)
33+
34+
const response = await request(app).get('/submissions-status')
35+
36+
expect(response.status).toBe(200)
37+
expect(response.body).toEqual({
38+
areSubmissionsEnabled: false,
39+
message: 'Submissions are currently disabled',
40+
})
41+
})
42+
})
43+
})

backend/src/tests/telegram.test.ts

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import request from 'supertest'
22
import express from 'express'
33
import { Knex } from 'knex'
44
import { createTelegramRouter } from '../routes/telegram'
5+
import { globalState } from '../utils/globalState'
56

67
// Mock environment variables
78
process.env.TELEGRAM_CHAT_ID = '123456789'
@@ -229,6 +230,8 @@ describe('Telegram Webhook', () => {
229230
chat_id: 123456789,
230231
text: expect.stringContaining('Supported commands:'),
231232
})
233+
expect(response.body.text).toContain('submissions: 1')
234+
expect(response.body.text).toContain('submissions: 0')
232235
expect(mockDb).not.toHaveBeenCalled()
233236
})
234237

@@ -463,6 +466,100 @@ describe('Telegram Webhook', () => {
463466
})
464467
expect(mockDb).not.toHaveBeenCalled()
465468
})
469+
470+
it('should process submissions enable command correctly', async () => {
471+
const response = await request(app)
472+
.post('/webhook')
473+
.send({
474+
update_id: 123456789,
475+
message: {
476+
message_id: 1,
477+
from: {
478+
id: 123456789,
479+
is_bot: false,
480+
first_name: 'Test',
481+
},
482+
chat: {
483+
id: 123456789, // Same as TELEGRAM_CHAT_ID
484+
first_name: 'Test',
485+
type: 'private',
486+
},
487+
date: 1631234567,
488+
text: 'submissions: 1',
489+
},
490+
})
491+
492+
expect(response.status).toBe(200)
493+
expect(response.body).toEqual({
494+
method: 'sendMessage',
495+
chat_id: 123456789,
496+
text: 'Submissions have been enabled.',
497+
})
498+
499+
expect(globalState.getSubmissionsEnabled()).toBe(true)
500+
})
501+
502+
it('should process submissions disable command correctly', async () => {
503+
const response = await request(app)
504+
.post('/webhook')
505+
.send({
506+
update_id: 123456789,
507+
message: {
508+
message_id: 1,
509+
from: {
510+
id: 123456789,
511+
is_bot: false,
512+
first_name: 'Test',
513+
},
514+
chat: {
515+
id: 123456789,
516+
first_name: 'Test',
517+
type: 'private',
518+
},
519+
date: 1631234567,
520+
text: 'submissions: 0',
521+
},
522+
})
523+
524+
expect(response.status).toBe(200)
525+
expect(response.body).toEqual({
526+
method: 'sendMessage',
527+
chat_id: 123456789,
528+
text: 'Submissions have been disabled.',
529+
})
530+
531+
expect(globalState.getSubmissionsEnabled()).toBe(false)
532+
})
533+
534+
it('should handle invalid submissions command correctly', async () => {
535+
const response = await request(app)
536+
.post('/webhook')
537+
.send({
538+
update_id: 123456789,
539+
message: {
540+
message_id: 1,
541+
from: {
542+
id: 123456789,
543+
is_bot: false,
544+
first_name: 'Test',
545+
},
546+
chat: {
547+
id: 123456789,
548+
first_name: 'Test',
549+
type: 'private',
550+
},
551+
date: 1631234567,
552+
text: 'submissions: invalid',
553+
},
554+
})
555+
556+
expect(response.status).toBe(200)
557+
expect(response.body).toEqual({
558+
method: 'sendMessage',
559+
chat_id: 123456789,
560+
text: 'Invalid submissions value. Use "submissions: 1" to enable or "submissions: 0" to disable.',
561+
})
562+
})
466563
})
467564
})
468565
/* eslint-enable @typescript-eslint/unbound-method */

backend/src/tests/templates.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { TestDb } from './utils/testDb'
99
import { Router } from 'express'
1010
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
1111
import { MockNotificationService } from './__mocks__/notification'
12+
import { globalState } from '../utils/globalState'
1213

1314
interface DbTemplate {
1415
id: number
@@ -201,6 +202,38 @@ describe('Templates API', () => {
201202
expect(response.body.error).toBe(TEMPLATE_VALIDATION.JSON_TOO_LONG)
202203
}, 30000)
203204

205+
it('should reject template creation when submissions are disabled', async () => {
206+
// Disable submissions
207+
globalState.setSubmissionsEnabled(false)
208+
209+
const message = `Create template: ${validTemplate.title}`
210+
const signature = await walletClient.signMessage({
211+
message,
212+
account: testAccount,
213+
})
214+
215+
const response = await request(expressApp)
216+
.post('/api/templates')
217+
.set('x-wallet-address', testAccount.address)
218+
.send({
219+
...validTemplate,
220+
address: testAccount.address,
221+
signature,
222+
})
223+
224+
expect(response.status).toBe(403)
225+
expect(response.body.error).toBe('Submissions are currently disabled. Thank you for your participation in the hackathon!')
226+
227+
// Verify no template was created
228+
const templates = await db<DbTemplate>('templates')
229+
.whereRaw('LOWER(owner_address) = ?', [testAccount.address.toLowerCase()])
230+
.select()
231+
expect(templates).toHaveLength(0)
232+
233+
// Re-enable submissions for other tests
234+
globalState.setSubmissionsEnabled(true)
235+
}, 30000)
236+
204237
it('should handle errors gracefully', async () => {
205238
// Silence console.error during this test
206239
console.error = jest.fn()

0 commit comments

Comments
 (0)