Skip to content

feat(portfolio): shared validation rules for portfolios and usernames - #55

Merged
procoder-ji merged 4 commits into
mainfrom
feat/portfolio-shared-rules
Nov 10, 2026
Merged

procoder-ji merged 4 commits into
mainfrom
feat/portfolio-shared-rules

Conversation

@Coding-Professional

@Coding-Professional Coding-Professional commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Closes #42. Part of #40. Follows ADR 0002.

One source of rules for student portfolios, enforced by two validators that run against the same fixtures, the way shared/practice/questions.json is shared today.

  • shared/portfolio/rules.json: limits, reserved names, blocked words, and the patterns for links, hosts and email.
  • shared/portfolio/fixtures.json: 38 username, 116 portfolio and 17 malformed-text cases, each with the problems it must produce (path:code, for example projects[0].link:invalid-link).
  • src/features/portfolio/: TypeScript validator (validatePortfolio, validateUsername) and types.
  • backend/src/ProCoderJi.Api/Features/Portfolio/: .NET validator and rules loader. rules.json is copied into the build output like the question bank.
  • shared/portfolio/README.md: the limits (generated from rules.json), the problem codes, and how to change a rule.

Nothing calls the validators yet; the API (#43) and the editor (#45) will.

How it behaves

  • Modes. A draft only has to be well-formed so students can save half-finished work. Publishing also requires name and headline.
  • Links must be https, with no credentials, port, IP address or localhost. links.github must be on github.com and links.linkedin on linkedin.com (subdomains allowed), so a field labelled GitHub cannot point at a lookalike site.
  • Usernames are 3 to 30 characters, lowercase letters and digits with single hyphens. Reserved names (including every existing route) and names containing a blocked word are refused. Hyphens are ignored when matching blocked words.
  • Text rejects control and invisible characters (zero-width space, right-to-left override) and anything that looks like an HTML tag.
  • Unknown fields are rejected, and every problem is reported with a stable code.

Numbers to review

Field Max length List Max entries
name 80 skills 30
headline 120 projects 8
about 1000 projects.technologies 12
skill / technology item 40 education 5
project title / description 80 / 500 experience 6
education institution / degree / period / details 120 / 120 / 40 / 300 achievements 10
experience organization / role / period / description 120 / 120 / 40 / 500
achievement 200
link / email 300 / 254

These are starting points taken from what fits a phone-sized card. They live only in rules.json, so changing one is a one-line edit plus a fixture.

Decisions for the reviewer

  • Email is stored as an address, not a mailto: link. ADR 0002 says links allow mailto: for the email field; storing the bare address is simpler for students and the page builds the link. Say if you would rather store mailto:.
  • No theme field yet. The page issue (Portfolio: public page at /u/[username] with two themes #44) decides how a student picks a theme and can add it with a rules version bump.
  • The blocked-word check is deliberately blunt. bank-guide is refused as well as paytm-help. It is cheap to loosen later and costly to discover a phishing name after the fact.
  • x<y is refused as HTML-like; x < y is fine. The Markdown import (Portfolio: import details from a Markdown file #46) will need to explain this to students writing C++ or maths.

Keeping the two runtimes identical

The validators avoid URL parsers and culture-aware string functions, because JavaScript and .NET disagree about them. Two divergences were found and are covered by fixtures:

  • .NET's $ also matches before a trailing newline, so riya\n would pass a pattern that JavaScript rejects. The .NET validator anchors with \z.
  • Lower-casing with the culture-aware functions folds the Kelvin sign (U+212A) into k. Both validators lower-case A-Z only.

I mutated each validator at these points and at the host-suffix, reserved-name, hyphen and item-limit checks to confirm the fixtures fail when a rule breaks.

Checks

  • npm run lint
  • npm run typecheck
  • npm test (159 pass, 2 skipped; 11 new)
  • npm run build
  • dotnet test backend/ProCoderJi.slnx --configuration Release (34 pass; 13 new)
  • Browser checks (not applicable: no routes or UI)

Define the student-portfolio rules once in shared/portfolio/rules.json:
field limits, https-only link and host patterns, email pattern, and
the username format with reserved names and blocked words. Add
fixtures.json with 144 username and portfolio cases that both
validators must agree on, and a README that documents the limits and
how to change a rule.

Refs #42
Validate untrusted portfolio data and usernames against the shared
rules, reporting each problem as a path and a stable code. Drafts only
need to be well-formed; publishing also requires a name and headline.
Links are checked without a URL parser, and only A-Z is lower-cased,
so the result matches the .NET validator exactly. Run all shared
fixtures in the unit tests.

Refs #42
Implement the same portfolio and username rules in the backend, read
from the shared rules file copied into the build output, and run the
shared fixtures in the test project. Anchor patterns with \z because
.NET's $ also matches before a trailing newline, and fail loudly when
a rule key is missing instead of skipping the check.

Refs #42
Copilot AI balanced review requested due to automatic review settings November 10, 2026 09:46

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@procoder-ji procoder-ji left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the TypeScript and .NET validators, shared rules/fixtures, build inclusion and issue #42. Two reproducible input-validation defects need fixes: oversized blank fields bypass the declared limits, and malformed UTF-16 is accepted by TypeScript but throws in .NET. Details and minimal repros are inline.

Verification: 9 focused TypeScript tests and 8 focused .NET tests pass, and all three CI jobs are green on this commit. Additional probes expose the two missing cases. No changes were made to the PR branch.

Comment thread src/features/portfolio/validation.ts Resolved
Check the length before the blank test, so a field of only spaces can
no longer exceed its limit. Previously an 1001-space about, a 301-space
website or a 255-space email passed validation.

Treat a lone surrogate half as invalid-characters in values, links,
emails, usernames and field names. System.Text.Json decodes strings and
property names lazily and throws when they are first read, so the .NET
validator now reads them without throwing and reports the same problem
as the TypeScript validator, which previously accepted them.

Add shared fixtures for both cases. The malformed-text cases list UTF-16
code units because a lone surrogate escape is not portable JSON.

Refs #42
Copilot AI balanced review requested due to automatic review settings November 10, 2026 11:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@procoder-ji procoder-ji left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Coding-Professional for addressing both findings and adding shared regression coverage.

Re-reviewed a284af6: oversized blank text, links and email now respect their limits; malformed UTF-16 values and property names return consistent validation problems without throwing. Valid surrogate pairs still pass.

Verified the original repros and ran 11 focused TypeScript tests and 13 focused .NET tests, all passing. All three CI jobs are green on this commit. No remaining blocking findings.

@procoder-ji
procoder-ji merged commit 4016bb1 into main Nov 10, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Portfolio: shared field rules, username rules and reserved names

3 participants