ガイド 第7

ユーザー / 組織プロフィール

org members・slug 正規化

@event/fours-sdk

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 エンドポイント
extractSlugURL / 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 / asciiNameFourSUser → 表示名
currentMembers / pastMembers現/過去所属の抽出
normName氏名正規化(旧字体→新字体)

extractSlug — URL でも生でも受ける

4S の公開プロフィール URL、パス、素の slug のいずれを渡しても最後のパスセグメントを取り出す(decodeURIComponent 済み)。UI の入力欄が「URL を貼る / slug を打つ」のどちらでも壊れないための入口関数である。

入力extractSlug の結果
https://4s.link/ja/asaoasao
/ja/asaoasao
asaoasao
https://4s.link/en/team/foofoo(最後のセグメント)
"" / 空白のみ""

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)
}

fetch4SUserGET /slug/{slug} を叩き、レスポンスの type"user" でなければ null。応答は FourSUsersocialLinks / organizationMembers / tags 付き)だが、socialLinks 自体が null のケースがある — 必ず ?. でアクセスする。

ヘルパー返り値規則
fullName(user)表示名nativeNamefirstName + lastNameslug
asciiName(user)ラテン表記firstName + lastName を大文字化
currentMembers(user)FourSOrganizationMember[]current === true かつ organization 非 null
pastMembers(user)FourSOrganizationMember[]current === falseendDate 降順

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.orgsOrgAffiliationorgId / 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 / biomember.user
orgTitleこの組織での肩書(membership.title
careerTitle本人の経歴肩書(user.title
role"ADMIN" / "MEMBER" / null(大文字化して判別)
current在籍中フラグ

数だけ欲しいときは fetchOrgMemberCount(config, orgId)/slug/{orgId}type === "organization")の data.memberCount を安価に返す(全ページ走査より軽い)。

mode ガードに関する注意

event.tsfetchEventfoursFetchPublic 経由で mock-locked / stubnull に落とすが、profile.tsfetch4SUser / fetchPublicProfile / fetchOrgMembers は transport 層を通さず生 fetch を使う。したがって mode ガードは効かず、config.apiBase だけを見て 4S を叩く。オフライン UI 開発でプロフィール取得も遮断したい場合は、呼び出し側で config.mode を見て早期 return するか、サンプルデータへ差し替える。