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
|
|||
|
|
}
|