@johnnyboy11234/esg-idea-contest 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/api/openapi.yaml +44 -0
- package/backend/cmd/server/main.go +20 -2
- package/backend/db/migration/V004__session.sql +19 -0
- package/backend/go.mod +1 -1
- package/backend/internal/account/account_test.go +191 -0
- package/backend/internal/account/handler.go +58 -0
- package/backend/internal/account/integration_test.go +268 -0
- package/backend/internal/account/internal/store/store.go +35 -0
- package/backend/internal/account/service.go +153 -0
- package/backend/internal/account/validate.go +100 -0
- package/backend/internal/api/api.gen.go +82 -0
- package/backend/internal/audit/integration_test.go +1 -1
- package/backend/internal/auth/csrf.go +212 -0
- package/backend/internal/auth/csrf_test.go +281 -0
- package/backend/internal/auth/internal/store/session.go +111 -0
- package/backend/internal/auth/middleware.go +66 -0
- package/backend/internal/auth/middleware_test.go +153 -0
- package/backend/internal/auth/session.go +113 -0
- package/backend/internal/auth/session_integration_test.go +245 -0
- package/backend/internal/auth/session_test.go +36 -0
- package/backend/internal/server/api.go +12 -0
- package/backend/internal/server/errors.go +10 -25
- package/backend/internal/server/errors_test.go +18 -0
- package/backend/internal/server/public.go +35 -0
- package/backend/internal/server/public_test.go +73 -0
- package/backend/internal/server/server.go +7 -3
- package/backend/internal/shared/config/config.go +23 -4
- package/backend/internal/shared/config/config_test.go +14 -8
- package/backend/internal/shared/db/intx_test.go +25 -0
- package/backend/internal/shared/db/mysqlerr.go +26 -0
- package/backend/internal/shared/security/security.go +6 -2
- package/backend/internal/shared/security/security_test.go +6 -4
- package/backend/internal/shared/web/problem.go +6 -0
- package/backend/internal/shared/web/request.go +27 -0
- package/frontend/src/app/App.test.tsx +13 -0
- package/frontend/src/app/App.tsx +4 -0
- package/frontend/src/app/session.ts +28 -6
- package/frontend/src/shared/api/csrf.ts +51 -0
- package/frontend/src/shared/api/http.test.tsx +209 -0
- package/frontend/src/shared/api/http.ts +91 -0
- package/frontend/src/shared/api/index.ts +3 -0
- package/frontend/src/shared/text/index.ts +93 -0
- package/frontend/src/shared/text/text.test.ts +22 -0
- package/package.json +1 -1
- package/frontend/src/shared/text/.gitkeep +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Implements PRD §6.2 clean-up and counting. The back end (Go) implements the
|
|
2
|
+
// same rules; both run testdata/text-cleanup.json.
|
|
3
|
+
// String.prototype.trim is NOT used: its whitespace set differs from Unicode
|
|
4
|
+
// White_Space (it strips U+FEFF and keeps U+0085).
|
|
5
|
+
|
|
6
|
+
export type Problem = '' | 'REQUIRED' | 'INVALID_FORMAT' | 'INVALID_NAME_CHARS' | 'OUT_OF_RANGE';
|
|
7
|
+
|
|
8
|
+
export interface CheckResult {
|
|
9
|
+
cleaned: string;
|
|
10
|
+
problem: Problem;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Unicode White_Space property: 25 code points. */
|
|
14
|
+
export function isWhitespace(cp: number): boolean {
|
|
15
|
+
return (
|
|
16
|
+
(cp >= 0x0009 && cp <= 0x000d) ||
|
|
17
|
+
cp === 0x0020 ||
|
|
18
|
+
cp === 0x0085 ||
|
|
19
|
+
cp === 0x00a0 ||
|
|
20
|
+
cp === 0x1680 ||
|
|
21
|
+
(cp >= 0x2000 && cp <= 0x200a) ||
|
|
22
|
+
cp === 0x2028 ||
|
|
23
|
+
cp === 0x2029 ||
|
|
24
|
+
cp === 0x202f ||
|
|
25
|
+
cp === 0x205f ||
|
|
26
|
+
cp === 0x3000
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Characters that would end up in the expected filename (PRD §6.2 rule 8). */
|
|
31
|
+
const FORBIDDEN_NAME_CHARS = /[/\\:*?"<>|]/;
|
|
32
|
+
const CONTROL = /\p{Cc}/u;
|
|
33
|
+
|
|
34
|
+
/** Length in Unicode code points (not UTF-16 units). */
|
|
35
|
+
export function countCodePoints(s: string): number {
|
|
36
|
+
return Array.from(s).length;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Removes leading and trailing White_Space code points. */
|
|
40
|
+
export function trimWhitespace(s: string): string {
|
|
41
|
+
const cps = Array.from(s);
|
|
42
|
+
let start = 0;
|
|
43
|
+
let end = cps.length;
|
|
44
|
+
while (start < end && isWhitespace(cps[start]!.codePointAt(0)!)) start++;
|
|
45
|
+
while (end > start && isWhitespace(cps[end - 1]!.codePointAt(0)!)) end--;
|
|
46
|
+
return cps.slice(start, end).join('');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** NFC, trim, then every whitespace run becomes one ASCII space. */
|
|
50
|
+
export function cleanName(s: string): string {
|
|
51
|
+
const trimmed = trimWhitespace(s.normalize('NFC'));
|
|
52
|
+
let out = '';
|
|
53
|
+
let inRun = false;
|
|
54
|
+
for (const ch of trimmed) {
|
|
55
|
+
if (isWhitespace(ch.codePointAt(0)!)) {
|
|
56
|
+
inRun = true;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (inRun) {
|
|
60
|
+
out += ' ';
|
|
61
|
+
inRun = false;
|
|
62
|
+
}
|
|
63
|
+
out += ch;
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** NFC, CRLF and CR become LF, then trim. */
|
|
69
|
+
export function cleanSummary(s: string): string {
|
|
70
|
+
return trimWhitespace(s.normalize('NFC').replace(/\r\n/g, '\n').replace(/\r/g, '\n'));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function check(cleaned: string, max: number, name: boolean): Problem {
|
|
74
|
+
if (cleaned === '') return 'REQUIRED';
|
|
75
|
+
for (const ch of cleaned) {
|
|
76
|
+
if (CONTROL.test(ch) && (name || ch !== '\n')) return 'INVALID_FORMAT';
|
|
77
|
+
}
|
|
78
|
+
if (name && FORBIDDEN_NAME_CHARS.test(cleaned)) return 'INVALID_NAME_CHARS';
|
|
79
|
+
if (countCodePoints(cleaned) > max) return 'OUT_OF_RANGE';
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Cleans a person / Idea name and reports its first problem; `max` is inclusive. */
|
|
84
|
+
export function checkName(s: string, max: number): CheckResult {
|
|
85
|
+
const cleaned = cleanName(s);
|
|
86
|
+
return { cleaned, problem: check(cleaned, max, true) };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Cleans a summary and reports its first problem; `max` is inclusive. */
|
|
90
|
+
export function checkSummary(s: string, max: number): CheckResult {
|
|
91
|
+
const cleaned = cleanSummary(s);
|
|
92
|
+
return { cleaned, problem: check(cleaned, max, false) };
|
|
93
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import cleanup from 'testdata/text-cleanup.json';
|
|
2
|
+
import { checkName, checkSummary, countCodePoints, trimWhitespace } from './index';
|
|
3
|
+
|
|
4
|
+
describe('shared/text contract (testdata/text-cleanup.json)', () => {
|
|
5
|
+
it('has cases', () => {
|
|
6
|
+
expect(cleanup.cases.length).toBeGreaterThan(0);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it.each(cleanup.cases)('$id', (c) => {
|
|
10
|
+
const r = c.kind === 'name' ? checkName(c.input, c.max) : checkSummary(c.input, c.max);
|
|
11
|
+
expect(r.cleaned).toBe(c.want);
|
|
12
|
+
expect(countCodePoints(r.cleaned)).toBe(c.wantCount);
|
|
13
|
+
expect(r.problem).toBe(c.wantProblem);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('trimWhitespace', () => {
|
|
18
|
+
it('uses White_Space, not String.prototype.trim', () => {
|
|
19
|
+
expect(trimWhitespace('\u0085a\u0085')).toBe('a');
|
|
20
|
+
expect(trimWhitespace('a')).toBe('a');
|
|
21
|
+
});
|
|
22
|
+
});
|
package/package.json
CHANGED
|
File without changes
|