diff --git a/backend/pkg/handler/members.go b/backend/pkg/handler/members.go index a323e92..33df88f 100644 --- a/backend/pkg/handler/members.go +++ b/backend/pkg/handler/members.go @@ -201,6 +201,7 @@ func (h *Handler) PostApiMembers(c *gin.Context) { Message: "メンバーを登録しました", }) } + // if a, ok := v.([]any); ok { // out := make([]string, 0, len(a)) // for _, x := range a { diff --git a/frontend/components/member-list.tsx b/frontend/components/member-list.tsx index 9f6267f..2b7e6c1 100644 --- a/frontend/components/member-list.tsx +++ b/frontend/components/member-list.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useState, useEffect } from "react" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { Badge } from "@/components/ui/badge" @@ -8,15 +8,20 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } f import { MessageSquare, Github } from "lucide-react" import ReactMarkdown from "react-markdown" -interface Member { +// API から返ってくる一覧用の型(MemberSummary) +interface MemberSummary { id: string name: string nickname: string + roles: string[] + avatar?: string +} + +// API から返ってくる詳細用の型(MemberDetail) +interface MemberDetail extends MemberSummary { department: string year: string - roles: string[] bio: string - avatar?: string accounts: { line: boolean discord: boolean @@ -33,110 +38,78 @@ interface Member { }> } -const mockMembers: Member[] = [ - { - id: "1", - name: "田中 太郎", - nickname: "たなたろ", - department: "情報工学部", - year: "2年生", - roles: ["Web班", "副代表"], - bio: `# 自己紹介 - -プログラミングが好きな2年生です! - -## 興味のある分野 -- **Webアプリ開発** -- **機械学習** -- **UI/UXデザイン** - -よろしくお願いします! 🚀`, - accounts: { line: true, discord: true, github: true }, - links: [ - { title: "個人ブログ", url: "https://tanaka-blog.com" }, - { title: "ポートフォリオ", url: "https://tanaka-portfolio.dev" }, - ], - events: [ - { name: "新歓BBQ大会", date: "2024-04-15", status: "upcoming" }, - { name: "冬合宿", date: "2024-02-10", status: "completed" }, - ], - }, - { - id: "2", - name: "佐藤 花子", - nickname: "さとはな", - department: "経済学部", - year: "3年生", - roles: ["イベント班", "代表"], - bio: "イベント企画が大好きです!みんなで楽しい思い出を作りましょう✨", - accounts: { line: true, discord: true, github: false }, - links: [], - events: [{ name: "文化祭出展準備", date: "2024-05-01", status: "upcoming" }], - }, - { - id: "3", - name: "山田 次郎", - nickname: "やまじ", - department: "理学部", - year: "1年生", - roles: ["新入生"], - bio: "新入生です!よろしくお願いします🌟", - accounts: { line: true, discord: true, github: false }, - links: [], - events: [{ name: "新歓BBQ大会", date: "2024-04-15", status: "upcoming" }], - }, - { - id: "4", - name: "鈴木 一郎", - nickname: "すずいち", - department: "情報工学部", - year: "1年生", - roles: ["新入生"], - bio: "プログラミング初心者ですが、頑張ります!", - accounts: { line: true, discord: true, github: true }, - links: [], - events: [{ name: "新歓BBQ大会", date: "2024-04-15", status: "upcoming" }], - }, - { - id: "5", - name: "高橋 美咲", - nickname: "みさき", - department: "経済学部", - year: "2年生", - roles: ["広報班"], - bio: "SNS運用とデザインが得意です📱", - accounts: { line: true, discord: true, github: false }, - links: [{ title: "Instagram", url: "https://instagram.com/misaki" }], - events: [], - }, - { - id: "6", - name: "伊藤 健太", - nickname: "けんた", - department: "情報工学部", - year: "4年生", - roles: ["4年生", "技術顧問"], - bio: "卒業研究でAI開発をしています。技術的な質問はお気軽に!", - accounts: { line: true, discord: true, github: true }, - links: [{ title: "研究室ページ", url: "https://lab.example.com" }], - events: [], - }, -] +const API_BASE_URL = "http://localhost:8080" export default function MemberList() { - const [selectedMember, setSelectedMember] = useState(null) + const [members, setMembers] = useState([]) + const [selectedMember, setSelectedMember] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + // 一覧を取得 + useEffect(() => { + fetch(`${API_BASE_URL}/api/members`) + .then((res) => { + if (!res.ok) throw new Error("メンバー一覧の取得に失敗しました") + return res.json() + }) + .then((data) => { + setMembers(data) + setLoading(false) + }) + .catch((err) => { + setError(err.message) + setLoading(false) + }) + }, []) + + // 詳細を取得 + const handleMemberClick = async (memberId: string) => { + try { + const res = await fetch(`${API_BASE_URL}/api/members/${memberId}`) + if (!res.ok) throw new Error("メンバー詳細の取得に失敗しました") + const data = await res.json() + setSelectedMember(data) + } catch (err) { + console.error(err) + } + } - const filteredMembers = mockMembers + if (loading) { + return ( +
+
+ 読み込み中... +
+ ) + } + + if (error) { + return ( +
+

{error}

+

バックエンドサーバーが起動しているか確認してください

+
+ ) + } + + if (members.length === 0) { + return ( +
+

メンバーが登録されていません

+
+ ) + } return (
{/* メンバーカード一覧 */}
- {filteredMembers.map((member) => ( + {members.map((member) => ( setSelectedMember(member)} + onClick={() => handleMemberClick(member.id)} > @@ -148,12 +121,12 @@ export default function MemberList() {

{member.name}

@{member.nickname}

- {member.roles.slice(0, 2).map((role) => ( + {(member.roles ?? []).slice(0, 2).map((role) => ( {role} ))} - {member.roles.length > 2 && ( + {(member.roles ?? []).length > 2 && ( +{member.roles.length - 2} @@ -201,7 +174,7 @@ export default function MemberList() {

ロール

- {selectedMember.roles.map((role) => ( + {(selectedMember.roles ?? []).map((role) => ( {role} diff --git a/frontend/src/lib/api/.openapi-generator/FILES b/frontend/src/lib/api/.openapi-generator/FILES index 158e042..6eb0fc6 100644 --- a/frontend/src/lib/api/.openapi-generator/FILES +++ b/frontend/src/lib/api/.openapi-generator/FILES @@ -9,6 +9,9 @@ docs/BasicInfo.md docs/DefaultApi.md docs/LineOAuthResponse.md docs/LineUser.md +docs/MemberCreate.md +docs/MemberCreateAccounts.md +docs/MemberCreateResponse.md docs/MemberDetail.md docs/MemberDetailAllOfAccounts.md docs/MemberDetailAllOfEvents.md @@ -22,6 +25,9 @@ models/basic-info.ts models/index.ts models/line-oauth-response.ts models/line-user.ts +models/member-create-accounts.ts +models/member-create-response.ts +models/member-create.ts models/member-detail-all-of-accounts.ts models/member-detail-all-of-events.ts models/member-detail-all-of-links.ts diff --git a/frontend/src/lib/api/apis/default-api.ts b/frontend/src/lib/api/apis/default-api.ts index 5b32c13..784af3c 100644 --- a/frontend/src/lib/api/apis/default-api.ts +++ b/frontend/src/lib/api/apis/default-api.ts @@ -26,6 +26,10 @@ import type { BasicInfo } from '../models'; // @ts-ignore import type { LineOAuthResponse } from '../models'; // @ts-ignore +import type { MemberCreate } from '../models'; +// @ts-ignore +import type { MemberCreateResponse } from '../models'; +// @ts-ignore import type { MemberDetail } from '../models'; // @ts-ignore import type { MemberSummary } from '../models'; @@ -143,6 +147,42 @@ export const DefaultApiAxiosParamCreator = function (configuration?: Configurati options: localVarRequestOptions, }; }, + /** + * 新しいメンバーを登録します。 + * @summary メンバーを登録する + * @param {MemberCreate} memberCreate + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiMembersPost: async (memberCreate: MemberCreate, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'memberCreate' is not null or undefined + assertParamExists('apiMembersPost', 'memberCreate', memberCreate) + const localVarPath = `/api/members`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(memberCreate, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * 現在登録されているユーザーの基本情報を返します。 * @summary 基本情報を取得する @@ -258,6 +298,19 @@ export const DefaultApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['DefaultApi.apiMembersIdGet']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * 新しいメンバーを登録します。 + * @summary メンバーを登録する + * @param {MemberCreate} memberCreate + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async apiMembersPost(memberCreate: MemberCreate, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.apiMembersPost(memberCreate, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['DefaultApi.apiMembersPost']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * 現在登録されているユーザーの基本情報を返します。 * @summary 基本情報を取得する @@ -323,6 +376,16 @@ export const DefaultApiFactory = function (configuration?: Configuration, basePa apiMembersIdGet(id: string, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.apiMembersIdGet(id, options).then((request) => request(axios, basePath)); }, + /** + * 新しいメンバーを登録します。 + * @summary メンバーを登録する + * @param {MemberCreate} memberCreate + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + apiMembersPost(memberCreate: MemberCreate, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.apiMembersPost(memberCreate, options).then((request) => request(axios, basePath)); + }, /** * 現在登録されているユーザーの基本情報を返します。 * @summary 基本情報を取得する @@ -388,6 +451,18 @@ export class DefaultApi extends BaseAPI { return DefaultApiFp(this.configuration).apiMembersIdGet(id, options).then((request) => request(this.axios, this.basePath)); } + /** + * 新しいメンバーを登録します。 + * @summary メンバーを登録する + * @param {MemberCreate} memberCreate + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public apiMembersPost(memberCreate: MemberCreate, options?: RawAxiosRequestConfig) { + return DefaultApiFp(this.configuration).apiMembersPost(memberCreate, options).then((request) => request(this.axios, this.basePath)); + } + /** * 現在登録されているユーザーの基本情報を返します。 * @summary 基本情報を取得する diff --git a/frontend/src/lib/api/docs/DefaultApi.md b/frontend/src/lib/api/docs/DefaultApi.md index ae3f7f1..7450405 100644 --- a/frontend/src/lib/api/docs/DefaultApi.md +++ b/frontend/src/lib/api/docs/DefaultApi.md @@ -7,6 +7,7 @@ All URIs are relative to *http://localhost:8080* |[**apiLineOauthGet**](#apilineoauthget) | **GET** /api/line-oauth | LINE OAuthコールバック| |[**apiMembersGet**](#apimembersget) | **GET** /api/members | メンバー一覧を取得する| |[**apiMembersIdGet**](#apimembersidget) | **GET** /api/members/{id} | メンバー詳細を取得する| +|[**apiMembersPost**](#apimemberspost) | **POST** /api/members | メンバーを登録する| |[**apiProfileBasicInfoGet**](#apiprofilebasicinfoget) | **GET** /api/profile/basic-info | 基本情報を取得する| |[**apiProfileBasicInfoPut**](#apiprofilebasicinfoput) | **PUT** /api/profile/basic-info | 基本情報を更新する| @@ -164,6 +165,60 @@ No authorization required [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) +# **apiMembersPost** +> MemberCreateResponse apiMembersPost(memberCreate) + +新しいメンバーを登録します。 + +### Example + +```typescript +import { + DefaultApi, + Configuration, + MemberCreate +} from './api'; + +const configuration = new Configuration(); +const apiInstance = new DefaultApi(configuration); + +let memberCreate: MemberCreate; // + +const { status, data } = await apiInstance.apiMembersPost( + memberCreate +); +``` + +### Parameters + +|Name | Type | Description | Notes| +|------------- | ------------- | ------------- | -------------| +| **memberCreate** | **MemberCreate**| | | + + +### Return type + +**MemberCreateResponse** + +### Authorization + +No authorization required + +### HTTP request headers + + - **Content-Type**: application/json + - **Accept**: application/json + + +### HTTP response details +| Status code | Description | Response headers | +|-------------|-------------|------------------| +|**201** | 登録成功 | - | +|**400** | バリデーションエラー | - | +|**500** | サーバーエラー | - | + +[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) + # **apiProfileBasicInfoGet** > BasicInfo apiProfileBasicInfoGet() diff --git a/frontend/src/lib/api/docs/MemberCreate.md b/frontend/src/lib/api/docs/MemberCreate.md new file mode 100644 index 0000000..04b913d --- /dev/null +++ b/frontend/src/lib/api/docs/MemberCreate.md @@ -0,0 +1,36 @@ +# MemberCreate + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**name** | **string** | | [default to undefined] +**nickname** | **string** | | [default to undefined] +**department** | **string** | | [default to undefined] +**year** | **string** | | [default to undefined] +**bio** | **string** | Markdown形式の自己紹介 | [default to undefined] +**roles** | **Array<string>** | | [default to undefined] +**avatar** | **string** | | [optional] [default to undefined] +**accounts** | [**MemberCreateAccounts**](MemberCreateAccounts.md) | | [default to undefined] +**links** | [**Array<MemberDetailAllOfLinks>**](MemberDetailAllOfLinks.md) | | [optional] [default to undefined] + +## Example + +```typescript +import { MemberCreate } from './api'; + +const instance: MemberCreate = { + name, + nickname, + department, + year, + bio, + roles, + avatar, + accounts, + links, +}; +``` + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/frontend/src/lib/api/docs/MemberCreateAccounts.md b/frontend/src/lib/api/docs/MemberCreateAccounts.md new file mode 100644 index 0000000..cc0550b --- /dev/null +++ b/frontend/src/lib/api/docs/MemberCreateAccounts.md @@ -0,0 +1,24 @@ +# MemberCreateAccounts + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**line** | **boolean** | | [default to undefined] +**discord** | **boolean** | | [default to undefined] +**github** | **boolean** | | [default to undefined] + +## Example + +```typescript +import { MemberCreateAccounts } from './api'; + +const instance: MemberCreateAccounts = { + line, + discord, + github, +}; +``` + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/frontend/src/lib/api/docs/MemberCreateResponse.md b/frontend/src/lib/api/docs/MemberCreateResponse.md new file mode 100644 index 0000000..1afd020 --- /dev/null +++ b/frontend/src/lib/api/docs/MemberCreateResponse.md @@ -0,0 +1,22 @@ +# MemberCreateResponse + + +## Properties + +Name | Type | Description | Notes +------------ | ------------- | ------------- | ------------- +**id** | **string** | 登録されたメンバーのID | [default to undefined] +**message** | **string** | | [default to undefined] + +## Example + +```typescript +import { MemberCreateResponse } from './api'; + +const instance: MemberCreateResponse = { + id, + message, +}; +``` + +[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) diff --git a/frontend/src/lib/api/models/index.ts b/frontend/src/lib/api/models/index.ts index f8fdb93..ec12b6b 100644 --- a/frontend/src/lib/api/models/index.ts +++ b/frontend/src/lib/api/models/index.ts @@ -1,6 +1,9 @@ export * from './basic-info'; export * from './line-oauth-response'; export * from './line-user'; +export * from './member-create'; +export * from './member-create-accounts'; +export * from './member-create-response'; export * from './member-detail'; export * from './member-detail-all-of-accounts'; export * from './member-detail-all-of-events'; diff --git a/frontend/src/lib/api/models/member-create-accounts.ts b/frontend/src/lib/api/models/member-create-accounts.ts new file mode 100644 index 0000000..078df6f --- /dev/null +++ b/frontend/src/lib/api/models/member-create-accounts.ts @@ -0,0 +1,42 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * 基本情報編集API + * ユーザーの基本情報を編集するためのAPI + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface MemberCreateAccounts + */ +export interface MemberCreateAccounts { + /** + * + * @type {boolean} + * @memberof MemberCreateAccounts + */ + 'line': boolean; + /** + * + * @type {boolean} + * @memberof MemberCreateAccounts + */ + 'discord': boolean; + /** + * + * @type {boolean} + * @memberof MemberCreateAccounts + */ + 'github': boolean; +} + diff --git a/frontend/src/lib/api/models/member-create-response.ts b/frontend/src/lib/api/models/member-create-response.ts new file mode 100644 index 0000000..c9ae223 --- /dev/null +++ b/frontend/src/lib/api/models/member-create-response.ts @@ -0,0 +1,36 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * 基本情報編集API + * ユーザーの基本情報を編集するためのAPI + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * + * @export + * @interface MemberCreateResponse + */ +export interface MemberCreateResponse { + /** + * 登録されたメンバーのID + * @type {string} + * @memberof MemberCreateResponse + */ + 'id': string; + /** + * + * @type {string} + * @memberof MemberCreateResponse + */ + 'message': string; +} + diff --git a/frontend/src/lib/api/models/member-create.ts b/frontend/src/lib/api/models/member-create.ts new file mode 100644 index 0000000..8e6a2a3 --- /dev/null +++ b/frontend/src/lib/api/models/member-create.ts @@ -0,0 +1,84 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * 基本情報編集API + * ユーザーの基本情報を編集するためのAPI + * + * The version of the OpenAPI document: 1.0.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { MemberCreateAccounts } from './member-create-accounts'; +// May contain unused imports in some cases +// @ts-ignore +import type { MemberDetailAllOfLinks } from './member-detail-all-of-links'; + +/** + * + * @export + * @interface MemberCreate + */ +export interface MemberCreate { + /** + * + * @type {string} + * @memberof MemberCreate + */ + 'name': string; + /** + * + * @type {string} + * @memberof MemberCreate + */ + 'nickname': string; + /** + * + * @type {string} + * @memberof MemberCreate + */ + 'department': string; + /** + * + * @type {string} + * @memberof MemberCreate + */ + 'year': string; + /** + * Markdown形式の自己紹介 + * @type {string} + * @memberof MemberCreate + */ + 'bio': string; + /** + * + * @type {Array} + * @memberof MemberCreate + */ + 'roles': Array; + /** + * + * @type {string} + * @memberof MemberCreate + */ + 'avatar'?: string; + /** + * + * @type {MemberCreateAccounts} + * @memberof MemberCreate + */ + 'accounts': MemberCreateAccounts; + /** + * + * @type {Array} + * @memberof MemberCreate + */ + 'links'?: Array; +} + diff --git a/openapi.yaml b/openapi.yaml index dede5cb..23613ce 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -56,6 +56,26 @@ paths: $ref: '#/components/schemas/MemberSummary' '500': description: サーバーエラー + post: + summary: メンバーを登録する + description: 新しいメンバーを登録します。 + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MemberCreate' + responses: + '201': + description: 登録成功 + content: + application/json: + schema: + $ref: '#/components/schemas/MemberCreateResponse' + '400': + description: バリデーションエラー + '500': + description: サーバーエラー /api/members/{id}: get: @@ -322,3 +342,81 @@ components: type: string description: ステータスメッセージ example: いつでも連絡ください + MemberCreate: + type: object + required: + - name + - nickname + - department + - year + - bio + - roles + - accounts + properties: + name: + type: string + example: "田中 太郎" + nickname: + type: string + example: "たなたろ" + department: + type: string + example: "情報工学部" + year: + type: string + example: "2年生" + bio: + type: string + description: Markdown形式の自己紹介 + example: "プログラミングが好きな2年生です!" + roles: + type: array + items: + type: string + example: ["Web班", "副代表"] + avatar: + type: string + example: "https://example.com/avatar.jpg" + accounts: + type: object + required: + - line + - discord + - github + properties: + line: + type: boolean + example: true + discord: + type: boolean + example: true + github: + type: boolean + example: false + links: + type: array + items: + type: object + required: + - title + - url + properties: + title: + type: string + example: "個人ブログ" + url: + type: string + example: "https://example.com" + MemberCreateResponse: + type: object + required: + - id + - message + properties: + id: + type: string + description: 登録されたメンバーのID + example: "abc123" + message: + type: string + example: "メンバーを登録しました"