チーム開発の基礎を学ぶ。
ビジョンを共有するためにアプリの概要と設計を行っていく。
#テーマ:SNS
プログラミング経験がほぼ皆無な友人との開発を行うため興味を持ってもらいやすいSNSをテーマとして選定した。
やること
①システム開発の概要、目的を明確にする
②ユーザーストーリーの作成
③必要な機能の決定
④簡単なデータベース設計
目標
趣味ベース、オフラインでつながることのできるSNSが作りたい。
オフラインで出会うことのメリット
・新たな交友関係の構築
・オンラインよりも効率的な知識や技能の共有。
・共同作業や創造的なプロジェクトが可能になる。
既存SNSの課題
オンライン重視のコミュニケーション:
多くのSNS(Instagram, Twitter, TikTokなど)は、オンライン上でのコンテンツシェアやフォロワーとのつながりが中心。
オンラインで完結する「軽いつながり」が主流となっており、特定のテーマや趣味で深い交流ができる場が限られている。
オフラインでの出会いに対する心理的な準備:
マッチングアプリなどでは実際にオフラインで出会うケースが多い一方で多くのSNSでは実際に出会うことは少ない。これは、ユーザーがアプリに登録する時点で「会うこと」を念頭に置いていないため、心構えができておらず、会うことに対する抵抗感や準備不足が生じやすいことに起因する。
ユーザーストーリー
ユーザーストーリー 1: 新規ユーザーがアカウントを作成し、初めてクラブに参加するまでの流れ
ユーザー: 新規ユーザー
アプリをダウンロードする。
初回起動時に、「新規登録」を選択し、メールアドレスとパスワードを入力してアカウントを作成する。また、Googleアカウントを使って簡単に登録を行うこともできる。
アカウント作成後、趣味や関心に基づくクラブをいくつか提案される。「学園都市エンジニア志望」を見つけ、参加申請を送信する。
クラブの管理者が参加を承認し、クラブ専用のタイムラインにアクセスできるようになる。
各クラブのタイムライン、またはチャットにアクセスできる。
また、検索バー、趣味タグ、場所フィルターで他のクラブを探すこともできる。
ユーザーストーリー 3: クラブに参加してからオフラインで会うまでの流れ
ユーザー: クラブ参加者
クラブのタイムラインをチェックしていると、クラブ内のリーダーから「交流会イベント」が告知されていることに気づく。
イベントページで日時や場所(地元の公園での撮影会)を確認、「参加する」をタップして参加を表明する。
イベント当日、集合場所に向かう。オフラインで初めてクラブの他のメンバーと交流する。
イベント後、撮った写真をイベント専用タイムラインにアップロードし、他の参加者とも交流を続ける。
実装する機能
- ユーザー向け機能
1.1. アカウント関連機能
・新規ユーザー登録: メールアドレス、SNS認証(Google、Facebookなど)を利用したアカウント登録。
・ログイン/ログアウト: ユーザー認証を行い、セッションを管理する。
・アカウント削除: ユーザーが自分のアカウントを削除する機能。
1.2. プロフィール機能
・プロフィール作成・編集: プロフィール写真、ユーザーネーム、自己紹介、趣味などの基本情報を編集できる。
1.3. クラブ関連機能
・クラブ作成: ユーザーが新しいクラブを作成できる。クラブ名、説明、テーマ画像、趣味タグを設定可能。
・クラブ参加申請 クラブに参加するための申請を送る。
・クラブ参加: 承認されるとクラブに参加し、クラブ内の投稿やチャットに参加できる。
・クラブ退会: クラブから自由に退会できる機能。
・クラブの参加管理: クラブに参加したメンバーの管理や、新しいメンバーを承認する機能。
1.4. クラブ内機能
・クラブ専用チャット: クラブ内のメンバー専用のチャット。
・投稿機能: テキスト、画像、短い動画(1分以内)の投稿が可能。
・コメント機能: 投稿に対してコメントを残す機能。
・「いいね」機能: 投稿やコメントに対して「いいね」を押せる機能。
・タグ付け: 投稿に趣味タグや関連タグをつけて、他のユーザーにアピールできる。
・クラブ内のグループチャット: クラブメンバーがリアルタイムで会話できるチャットルーム。
1.5. 検索・フィルタリング機能
・フリーワード検索: クラブや投稿、ユーザーをキーワードで検索できる。
・趣味タグによるフィルタリング: 興味のある趣味タグを選択して、関連するクラブを検索。
・高度なフィルター機能: メンバー数、活動頻度、場所などでクラブを絞り込む。
1.6. イベント関連機能
・イベント作成: クラブや個人がオンライン・オフラインのイベントを作成し、参加者を募集できる。日時、場所、説明、人数制限を設定可能。
・イベント参加: ユーザーがイベントに参加を表明し、参加者リストに追加される。
1.7. ユーザー関連機能
・1対1の個人チャット: 個人チャット内でリアルタイムでテキストメッセージを送信。
・ポストの投稿:twitterのようにフォロワーたちとポストを共有、自分の参加しているクラブの情報などを発信。
・フォロー機能
- 管理者向け機能
2.1. クラブ管理者機能
・メンバー管理: クラブ参加申請の承認/拒否や、既存メンバーの削除が可能。
・クラブ情報編集: クラブ名や説明、タグ、テーマ画像の変更が可能。
・イベント管理: クラブで開催するイベントの編集、キャンセル、リスケジュールが可能。
2.2. システム管理者機能
・クラブの監視: 不適切なクラブやスパムクラブの削除。
・アクティビティ監視: ユーザーやクラブの活動状況を監視して、不正行為を検出。
- その他の機能
3.1. サポート機能
・ユーザーサポート: 問い合わせフォームやFAQページ、サポートチャットを提供し、ユーザーが問題を報告したり、質問に答えるための機能。
3.2. セキュリティ機能
・認証と認可: JWTベースの認証と、アクセス権に基づいた機能制御(例: 管理者のみが利用できる機能)。
実装の優先順位
ユーザーストーリーに沿って実装を進めていく。
①アカウント、ユーザー関連機能
②クラブ関連機能
③検索機能
⑤その他機能
技術スタック
最も使い慣れたMERNスタックで進めていく
データベース設計
エンティティの洗い出しとエンティティの関係性の整理
・クラブはざまざまなイベントを開催する
・クラブのトークでクラブ参加者たちが参加する
・ユーザーは複数のクラブに参加できる
・ユーザーはあるユーザーとダイレクトメッセージでやり取りができる
・ユーザーはTwitterのようにポストを行うことができ、タイムラインでは自分の投稿とフォローしているユーザーの投稿を閲覧できる。(プロフィールページでは自分の投稿のみを閲覧できる)
・ポストにはコメントが付く。
・クラブはユーザーに対して招待リクエストを行うことができる。
・ユーザーはクラブに対して参加リクエストを行うことができる。
・クラブの管理者の承認があって参加できる。
・クラブはイベントを開催できる(日時や参加決定者他様々な情報を持つ)
User ⇔ Club: 多対多 (many-to-many)
User ⇔ Post: 1対多 (one-to-many)
User ⇔ Comment: 1対多 (one-to-many)
User ⇔ User(Following/Follower): 多対多 (many-to-many)
User ⇔ Chat: 多対多 (many-to-many)
Club ⇔ Event: 1対多 (one-to-many)
Club ⇔ Invitation: 1対多 (one-to-many)
Club ⇔ JoinRequest: 1対多 (one-to-many)
Post ⇔ Comment: 1対多 (one-to-many)
Event ⇔ User: 多対多 (many-to-many)
User ⇔ Invitation: 1対多 (one-to-many)
User ⇔ JoinRequest: 1対多 (one-to-many)
User(ユーザー)スキーマ
const UserSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
profilePicture: { type: String, default: '' },
bio: { type: String, default: '' },
hobbies: [String], // 趣味タグ
clubs: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Club' }], // 所属クラブ
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }], // 投稿リスト
following: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], // フォローしているユーザー
followers: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], // フォロワー
directMessages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Chat' }], // ダイレクトメッセージ
}, { timestamps: true });
Club(クラブ)スキーマ
const ClubSchema = new mongoose.Schema({
name: { type: String, required: true, unique: true },
description: { type: String, required: true },
themeImage: { type: String, default: '' },
tags: [String], // 趣味タグ
members: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], // メンバーリスト
events: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Event' }], // クラブイベント
posts: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }], // クラブ内投稿
invitations: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Invitation' }], // 招待リクエスト
joinRequests: [{ type: mongoose.Schema.Types.ObjectId, ref: 'JoinRequest' }], // 参加リクエスト
chat: { type: mongoose.Schema.Types.ObjectId, ref: 'Chat' }, // クラブ内トークチャット
admins: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], // クラブ管理者
}, { timestamps: true });
Post(投稿)スキーマ
const PostSchema = new mongoose.Schema({
content: { type: String, required: true }, // 投稿内容
media: { type: String, default: '' }, // メディアファイル
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, // 投稿者
club: { type: mongoose.Schema.Types.ObjectId, ref: 'Club' }, // 所属クラブ(オプション)
tags: [String], // タグ
comments: [{ // コメント
content: { type: String },
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
}],
likes: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], // いいねリスト
}, { timestamps: true });
Comment(コメント)スキーマ
const CommentSchema = new mongoose.Schema({
content: { type: String, required: true },
author: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, // コメント投稿者
post: { type: mongoose.Schema.Types.ObjectId, ref: 'Post', required: true }, // 対象ポスト
}, { timestamps: true });
Event(イベント)スキーマ
const EventSchema = new mongoose.Schema({
title: { type: String, required: true }, // イベント名
description: { type: String, required: true }, // イベント説明
date: { type: Date, required: true }, // イベント日時
location: { type: String, required: true }, // 場所
organizer: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, // イベント主催者
participants: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }], // 参加者
club: { type: mongoose.Schema.Types.ObjectId, ref: 'Club' }, // 関連クラブ
}, { timestamps: true });
Chat(チャット)スキーマ
const ChatSchema = new mongoose.Schema({
participants: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }], // 参加者(1対1またはグループ)
messages: [{
sender: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, // メッセージ送信者
content: { type: String, required: true }, // メッセージ内容
sentAt: { type: Date, default: Date.now },
}],
}, { timestamps: true });
Invitation(クラブ招待リクエスト)スキーマ
const InvitationSchema = new mongoose.Schema({
club: { type: mongoose.Schema.Types.ObjectId, ref: 'Club', required: true }, // クラブ
invitee: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, // 招待されるユーザー
invitedBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, // 招待者
status: { type: String, enum: ['pending', 'accepted', 'rejected'], default: 'pending' }, // ステータス
}, { timestamps: true });
JoinRequest(クラブ参加リクエスト)スキーマ
const JoinRequestSchema = new mongoose.Schema({
club: { type: mongoose.Schema.Types.ObjectId, ref: 'Club', required: true }, // クラブ
requester: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true }, // 参加申請者
status: { type: String, enum: ['pending', 'approved', 'rejected'], default: 'pending' }, // ステータス
}, { timestamps: true });