backend/pkg/chat/repository/room.go

405 lines
9.0 KiB
Go
Raw Normal View History

2026-01-06 07:15:18 +00:00
package repository
import (
"backend/pkg/chat/domain/chat"
"backend/pkg/chat/domain/entity"
"backend/pkg/chat/domain/repository"
"backend/pkg/library/cassandra"
"context"
"fmt"
"time"
"github.com/gocql/gocql"
)
type roomRepository struct {
roomRepo cassandra.Repository[entity.Room]
memberRepo cassandra.Repository[entity.RoomMember]
userRoomRepo cassandra.Repository[entity.UserRoom]
db *cassandra.DB
keyspace string
}
// RoomRepositoryParam 創建 RoomRepository 所需的參數
type RoomRepositoryParam struct {
2026-01-07 02:47:37 +00:00
DB *cassandra.DB
2026-01-06 07:15:18 +00:00
Keyspace string
}
// MustRoomRepository 創建 RoomRepository如果失敗會 panic
func MustRoomRepository(param RoomRepositoryParam) repository.RoomRepository {
repo, err := NewRoomRepository(param.DB, param.Keyspace)
if err != nil {
panic(fmt.Sprintf("failed to create room repository: %v", err))
}
return repo
}
// NewRoomRepository 創建新的聊天室 Repository
func NewRoomRepository(db *cassandra.DB, keyspace string) (repository.RoomRepository, error) {
roomRepo, err := cassandra.NewRepository[entity.Room](db, keyspace)
if err != nil {
return nil, err
}
memberRepo, err := cassandra.NewRepository[entity.RoomMember](db, keyspace)
if err != nil {
return nil, err
}
userRoomRepo, err := cassandra.NewRepository[entity.UserRoom](db, keyspace)
if err != nil {
return nil, err
}
return &roomRepository{
roomRepo: roomRepo,
memberRepo: memberRepo,
userRoomRepo: userRoomRepo,
db: db,
keyspace: keyspace,
}, nil
}
// ==================== Room Interface 實作 ====================
func (r *roomRepository) Create(ctx context.Context, room *entity.Room) error {
now := time.Now().UTC().UnixNano()
if room.CreatedAt == 0 {
room.CreatedAt = now
}
if room.UpdatedAt == 0 {
room.UpdatedAt = now
}
room.RoomID = gocql.TimeUUID()
return r.roomRepo.Insert(ctx, *room)
}
func (r *roomRepository) RoomGet(ctx context.Context, roomID string) (*entity.Room, error) {
uuid, err := gocql.ParseUUID(roomID)
if err != nil {
return nil, err
}
room, err := r.roomRepo.Get(ctx, entity.Room{
RoomID: uuid,
})
if err != nil {
if cassandra.IsNotFound(err) {
return nil, cassandra.ErrNotFound
}
return nil, err
}
return &room, nil
}
func (r *roomRepository) RoomUpdate(ctx context.Context, room *entity.Room) error {
update := entity.Room{}
now := time.Now().UTC().UnixNano()
get, err := r.RoomGet(ctx, room.RoomID.String())
if err != nil {
return err
}
update.CreatedAt = get.CreatedAt
update.UpdatedAt = now
update.Status = room.Status
update.Name = room.Name
update.RoomID = room.RoomID
if err = r.roomRepo.Update(ctx, update); err != nil {
return err
}
return nil
}
func (r *roomRepository) RoomDelete(ctx context.Context, roomID string) error {
uuid, err := gocql.ParseUUID(roomID)
if err != nil {
return err
}
// 使用原生 CQL 語句來刪除,避免 cassandra library Delete 方法的 struct 綁定問題
e := entity.Room{}
stmt := fmt.Sprintf("DELETE FROM %s.%s WHERE room_id = ?", r.keyspace, e.TableName())
return r.db.GetSession().Query(stmt, nil).
Bind(uuid).
WithContext(ctx).
ExecRelease()
}
func (r *roomRepository) RoomList(ctx context.Context, param repository.ListRoomsReq) ([]entity.Room, error) {
query := r.roomRepo.Query()
if param.Status != "" {
query = query.Where(cassandra.Eq("status", param.Status)).AllowFiltering()
}
if param.PageSize > 0 {
query = query.Limit(param.PageSize)
}
if param.LastID != "" {
lastUUID, err := gocql.ParseUUID(param.LastID)
if err != nil {
return nil, err
}
query = query.Where(cassandra.Lt("room_id", lastUUID))
}
var rooms []entity.Room
if err := query.Scan(ctx, &rooms); err != nil {
return nil, err
}
return rooms, nil
}
func (r *roomRepository) RoomCount(ctx context.Context, param repository.CountRoomsReq) (int64, error) {
query := r.roomRepo.Query()
if param.Status != "" {
query = query.Where(cassandra.Eq("status", param.Status)).AllowFiltering()
}
return query.Count(ctx)
}
func (r *roomRepository) RoomExists(ctx context.Context, roomID string) (bool, error) {
uuid, err := gocql.ParseUUID(roomID)
if err != nil {
return false, err
}
_, err = r.roomRepo.Get(ctx, entity.Room{
RoomID: uuid,
})
if err != nil {
if cassandra.IsNotFound(err) {
return false, nil
}
return false, err
}
return true, nil
}
func (r *roomRepository) RoomGetByID(ctx context.Context, roomIDs []string) ([]entity.Room, error) {
if len(roomIDs) == 0 {
return []entity.Room{}, nil
}
// 將字串 ID 轉換為 UUID
uuids := make([]any, 0, len(roomIDs))
for _, id := range roomIDs {
uuid, err := gocql.ParseUUID(id)
if err != nil {
return nil, err
}
uuids = append(uuids, uuid)
}
var rooms []entity.Room
err := r.roomRepo.Query().
Where(cassandra.In("room_id", uuids)).
Scan(ctx, &rooms)
if err != nil {
return nil, err
}
return rooms, nil
}
// ==================== Member Interface 實作 ====================
func (r *roomRepository) Insert(ctx context.Context, member *entity.RoomMember) error {
now := time.Now().UTC().UnixNano()
if member.JoinedAt == 0 {
member.JoinedAt = now
}
if member.Role == "" {
member.Role = chat.RoomRoleMember.String()
}
// 同時插入到 user_room 表(反向查詢表)
userRoom := entity.UserRoom{
UID: member.UID,
RoomID: member.RoomID,
JoinedAt: member.JoinedAt,
}
if err := r.memberRepo.Insert(ctx, *member); err != nil {
return err
}
if err := r.userRoomRepo.Insert(ctx, userRoom); err != nil {
return err
}
return nil
}
func (r *roomRepository) Get(ctx context.Context, roomID, uid string) (*entity.RoomMember, error) {
uuid, err := gocql.ParseUUID(roomID)
if err != nil {
return nil, err
}
member, err := r.memberRepo.Get(ctx, entity.RoomMember{
RoomID: uuid,
UID: uid,
})
if err != nil {
if cassandra.IsNotFound(err) {
return nil, cassandra.ErrNotFound
}
return nil, err
}
return &member, nil
}
func (r *roomRepository) AllMembers(ctx context.Context, roomID string) ([]entity.RoomMember, error) {
uuid, err := gocql.ParseUUID(roomID)
if err != nil {
return nil, err
}
var members []entity.RoomMember
err = r.memberRepo.Query().
Where(cassandra.Eq("room_id", uuid)).
Scan(ctx, &members)
if err != nil {
return nil, err
}
return members, nil
}
func (r *roomRepository) UpdateRole(ctx context.Context, member *entity.RoomMember) error {
get, err := r.Get(ctx, member.RoomID.String(), member.UID)
if err != nil {
return err
}
update := entity.RoomMember{
RoomID: member.RoomID,
UID: member.UID,
Role: member.Role,
JoinedAt: get.JoinedAt,
}
return r.memberRepo.Update(ctx, update)
}
func (r *roomRepository) DeleteMember(ctx context.Context, roomID, uid string) error {
uuid, err := gocql.ParseUUID(roomID)
if err != nil {
return err
}
// 同時從兩個表中刪除
if err := r.memberRepo.Delete(ctx, entity.RoomMember{
RoomID: uuid,
UID: uid,
}); err != nil {
return err
}
if err := r.userRoomRepo.Delete(ctx, entity.UserRoom{
UID: uid,
RoomID: uuid,
}); err != nil {
return err
}
return nil
}
func (r *roomRepository) DeleteRoom(ctx context.Context, roomID string) error {
uuid, err := gocql.ParseUUID(roomID)
if err != nil {
return err
}
// 先查詢所有成員(必須在刪除之前查詢)
members, err := r.AllMembers(ctx, roomID)
if err != nil {
return err
}
// 刪除 user_room 表中的所有關聯
for _, member := range members {
if err := r.userRoomRepo.Delete(ctx, entity.UserRoom{
UID: member.UID,
RoomID: uuid,
}); err != nil {
return err
}
}
// 最後刪除 room_member 表中的所有成員
e := entity.RoomMember{}
stmt := fmt.Sprintf("DELETE FROM %s.%s WHERE room_id = ?", r.keyspace, e.TableName())
if err := r.db.GetSession().Query(stmt, nil).
Bind(uuid).
WithContext(ctx).
WithTimestamp(time.Now().UnixNano() / 1e3).
ExecRelease(); err != nil {
return err
}
return nil
}
func (r *roomRepository) Count(ctx context.Context, roomID string) (int64, error) {
uuid, err := gocql.ParseUUID(roomID)
if err != nil {
return 0, err
}
return r.memberRepo.Query().
Where(cassandra.Eq("room_id", uuid)).
Count(ctx)
}
// ==================== User Interface 實作 ====================
func (r *roomRepository) GetUserRooms(ctx context.Context, uid string) ([]entity.UserRoom, error) {
var userRooms []entity.UserRoom
err := r.userRoomRepo.Query().
Where(cassandra.Eq("uid", uid)).
Scan(ctx, &userRooms)
if err != nil {
return nil, err
}
return userRooms, nil
}
func (r *roomRepository) CountUserRooms(ctx context.Context, uid string) (int64, error) {
return r.userRoomRepo.Query().
Where(cassandra.Eq("uid", uid)).
Count(ctx)
}
func (r *roomRepository) IsUserInRoom(ctx context.Context, uid, roomID string) (bool, error) {
uuid, err := gocql.ParseUUID(roomID)
if err != nil {
return false, err
}
_, err = r.userRoomRepo.Get(ctx, entity.UserRoom{
UID: uid,
RoomID: uuid,
})
if err != nil {
if cassandra.IsNotFound(err) {
return false, nil
}
return false, err
}
return true, nil
}