Skip to content

Commit d7da33b

Browse files
committed
Merge branch 'main' of github.com:speechanddebate/indexcards
2 parents 0db886c + 23fca3a commit d7da33b

36 files changed

+868
-125
lines changed

api/controllers/rest/paradigmsController.js

Lines changed: 171 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,26 @@
11
import personRepo from '../../repos/personRepo.js';
22
import { BadRequest, NotFound } from '../../helpers/problem.js';
3+
import config from '../../../config/config.js';
34

45
async function getParadigms(req, res) {
56
//get the search query from the query params
6-
const { search } = req.query;
7+
const { search, limit = 50, offset = 0 } = req.query;
78
if (!search) {
89
throw new BadRequest(req, res, 'Search query is required');
910
}
11+
if (limit > 100) {
12+
throw new BadRequest(req, res, 'Limit cannot exceed 100');
13+
}
1014

1115
const paradigms = await personRepo.personSearch(search, {
1216
excludeBanned: true,
1317
excludeUnconfirmedEmail: true,
1418
hasValidParadigm: true,
1519
hasJudged: true,
16-
limit: 50,
20+
limit,
21+
offset,
1722
include: {
18-
judges: {
23+
Judges: {
1924
fields: ['id'],
2025
include: {
2126
school: {
@@ -25,7 +30,6 @@ async function getParadigms(req, res) {
2530
},
2631
},
2732
});
28-
2933
const results = paradigms.map(p => {
3034
const nameParts = [p.firstName, p.middleName, p.lastName].filter(Boolean);
3135
// Get all schools from Judges
@@ -52,30 +56,188 @@ async function getParadigms(req, res) {
5256
});
5357
res.json(results);
5458
};
59+
60+
//NOTE, I also hate this and will fix it, I was just seeing if it would work well. it didn't. RCT
5561
async function getParadigmByPersonId(req, res) {
5662
const { personId } = req.params;
5763
if (!personId) {
5864
throw new BadRequest(req, res, 'Person ID is required');
5965
}
6066

67+
const certInclude = {
68+
PersonQuizzes: {
69+
isValid: true,
70+
fields: ['id', 'updatedAt'],
71+
include: {
72+
Quiz: {
73+
fields: ['id', 'label', 'description', 'badgeDescription', 'badge', 'badgeLink'],
74+
},
75+
},
76+
},
77+
};
78+
const sectionRecordInclude = {
79+
Section: {
80+
required: true,
81+
fields: ['id'],
82+
include: {
83+
Round: {
84+
required: true,
85+
publicPrimaryResults: true,
86+
fields: ['id','name','label'],
87+
include: {
88+
Event: {
89+
fields: ['id','abbr'],
90+
settings: ['aff_label','neg_label'],
91+
},
92+
},
93+
},
94+
Ballots: {
95+
fields: ['id', 'judgeId', 'side'],
96+
include: {
97+
Scores: {
98+
winloss: true,
99+
fields: ['id','tag','value'],
100+
},
101+
Entry: {
102+
fields: ['id','code'],
103+
},
104+
},
105+
},
106+
},
107+
},
108+
};
109+
61110
const person = await personRepo.getPerson(personId, {
62111
excludeBanned: true,
63112
excludeUnconfirmedEmail: true,
64113
hasValidParadigm: true,
65-
settings: ['paradigm', 'paradigm_timestamp'],
114+
settings: ['paradigm'],
115+
include: {
116+
...certInclude,
117+
Judges: {
118+
fields: ['id'],
119+
include: {
120+
Category: {
121+
required: true,
122+
fields: ['id'],
123+
include: {
124+
Tourn: {
125+
required: true,
126+
fields: ['id','name','start', 'hidden'],
127+
},
128+
},
129+
},
130+
Ballots : {
131+
fields: ['id','side'],
132+
winnerBallot: true,
133+
include: {
134+
...sectionRecordInclude,
135+
Scores: {
136+
winloss: true,
137+
fields: ['id','tag','value'],
138+
},
139+
},
140+
},
141+
},
142+
},
143+
},
66144
});
67-
68145
if (!person) {
69146
return NotFound(req, res, 'Person not found or does not have a valid paradigm');
70147
}
148+
// Build record array: one element per ballot on each judge
149+
const record = [];
150+
151+
for (const judge of person.Judges || []) {
152+
for (const judgeBallot of judge.Ballots || []) {
153+
// Skip non winner ballots
154+
155+
const section = judgeBallot.Section;
156+
if (!section) continue;
157+
let affLabel = section.Round?.Event?.settings?.aff_label || 'Aff';
158+
let negLabel = section.Round?.Event?.settings?.neg_label || 'Neg';
159+
160+
let affTeam = null;
161+
let negTeam = null;
162+
163+
let affWins = 0;
164+
let negWins = 0;
165+
166+
let judgeVote = null;
167+
168+
for (const ballot of section.Ballots || []) {
169+
const entryName = ballot.Entry?.code;
170+
// Identify entries (will repeat, but safe)
171+
if (ballot.side == 1 && !affTeam) {
172+
affTeam = entryName;
173+
}
174+
175+
if (ballot.side == 2 && !negTeam) {
176+
negTeam = entryName;
177+
}
178+
const winScore = ballot.Scores?.find(
179+
s => s.tag === 'winloss'
180+
);
181+
182+
// Count panel votes
183+
if (winScore?.value === 1) {
184+
if (ballot.side == 1) affWins++;
185+
if (ballot.side == 2) negWins++;
186+
}
187+
188+
// Detect THIS judge's vote
189+
if (
190+
ballot.judgeId === judge.id &&
191+
winScore?.value === 1
192+
) {
193+
judgeVote = ballot.side == 1 ? affLabel : negLabel;
194+
}
195+
}
196+
197+
// Determine panel majority winner
198+
let panelVote = null;
199+
200+
if (affWins > negWins) panelVote = affLabel;
201+
else if (negWins > affWins) panelVote = negLabel;
202+
else panelVote = 'Tie'; // optional handling
203+
204+
let roundLabel = section?.Round?.label;
205+
if (!roundLabel && section?.Round?.name) {
206+
roundLabel = `R${section.Round.name}`;
207+
}
208+
record.push({
209+
tournName: judge.Category?.Tourn?.name || 'Unknown Tournament',
210+
roundDate: judge.Category?.Tourn?.start || null,
211+
roundLabel,
212+
eventAbbr: section?.Round?.Event?.abbr || null,
213+
affTeam,
214+
affLabel,
215+
negTeam,
216+
negLabel,
217+
vote: judgeVote, // this judge's vote
218+
panelVote, // overall result
219+
record: `${affWins}-${negWins}`, // optional
220+
});
221+
}
222+
}
71223
res.json({
72224
id: person.id,
73225
name: [person.firstName, person.middleName, person.lastName].filter(Boolean).join(' '),
74-
lastReviewed: person.settings['paradigm_timestamp'] || null,
226+
lastReviewed: person.settingsTimestamps['paradigm']?.updatedAt || null,
75227
paradigm: person.settings['paradigm'] || null,
76-
228+
record,
229+
certifications: person.PersonQuizzes?.map(pq => ({
230+
title: pq.Quiz?.label,
231+
description: pq.Quiz?.description,
232+
updatedAt: pq.updatedAt,
233+
badge: {
234+
altText: pq.Quiz?.badgeDescription || null,
235+
imageUrl: (pq.Quiz?.id && pq.Quiz?.badge) ? `${config.S3_URL}/badges/${pq.Quiz.id}/${pq.Quiz.badge}`
236+
: null,
237+
link: pq.Quiz?.badgeLink || null,
238+
},
239+
})),
77240
});
78-
79241
};
80242

81243
export default {

api/controllers/rest/roundController.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export async function getPublishedRounds(req, res){
3434
tournId: req.params.tournId,
3535
},{
3636
include: {
37-
event: {
37+
Event: {
3838
fields: ['id', 'name', 'abbr','type', 'level'],
3939
settings: ['min_entry'],
4040
},

api/data/db.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ const sequelize = new Sequelize(
77
config.DB_DATABASE,
88
config.DB_USER,
99
config.DB_PASS,
10-
config.sequelizeOptions
10+
{
11+
...config.sequelizeOptions,
12+
}
1113
);
1214

1315
// initalize all models created by sequelize-auto
@@ -29,6 +31,17 @@ db.school.hasMany(db.judge, { as: 'judges', foreignKey: 'school' });
2931
// person to judge
3032
db.person.hasMany(db.judge, { as: 'judges', foreignKey: 'person' });
3133
db.judge.belongsTo(db.person, { as: 'person_person', foreignKey: 'person' });
34+
// personQuiz to person and quiz
35+
db.personQuiz.belongsTo(db.person, { as: 'person_quiz', foreignKey: 'person' });
36+
db.person.hasMany(db.personQuiz, { as: 'personQuizzes', foreignKey: 'person' });
37+
db.personQuiz.belongsTo(db.quiz, { as: 'personQuiz_quiz', foreignKey: 'quiz' });
38+
db.quiz.hasMany(db.personQuiz, { as: 'personQuizzes', foreignKey: 'quiz' });
39+
// ballot to entry
40+
db.ballot.belongsTo(db.entry, { as: 'entry_entry', foreignKey: 'entry' });
41+
db.entry.hasMany(db.ballot, { as: 'ballots', foreignKey: 'entry' });
42+
// ballot to score
43+
db.ballot.hasMany(db.score, { as: 'ballot_scores', foreignKey: 'ballot' });
44+
// score to ballot in init-models
3245

3346
// By default Sequelize wants you to try...catch every single database call
3447
// for Reasons? Otherwise all your database errors just go unprinted and you

api/middleware/authorization/authorization.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import personRepo from '../../repos/personRepo.js';
22
import { buildTarget } from './buildTarget.js';
33
import { Unauthorized, Forbidden } from '../../helpers/problem.js';
4+
//requires login - use before any route that needs authentication
5+
export function requireLogin(req, res, next) {
6+
if (!req.person) {
7+
return Unauthorized(req, res,'User not Authenticated');
8+
}
9+
next();
10+
}
411
// used for ext routes
512
export async function requireAreaAccess(req, res, next) {
613
if (!req.person) {

api/repos/ballotRepo.js

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import db from '../data/db.js';
22
import { judgeInclude } from './judgeRepo.js';
33
import { sectionInclude } from './sectionRepo.js';
44
import { scoreInclude } from './scoreRepo.js';
5+
import { entryInclude } from './entryRepo.js';
56
import { FIELD_MAP, toDomain, toPersistence } from './mappers/ballotMapper.js';
67
import { resolveAttributesFromFields } from './utils/repoUtils.js';
78

@@ -11,27 +12,63 @@ function buildBallotQuery(opts = {}){
1112
attributes: resolveAttributesFromFields(opts.fields,FIELD_MAP),
1213
include: [],
1314
};
15+
if(opts.winnerBallot){
16+
17+
//only ballots with a score.tag = 'winloss' and score.value = ballot.side should be included
18+
}
1419
if(opts.include?.judge) {
1520
query.include.push({
1621
...judgeInclude(opts.include.judge),
1722
as: 'judge_judge',
1823
required: false,
1924
});
2025
}
21-
if(opts.include?.section) {
26+
if(opts.include?.Entry) {
27+
query.include.push({
28+
...entryInclude(opts.include.Entry),
29+
as: 'entry_entry',
30+
required: opts.include.Entry.required ?? false,
31+
});
32+
}
33+
if(opts.include?.Section) {
2234
query.include.push({
23-
...sectionInclude(opts.include.section),
35+
...sectionInclude(opts.include.Section),
2436
as: 'panel_panel',
2537
required: false,
2638
});
2739
}
28-
if(opts.include?.scores){
40+
if(opts.include?.Scores){
2941
query.include.push({
30-
...scoreInclude(opts.include.scores),
31-
as: 'scores',
32-
required: false,
42+
...scoreInclude(opts.include.Scores),
43+
as: 'ballot_scores',
44+
required: opts.include.Scores.required ?? false,
3345
});
3446
}
47+
if (opts.winnerBallot) {
48+
49+
const existing = query.include.find(i => i.as === 'ballot_scores');
50+
51+
if (existing) {
52+
existing.required = true;
53+
existing.where = {
54+
...(existing.where || {}),
55+
tag: 'winloss',
56+
value: 1,
57+
};
58+
} else {
59+
query.include.push({
60+
model: db.score,
61+
as: 'ballot_scores',
62+
attributes: [],
63+
required: true,
64+
where: {
65+
tag: 'winloss',
66+
value: 1,
67+
},
68+
});
69+
}
70+
}
71+
3572
return query;
3673
}
3774

api/repos/ballotRepo.test.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,24 @@ describe('ballotRepo', async () => {
3838

3939
const ballot = await ballotRepo.getBallot(
4040
ballotId,
41-
{ include: { scores: true } }
41+
{ include: { Scores: true } }
4242
);
4343

4444
expect(ballot).toBeDefined();
45-
expect(ballot.scores).toBeDefined();
46-
expect(Array.isArray(ballot.scores)).toBe(true);
45+
expect(ballot.Scores).toBeDefined();
46+
expect(Array.isArray(ballot.Scores)).toBe(true);
4747
});
4848
it('includes section when requested', async () => {
4949
const ballotId = await ballotRepo.createBallot({sectionId});
5050

5151
const ballot = await ballotRepo.getBallot(
5252
ballotId,
53-
{ include: { section: true } }
53+
{ include: { Section: true } }
5454
);
5555

5656
expect(ballot).toBeDefined();
57-
expect(ballot.section).toBeDefined();
58-
expect(ballot.section.id).toBeDefined();
57+
expect(ballot.Section).toBeDefined();
58+
expect(ballot.Section.id).toBeDefined();
5959
});
6060
});
6161
describe('ballotInclude', () => {

0 commit comments

Comments
 (0)