405 lines
9.0 KiB
Go
405 lines
9.0 KiB
Go
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
|
||
}
|