backend/pkg/chat/repository/room.go

405 lines
9.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 {
DB *cassandra.DB
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
}