profile-read 面は 認証なしの公開 API だけで完結する。個人の公開プロフィール(/slug/{userSlug})と組織のロスター(/organizations/{id}/members)を型付きで読み、外部名簿との名寄せ用の正規化関数を添える。実装は @event/fours-sdk/src/profile.ts(@harvested-from spikes-lp src/lib/fourS.ts / ivs-cms src/lib/fourS.ts)。
この面の export 一覧
| 関数 / 型 | 役割 | 4S エンドポイント |
|---|---|---|
extractSlug | URL / raw を slug に正規化 | — |
fetch4SUser | フル user 応答(FourSUser)を取得 | GET /slug/{userSlug} |
fetchPublicProfile | 正規化済み PublicProfile(10 分キャッシュ) | GET /slug/{userSlug} |
fetchPublicProfiles | 複数 slug を限定並列で | 同上(キャッシュ共有) |
fetchOrgMembers | 組織メンバー全ページ結合 | GET /organizations/{id}/members |
fetchOrgMemberCount | メンバー数のみ安価に | GET /slug/{orgId} |
fullName / asciiName | FourSUser → 表示名 | — |
currentMembers / pastMembers | 現/過去所属の抽出 | — |
normName | 氏名正規化(旧字体→新字体) | — |
extractSlug — URL でも生でも受ける
4S の公開プロフィール URL、パス、素の slug のいずれを渡しても最後のパスセグメントを取り出す(decodeURIComponent 済み)。UI の入力欄が「URL を貼る / slug を打つ」のどちらでも壊れないための入口関数である。
| 入力 | extractSlug の結果 |
|---|---|
https://4s.link/ja/asao | asao |
/ja/asao | asao |
asao | asao |
https://4s.link/en/team/foo | foo(最後のセグメント) |
"" / 空白のみ | "" |
URL パースに失敗しても前後スラッシュを剥がして返す(throw しない)。fetch4SUser は内部で必ずこれを通すので、呼び出し側は正規化を意識しなくてよい。
fetch4SUser とユーザーヘルパー
import { createFoursConfig, fetch4SUser, currentMembers, fullName } from "@event/fours-sdk";
const config = createFoursConfig();
const user = await fetch4SUser(config, "https://4s.link/ja/asao");
// type !== "user" / 失敗 → null
if (user) {
const name = fullName(user); // nativeName 優先
const orgs = currentMembers(user); // 現所属のみ(organization 非 null)
}
fetch4SUser は GET /slug/{slug} を叩き、レスポンスの type が "user" でなければ null。応答は FourSUser(socialLinks / organizationMembers / tags 付き)だが、socialLinks 自体が null のケースがある — 必ず ?. でアクセスする。
| ヘルパー | 返り値 | 規則 |
|---|---|---|
fullName(user) | 表示名 | nativeName → firstName + lastName → slug |
asciiName(user) | ラテン表記 | firstName + lastName を大文字化 |
currentMembers(user) | FourSOrganizationMember[] | current === true かつ organization 非 null |
pastMembers(user) | FourSOrganizationMember[] | current === false、endDate 降順 |
4S 側データ欠損(organization が null の membership)は currentMembers / pastMembers が源流で除外する。
normName — 外部名簿との名寄せ
normName は空白(全角含む)を除去し、旧字体を新字体へ畳んで照合キーを作る。展示会 CSV や登壇者リストの氏名を 4S の nativeName に突き合わせる用途。
| 変換 | 例 |
|---|---|
| 空白除去 | "浅尾 尭洋" → "浅尾尭洋" |
| 旧字体→新字体 | 﨑→崎 邊/邉→辺 髙→高 澤→沢 齊/齋→斉 國→国 濱→浜 他 |
正規化テーブルは KANJI_VARIANTS(内部定数)。表記ゆれを吸収するのは照合キーだけで、表示名は 4S の値をそのまま使う(勝手に字体を書き換えて表示しない)。
fetchPublicProfile — 正規化済みプロフィール
FourSUser の生形は消費側にとって重い。fetchPublicProfile は表示に必要な形(PublicProfile)へ畳んで返す。10 分 TTL の in-memory キャッシュを持ち、null(不在・失敗)もキャッシュする(存在しない slug への連打を防ぐ)。
import { fetchPublicProfile, fetchPublicProfiles } from "@event/fours-sdk";
const p = await fetchPublicProfile(config, "asao");
// => { slug, name, avatarUrl, title, bio, orgs: OrgAffiliation[] } | null
// 一覧向けに複数まとめて(限定並列・キャッシュ共有、既定 concurrency 6)
const map = await fetchPublicProfiles(config, ["asao", "foo", "bar"]);
PublicProfile.orgs は OrgAffiliation(orgId / orgName / orgType / role / current / title / isEducation)の配列で、organization.name が空の membership は落とす。TTL は opts.ttlMs で上書きできる。
fetchOrgMembers — 組織ロスター
fetchOrgMembers(config, orgId) は GET /organizations/{id}/members?page&perPage=100 を 全ページ結合して返す(totalPages は 50 ページで打ち切り、10 分キャッシュ、エラー時は [])。1 ページ目で総ページ数を得てから 2 ページ目以降を並列取得する。
OrgMember フィールド | 由来 |
|---|---|
slug / name / avatarUrl / bio | member.user |
orgTitle | この組織での肩書(membership.title) |
careerTitle | 本人の経歴肩書(user.title) |
role | "ADMIN" / "MEMBER" / null(大文字化して判別) |
current | 在籍中フラグ |
数だけ欲しいときは fetchOrgMemberCount(config, orgId) が /slug/{orgId}(type === "organization")の data.memberCount を安価に返す(全ページ走査より軽い)。
mode ガードに関する注意
event.ts の fetchEvent は foursFetchPublic 経由で mock-locked / stub を null に落とすが、profile.ts の fetch4SUser / fetchPublicProfile / fetchOrgMembers は transport 層を通さず生 fetch を使う。したがって mode ガードは効かず、config.apiBase だけを見て 4S を叩く。オフライン UI 開発でプロフィール取得も遮断したい場合は、呼び出し側で config.mode を見て早期 return するか、サンプルデータへ差し替える。