@johnnyboy11234/esg-idea-contest 0.3.0 → 0.3.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 +119 -0
- package/backend/cmd/server/main.go +36 -2
- package/backend/db/migration/V005__idea.sql +49 -0
- package/backend/db/migration/V006__login_lock.sql +15 -0
- package/backend/db/migration/V007__login_attempt.sql +15 -0
- package/backend/db/migration/V008__pending_file_deletion.sql +27 -0
- package/backend/db/migration/V009__login_throttle_audit.sql +13 -0
- package/backend/go.mod +17 -3
- package/backend/go.sum +48 -7
- package/backend/internal/account/account_test.go +6 -6
- package/backend/internal/account/integration_test.go +45 -9
- package/backend/internal/account/internal/passwords/NOTICE +36 -0
- package/backend/internal/account/internal/passwords/common-passwords.txt +10001 -0
- package/backend/internal/account/internal/passwords/passwords.go +37 -0
- package/backend/internal/account/internal/passwords/passwords_test.go +35 -0
- package/backend/internal/account/keyboard.go +75 -0
- package/backend/internal/account/keyboard_test.go +43 -0
- package/backend/internal/account/logging_integration_test.go +1 -1
- package/backend/internal/account/password.go +131 -0
- package/backend/internal/account/sequence.go +54 -0
- package/backend/internal/account/sequence_test.go +38 -0
- package/backend/internal/account/validate.go +4 -21
- package/backend/internal/api/api.gen.go +262 -0
- package/backend/internal/auth/internal/store/login_attempt.go +53 -0
- package/backend/internal/auth/internal/store/login_lock.go +77 -0
- package/backend/internal/auth/internal/store/login_throttle_audit.go +42 -0
- package/backend/internal/auth/internal/store/session.go +15 -7
- package/backend/internal/auth/login.go +16 -4
- package/backend/internal/auth/login_integration_test.go +14 -1
- package/backend/internal/auth/login_ip_integration_test.go +153 -0
- package/backend/internal/auth/login_lock_integration_test.go +205 -0
- package/backend/internal/auth/login_test.go +15 -0
- package/backend/internal/auth/login_throttle_audit_integration_test.go +88 -0
- package/backend/internal/auth/purge.go +93 -0
- package/backend/internal/auth/purge_integration_test.go +220 -0
- package/backend/internal/auth/purge_test.go +51 -0
- package/backend/internal/auth/session.go +0 -6
- package/backend/internal/auth/session_integration_test.go +3 -3
- package/backend/internal/auth/throttle.go +122 -0
- package/backend/internal/deadline/gate.go +80 -0
- package/backend/internal/deadline/gate_integration_test.go +67 -0
- package/backend/internal/deadline/gate_test.go +61 -0
- package/backend/internal/idea/create.go +356 -0
- package/backend/internal/idea/create_integration_test.go +680 -0
- package/backend/internal/idea/download.go +193 -0
- package/backend/internal/idea/download_integration_test.go +311 -0
- package/backend/internal/idea/download_test.go +25 -0
- package/backend/internal/idea/handler.go +151 -0
- package/backend/internal/idea/handler_integration_test.go +107 -0
- package/backend/internal/idea/integration_test.go +149 -0
- package/backend/internal/idea/internal/store/list.go +146 -0
- package/backend/internal/idea/internal/store/list_test.go +33 -0
- package/backend/internal/idea/internal/store/store.go +166 -0
- package/backend/internal/idea/list.go +166 -0
- package/backend/internal/idea/list_integration_test.go +252 -0
- package/backend/internal/idea/list_test.go +96 -0
- package/backend/internal/idea/service.go +54 -0
- package/backend/internal/server/api.go +22 -0
- package/backend/internal/server/binding.go +4 -0
- package/backend/internal/server/public.go +1 -0
- package/backend/internal/server/server.go +20 -1
- package/backend/internal/server/slides.go +21 -0
- package/backend/internal/server/slides_test.go +82 -0
- package/backend/internal/shared/config/config.go +44 -1
- package/backend/internal/shared/config/config_test.go +21 -13
- package/backend/internal/shared/db/intx_integration_test.go +179 -0
- package/backend/internal/shared/dbtest/dbtest.go +21 -3
- package/backend/internal/shared/dbtest/faults.go +162 -0
- package/backend/internal/shared/job/job.go +54 -0
- package/backend/internal/shared/job/job_test.go +105 -0
- package/backend/internal/shared/web/multipart.go +175 -0
- package/backend/internal/shared/web/multipart_test.go +169 -0
- package/backend/internal/shared/web/problem.go +1 -0
- package/backend/internal/shared/web/request.go +42 -1
- package/backend/internal/shared/web/transport.go +4 -3
- package/backend/internal/shared/web/web_test.go +36 -0
- package/backend/internal/storage/expectedfilename.go +43 -0
- package/backend/internal/storage/expectedfilename_test.go +16 -0
- package/backend/internal/storage/internal/store/pending.go +41 -0
- package/backend/internal/storage/minio.go +139 -0
- package/backend/internal/storage/minio_integration_test.go +87 -0
- package/backend/internal/storage/minio_test.go +84 -0
- package/backend/internal/storage/pending.go +144 -0
- package/backend/internal/storage/pending_integration_test.go +324 -0
- package/backend/internal/storage/pending_test.go +23 -0
- package/backend/internal/storage/validator.go +100 -0
- package/backend/internal/storage/validator_test.go +95 -0
- package/backend/test/arch/arch_test.go +5 -1
- package/backend/test/contract/expected_filename_test.go +60 -0
- package/backend/test/contract/password_test.go +46 -0
- package/backend/test/matrix/load_test.go +12 -0
- package/backend/test/matrix/matrix_integration_test.go +92 -5
- package/backend/test/migration/pending_file_deletion_test.go +46 -0
- package/docs/API/api-endpoints.md +1 -1
- package/docs/API/error-codes.md +1 -1
- package/docs/Architecture/Architecture.md +1 -1
- package/docs/Architecture/Backend.md +2 -1
- package/frontend/Dockerfile +2 -0
- package/frontend/package-lock.json +1 -0
- package/frontend/package.json +1 -0
- package/frontend/src/app/App.tsx +11 -2
- package/frontend/src/app/pages/ideaCreate.test.tsx +308 -0
- package/frontend/src/app/pages/ideas.tsx +23 -9
- package/frontend/src/app/pages/login.test.tsx +38 -1
- package/frontend/src/app/pages/register.test.tsx +32 -0
- package/frontend/src/features/auth/IdleWarning.test.tsx +82 -0
- package/frontend/src/features/auth/IdleWarning.tsx +66 -0
- package/frontend/src/features/auth/LoginForm.tsx +21 -3
- package/frontend/src/features/auth/PasswordChecklist.test.tsx +73 -0
- package/frontend/src/features/auth/PasswordChecklist.tsx +96 -0
- package/frontend/src/features/auth/RegisterForm.tsx +11 -2
- package/frontend/src/features/auth/common-passwords.ts +31 -0
- package/frontend/src/features/auth/password-policy.test.ts +64 -0
- package/frontend/src/features/auth/password-policy.ts +168 -0
- package/frontend/src/features/auth/useCountdown.test.tsx +33 -0
- package/frontend/src/features/auth/useCountdown.ts +28 -0
- package/frontend/src/features/idea/CreateIdeaForm.tsx +279 -0
- package/frontend/src/features/idea/create.ts +123 -0
- package/frontend/src/shared/api/activity.ts +20 -0
- package/frontend/src/shared/api/http.ts +2 -0
- package/frontend/src/shared/api/index.ts +1 -0
- package/frontend/src/shared/api/types.generated.ts +319 -1
- package/frontend/src/shared/i18n/locales/en.ts +46 -4
- package/frontend/src/shared/i18n/locales/zh-TW.ts +46 -4
- package/frontend/src/shared/slides/index.ts +74 -0
- package/frontend/src/shared/slides/slides.test.ts +51 -0
- package/frontend/src/shared/ui/copy-button.tsx +52 -0
- package/frontend/src/shared/ui/native-select.tsx +27 -0
- package/frontend/src/shared/ui/textarea.tsx +19 -0
- package/frontend/src/test-setup.ts +37 -2
- package/frontend/vite.config.ts +3 -1
- package/package.json +1 -1
- package/testdata/expected-filename.json +93 -0
- package/testdata/password.json +582 -0
- package/testdata/permission-matrix.json +71 -0
- package/testdata/schema/expected-filename.schema.json +32 -0
- package/testdata/schema/password.schema.json +59 -0
- package/testdata/schema/permission-matrix.schema.json +22 -1
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { CheckIcon, CopyIcon } from "lucide-react"
|
|
2
|
+
import { useEffect, useState } from "react"
|
|
3
|
+
import { Button } from "./button"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Copies `value` to the clipboard. The button has an accessible name of its own
|
|
7
|
+
* (`label`); the result is announced in a polite live region (WCAG 4.1.3).
|
|
8
|
+
*/
|
|
9
|
+
export function CopyButton({
|
|
10
|
+
value,
|
|
11
|
+
label,
|
|
12
|
+
copiedText,
|
|
13
|
+
failedText,
|
|
14
|
+
}: {
|
|
15
|
+
value: string
|
|
16
|
+
label: string
|
|
17
|
+
copiedText: string
|
|
18
|
+
failedText: string
|
|
19
|
+
}) {
|
|
20
|
+
const [state, setState] = useState<"idle" | "copied" | "failed">("idle")
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (state === "idle") return
|
|
23
|
+
const id = setTimeout(() => setState("idle"), 2000)
|
|
24
|
+
return () => clearTimeout(id)
|
|
25
|
+
}, [state])
|
|
26
|
+
|
|
27
|
+
const copy = async () => {
|
|
28
|
+
try {
|
|
29
|
+
await navigator.clipboard.writeText(value)
|
|
30
|
+
setState("copied")
|
|
31
|
+
} catch {
|
|
32
|
+
setState("failed")
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<>
|
|
38
|
+
<Button
|
|
39
|
+
type="button"
|
|
40
|
+
variant="outline"
|
|
41
|
+
size="icon-sm"
|
|
42
|
+
aria-label={label}
|
|
43
|
+
onClick={() => void copy()}
|
|
44
|
+
>
|
|
45
|
+
{state === "copied" ? <CheckIcon aria-hidden="true" /> : <CopyIcon aria-hidden="true" />}
|
|
46
|
+
</Button>
|
|
47
|
+
<span role="status" className="sr-only">
|
|
48
|
+
{state === "copied" ? copiedText : state === "failed" ? failedText : ""}
|
|
49
|
+
</span>
|
|
50
|
+
</>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { ChevronDownIcon } from "lucide-react"
|
|
3
|
+
import { cn } from "./utils"
|
|
4
|
+
|
|
5
|
+
/** A styled native <select>: keyboard, screen-reader and mobile behaviour stay the browser's. */
|
|
6
|
+
function NativeSelect({ className, ...props }: React.ComponentProps<"select">) {
|
|
7
|
+
return (
|
|
8
|
+
<div className="relative w-full">
|
|
9
|
+
<select
|
|
10
|
+
data-slot="native-select"
|
|
11
|
+
className={cn(
|
|
12
|
+
"h-9 w-full min-w-0 appearance-none rounded-md border border-input bg-transparent py-1 pr-9 pl-3 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
|
|
13
|
+
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
14
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
<ChevronDownIcon
|
|
20
|
+
aria-hidden="true"
|
|
21
|
+
className="pointer-events-none absolute top-1/2 right-3 size-4 -translate-y-1/2 opacity-50"
|
|
22
|
+
/>
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { NativeSelect }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "./utils"
|
|
3
|
+
|
|
4
|
+
function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
|
5
|
+
return (
|
|
6
|
+
<textarea
|
|
7
|
+
data-slot="textarea"
|
|
8
|
+
className={cn(
|
|
9
|
+
"flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
|
|
10
|
+
"focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
|
|
11
|
+
"aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
|
|
12
|
+
className
|
|
13
|
+
)}
|
|
14
|
+
{...props}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { Textarea }
|
|
@@ -1,6 +1,41 @@
|
|
|
1
1
|
import '@testing-library/jest-dom/vitest';
|
|
2
|
+
import { File as NodeFile } from 'node:buffer';
|
|
3
|
+
import { FormData as NodeFormData, Response as NodeResponse } from 'undici';
|
|
2
4
|
import { server } from '@/shared/api/mocks/server';
|
|
3
5
|
|
|
4
|
-
|
|
6
|
+
/**
|
|
7
|
+
* jsdom replaces FormData/File with its own classes, which Node's fetch cannot
|
|
8
|
+
* serialise (the multipart body is never written, so MSW fails with "Premature
|
|
9
|
+
* close"). Real browsers send a FormData natively; here the jsdom one is encoded
|
|
10
|
+
* to multipart bytes (by undici's Response) just before the request reaches MSW.
|
|
11
|
+
* Installed after `server.listen()` so that it sits on top of MSW's own fetch
|
|
12
|
+
* interceptor.
|
|
13
|
+
*/
|
|
14
|
+
async function encodeMultipart(body: FormData): Promise<{ bytes: ArrayBuffer; contentType: string }> {
|
|
15
|
+
const out = new NodeFormData();
|
|
16
|
+
for (const [key, value] of body.entries()) {
|
|
17
|
+
if (typeof value === 'string') out.append(key, value);
|
|
18
|
+
else out.append(key, new NodeFile([await value.arrayBuffer()], value.name, { type: value.type }));
|
|
19
|
+
}
|
|
20
|
+
const res = new NodeResponse(out);
|
|
21
|
+
return { bytes: await res.arrayBuffer(), contentType: res.headers.get('content-type') ?? '' };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let mswFetch: typeof fetch;
|
|
25
|
+
|
|
26
|
+
beforeAll(() => {
|
|
27
|
+
server.listen({ onUnhandledFrame: 'error' });
|
|
28
|
+
mswFetch = globalThis.fetch;
|
|
29
|
+
globalThis.fetch = async (input, init) => {
|
|
30
|
+
if (!(init?.body instanceof FormData)) return mswFetch(input, init);
|
|
31
|
+
const { bytes, contentType } = await encodeMultipart(init.body);
|
|
32
|
+
const headers = new Headers(init.headers);
|
|
33
|
+
headers.set('Content-Type', contentType);
|
|
34
|
+
return mswFetch(input, { ...init, headers, body: bytes });
|
|
35
|
+
};
|
|
36
|
+
});
|
|
5
37
|
afterEach(() => server.resetHandlers());
|
|
6
|
-
afterAll(() =>
|
|
38
|
+
afterAll(() => {
|
|
39
|
+
globalThis.fetch = mswFetch;
|
|
40
|
+
server.close();
|
|
41
|
+
});
|
package/frontend/vite.config.ts
CHANGED
|
@@ -5,10 +5,12 @@ import { defineConfig } from 'vitest/config';
|
|
|
5
5
|
|
|
6
6
|
const src = fileURLToPath(new URL('./src', import.meta.url));
|
|
7
7
|
const testdata = fileURLToPath(new URL('../testdata', import.meta.url));
|
|
8
|
+
// The backend's bundled common-password list (PRD §6.4): one file for both sides, lazy-loaded by the register page.
|
|
9
|
+
const passwords = fileURLToPath(new URL('../backend/internal/account/internal/passwords', import.meta.url));
|
|
8
10
|
|
|
9
11
|
export default defineConfig({
|
|
10
12
|
plugins: [react(), tailwindcss()],
|
|
11
|
-
resolve: { alias: { '@': src, testdata } },
|
|
13
|
+
resolve: { alias: { '@': src, testdata, passwords } },
|
|
12
14
|
server: {
|
|
13
15
|
fs: { allow: ['..'] },
|
|
14
16
|
// The app calls same-origin /api/v1; in dev the Go backend (ESG_PUBLIC_ADDR, default :8080) answers it.
|
package/package.json
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Shared by backend (Go) and frontend (TS). Expected slide filename base per PRD 6.8 and the 255 UTF-8 byte rule of PRD 6.2 (length measured with .pptx). Inputs may be NFD; wantBase is always NFC.",
|
|
3
|
+
"cases": [
|
|
4
|
+
{
|
|
5
|
+
"id": "cool-idea-pdf-example",
|
|
6
|
+
"category": "COOL_IDEA",
|
|
7
|
+
"org": "BSID",
|
|
8
|
+
"leaderName": "\u738b\u5c0f\u660e",
|
|
9
|
+
"leaderEmpNo": "089290",
|
|
10
|
+
"ideaName": "\u6210\u7acb\u4f9b\u61c9\u5546\u6c38\u7e8c\u5b78\u9662",
|
|
11
|
+
"wantBase": "I_BSID_\u738b\u5c0f\u660e_089290_\u6210\u7acb\u4f9b\u61c9\u5546\u6c38\u7e8c\u5b78\u9662",
|
|
12
|
+
"wantBytes": 56,
|
|
13
|
+
"wantTooLong": false
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "outstanding-impact-example",
|
|
17
|
+
"category": "OUTSTANDING_IMPACT",
|
|
18
|
+
"org": "AAID",
|
|
19
|
+
"leaderName": "\u838a\u80b2\u6210",
|
|
20
|
+
"leaderEmpNo": "054616",
|
|
21
|
+
"ideaName": "AI\u79d8\u5bc6\u6b66\u5668\u667a\u80fd\u5c0d\u8a71",
|
|
22
|
+
"wantBase": "O_AAID_\u838a\u80b2\u6210_054616_AI\u79d8\u5bc6\u6b66\u5668\u667a\u80fd\u5c0d\u8a71",
|
|
23
|
+
"wantBytes": 55,
|
|
24
|
+
"wantTooLong": false
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "name-with-inner-space",
|
|
28
|
+
"category": "COOL_IDEA",
|
|
29
|
+
"org": "TSID",
|
|
30
|
+
"leaderName": "Alice Wang",
|
|
31
|
+
"leaderEmpNo": "000001",
|
|
32
|
+
"ideaName": "Green Plant 2.0",
|
|
33
|
+
"wantBase": "I_TSID_Alice Wang_000001_Green Plant 2.0",
|
|
34
|
+
"wantBytes": 45,
|
|
35
|
+
"wantTooLong": false
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "idea-name-nfd-becomes-nfc",
|
|
39
|
+
"category": "COOL_IDEA",
|
|
40
|
+
"org": "ICSD",
|
|
41
|
+
"leaderName": "Zoe",
|
|
42
|
+
"leaderEmpNo": "000002",
|
|
43
|
+
"ideaName": "Cafe\u0301 \u30ab\u3099",
|
|
44
|
+
"wantBase": "I_ICSD_Zoe_000002_Caf\u00e9 \u30ac",
|
|
45
|
+
"wantBytes": 32,
|
|
46
|
+
"wantTooLong": false
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "leader-name-nfd-becomes-nfc",
|
|
50
|
+
"category": "OUTSTANDING_IMPACT",
|
|
51
|
+
"org": "PLED",
|
|
52
|
+
"leaderName": "Jose\u0301",
|
|
53
|
+
"leaderEmpNo": "000003",
|
|
54
|
+
"ideaName": "Reuse",
|
|
55
|
+
"wantBase": "O_PLED_Jos\u00e9_000003_Reuse",
|
|
56
|
+
"wantBytes": 30,
|
|
57
|
+
"wantTooLong": false
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "exactly-255-bytes",
|
|
61
|
+
"category": "OUTSTANDING_IMPACT",
|
|
62
|
+
"org": "AAID",
|
|
63
|
+
"leaderName": "\u838a\u80b2\u6210",
|
|
64
|
+
"leaderEmpNo": "054616",
|
|
65
|
+
"ideaName": "\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275abcd",
|
|
66
|
+
"wantBase": "O_AAID_\u838a\u80b2\u6210_054616_\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275abcd",
|
|
67
|
+
"wantBytes": 255,
|
|
68
|
+
"wantTooLong": false
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "256-bytes-too-long",
|
|
72
|
+
"category": "OUTSTANDING_IMPACT",
|
|
73
|
+
"org": "AAID",
|
|
74
|
+
"leaderName": "\u838a\u80b2\u6210",
|
|
75
|
+
"leaderEmpNo": "054616",
|
|
76
|
+
"ideaName": "\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275abcde",
|
|
77
|
+
"wantBase": "O_AAID_\u838a\u80b2\u6210_054616_\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275\u5275abcde",
|
|
78
|
+
"wantBytes": 256,
|
|
79
|
+
"wantTooLong": true
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "emoji-four-byte-too-long",
|
|
83
|
+
"category": "COOL_IDEA",
|
|
84
|
+
"org": "DEID",
|
|
85
|
+
"leaderName": "\u738b\u5c0f\u660e",
|
|
86
|
+
"leaderEmpNo": "089290",
|
|
87
|
+
"ideaName": "\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00",
|
|
88
|
+
"wantBase": "I_DEID_\u738b\u5c0f\u660e_089290_\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00\ud83d\ude00",
|
|
89
|
+
"wantBytes": 269,
|
|
90
|
+
"wantTooLong": true
|
|
91
|
+
}
|
|
92
|
+
]
|
|
93
|
+
}
|