Author: Adam Evyčędo <git@apiote.xyz>
save state of room creation and continue from last error
db/migrations/20240527_matrix_rooms.sql | 1 db/registration.go | 18 -- db/rooms.go | 23 ++++ go.mod | 1 go.sum | 2 matrix/rooms.go | 155 +++++++++++++++++++++-----
diff --git a/db/migrations/20240527_matrix_rooms.sql b/db/migrations/20240527_matrix_rooms.sql new file mode 100644 index 0000000000000000000000000000000000000000..7d322ed27d0a4024241bdc2a126e7115a169d1f0 --- /dev/null +++ b/db/migrations/20240527_matrix_rooms.sql @@ -0,0 +1 @@ +create table matrix_rooms(kind text primary key, room_id text, avatar_path text, parent_id text, avatar_uri text) diff --git a/db/registration.go b/db/registration.go index 08aa458e7196270304b23466344d61a260c56201..53609cc3de66f4673d8a2f2c4a5240f26e37c548 100644 --- a/db/registration.go +++ b/db/registration.go @@ -3,9 +3,6 @@ import ( "database/sql" "errors" - "fmt" - - _ "github.com/jackc/pgx/v5/stdlib" ) func GetRegistrationTokens() (as string, hs string, err error) { @@ -15,23 +12,16 @@ if err != nil { if errors.Is(err, sql.ErrNoRows) { return "", "", nil } - return "", "", fmt.Errorf("while selecting registration: %w", err) + return "", "", err } - return as, hs, nil + return } func SaveRegistrationTokens(as, hs string) error { _, err := db.Exec("insert into matrix_registration values($1, $2)", as, hs) - if err != nil { - return fmt.Errorf("while inserting registration: %w", err) - } - - return nil + return err } func DeleteRegistrationTokens() error { _, err := db.Exec("delete from matrix_registration") - if err != nil { - return fmt.Errorf("while deleting previous registration: %w", err) - } - return nil + return err } diff --git a/db/rooms.go b/db/rooms.go new file mode 100644 index 0000000000000000000000000000000000000000..c365b1535dee09a2d997af64a7960d60accf1be2 --- /dev/null +++ b/db/rooms.go @@ -0,0 +1,23 @@ +package db + +import ( + "database/sql" + "errors" +) + +func SaveRoom(kind, id, avatarPath, parent, avatarUri string) error { + _, err := db.Exec("insert into matrix_rooms values($1, $2, $3, $4, $5) on conflict do update set room_id = $2, avatar_path = $3, parent_id = $4, avatar_uri = $5", kind, id, avatarPath, parent, avatarUri) + return err +} + +func GetRoom(kind string) (id string, path string, parent string, uri string, err error) { + row := db.QueryRow("select room_id, avatar_path, parent_id, avatar_uri from matrix_rooms where kind = '$1'", kind) + err = row.Scan(&id, &path, &parent, &uri) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return "", "", "", "", nil + } + return "", "", "", "", err + } + return +} diff --git a/go.mod b/go.mod index 69473d0640524504bc808140319eb56ef1758aa5..79e6204e0ad69189b8e12b9764846e46ae990ed5 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ go 1.22 require ( + apiote.xyz/p/gott/v2 v2.0.3 git.sr.ht/~sircmpwn/getopt v1.0.0 github.com/BurntSushi/toml v1.3.2 github.com/gabriel-vasile/mimetype v1.4.4 diff --git a/go.sum b/go.sum index 64db3ad5de1de00d31412d55ee3b9900f1f497c0..be8b7fbb61fc422116ca7acc6ddbd29c985128ab 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +apiote.xyz/p/gott/v2 v2.0.3 h1:CUFo0OAau20eRCPq/D11F9hQjKMB2cwLz6dZupgs7ME= +apiote.xyz/p/gott/v2 v2.0.3/go.mod h1:H87aFMqvof1DWBzJuxzLaQRby4+PrIvFRwMfTTB6lK8= git.sr.ht/~sircmpwn/getopt v1.0.0 h1:/pRHjO6/OCbBF4puqD98n6xtPEgE//oq5U8NXjP7ROc= git.sr.ht/~sircmpwn/getopt v1.0.0/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= diff --git a/matrix/rooms.go b/matrix/rooms.go index 081121fa9acbd7a7b98e118a24fbaa38b1e99211..82af371280cf2e9503372cb14702ccb2dd7b546b 100644 --- a/matrix/rooms.go +++ b/matrix/rooms.go @@ -1,29 +1,47 @@ package matrix import ( - "errors" - "fmt" - "apiote.xyz/p/ampelmaennchen/config" + "apiote.xyz/p/ampelmaennchen/db" "context" + "errors" + "apiote.xyz/p/gott/v2" "maunium.net/go/mautrix" "maunium.net/go/mautrix/appservice" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" ) -func createSpace(intent *appservice.IntentAPI, cfg config.MatrixConfig) (id.RoomID, error) { +type createRoomResult struct { + Intent *appservice.IntentAPI + CreateFunction func(*appservice.IntentAPI, string, string, id.RoomID) (id.RoomID, error) + Kind string + Name string + Alias string + Parent id.RoomID + AvatarPath string + + RoomID id.RoomID + AvatarPathInDB string + AvatarPathSet string + ParentInDB string + ParentSet string + AvatarUri id.ContentURI + AvatarUriInDB string +} + +func createSpace(intent *appservice.IntentAPI, name, alias string, spaceID id.RoomID) (id.RoomID, error) { zero := 0 request := &mautrix.ReqCreateRoom{ - Name: cfg.Rooms.SpaceName, + Name: name, Preset: "public_chat", PowerLevelOverride: &event.PowerLevelsEventContent{ EventsDefault: 100, InvitePtr: &zero, }, - RoomAliasName: cfg.Rooms.SpaceAlias, + RoomAliasName: alias, Visibility: "private", CreationContent: map[string]interface{}{ "type": "m.space", @@ -55,7 +73,7 @@ } return response.RoomID, err } -func createRoom(intent *appservice.IntentAPI, name, alias string, spaceID id.RoomID) (id.RoomID, error) { +func createPublicRoom(intent *appservice.IntentAPI, name, alias string, spaceID id.RoomID) (id.RoomID, error) { spaceIDString := spaceID.String() request := &mautrix.ReqCreateRoom{ Name: name, @@ -98,49 +116,118 @@ } return response.RoomID, err } -func setAvatar(intent *appservice.IntentAPI, roomID id.RoomID, avatarUri id.ContentURI) error { - _, err := intent.SendStateEvent(context.Background(), roomID, event.StateRoomAvatar, "", &event.RoomAvatarEventContent{ - URL: avatarUri, - }) - return err +func saveRoom(r createRoomResult) error { + return db.SaveRoom(r.Kind, r.RoomID.String(), r.AvatarPathSet, r.ParentSet, r.AvatarUri.String()) } -func CreateSpace(as *appservice.AppService, cfg config.MatrixConfig) (id.RoomID, error) { - botuser := id.NewUserID("ampelmaennchen", cfg.Homeserver.Domain) - intent := as.Intent(botuser) - roomID, err := createSpace(intent, cfg) +func getRoom(r createRoomResult) (createRoomResult, error) { + roomID, avatarPathInDB, parentInDB, avatarUriInDB, err := db.GetRoom(r.Kind) if err != nil { - return id.RoomID(""), fmt.Errorf("while creating space: %w", err) + return r, err } - if roomID.String() != "" { - avatarUri, err := uploadImage(intent, cfg.Rooms.SpaceAvatarPath) + r.RoomID = id.RoomID(roomID) + r.AvatarPathInDB = avatarPathInDB + r.ParentInDB = parentInDB + r.AvatarUriInDB = avatarUriInDB + return r, nil +} + +func createRoom(r createRoomResult) (createRoomResult, error) { + if r.RoomID.String() == "" { + roomID, err := createPublicRoom(r.Intent, r.Name, r.Alias, r.Parent) if err != nil { - return id.RoomID(""), fmt.Errorf("while uploading avatar: %w", err) + return r, err } - setAvatar(intent, roomID, avatarUri) + r.RoomID = roomID + return r, nil } - return roomID, nil + return r, nil } -func CreatePublicRoom(as *appservice.AppService, cfg config.MatrixConfig, spaceID id.RoomID) error { - botuser := id.NewUserID("ampelmaennchen", cfg.Homeserver.Domain) - intent := as.Intent(botuser) - roomID, err := createRoom(intent, cfg.Rooms.PublicRoomName, cfg.Rooms.PublicRoomAlias, spaceID) - if err != nil { - return fmt.Errorf("while creating room: %w", err) - } - if roomID.String() != "" { - avatarUri, err := uploadImage(intent, cfg.Rooms.PublicRoomAvatarPath) +func uploadRoomAvatar(r createRoomResult) (createRoomResult, error) { + if r.AvatarPathInDB != r.AvatarPath { + uri, err := uploadImage(r.Intent, r.AvatarPath) if err != nil { - return fmt.Errorf("while uploading avatar: %w", err) + return r, err } + r.AvatarUri = uri + } + return r, nil +} - setAvatar(intent, roomID, avatarUri) +func setRoomAvatar(r createRoomResult) error { + if !r.AvatarUri.IsEmpty() && r.AvatarUriInDB != r.AvatarUri.String() { + _, err := r.Intent.SendStateEvent(context.Background(), r.RoomID, event.StateRoomAvatar, "", &event.RoomAvatarEventContent{ + URL: r.AvatarUri, + }) + r.AvatarPathSet = r.AvatarPath + return err + } + return nil +} - intent.SendStateEvent(context.Background(), spaceID, event.StateSpaceChild, roomID.String(), &event.SpaceChildEventContent{ +func setRoomParent(r createRoomResult) error { + if r.ParentInDB != r.Parent.String() { + _, err := r.Intent.SendStateEvent(context.Background(), r.Parent, event.StateSpaceChild, r.RoomID.String(), &event.SpaceChildEventContent{ Via: []string{"apiote.xyz"}, Suggested: true, }) + r.ParentSet = r.Parent.String() + return err } return nil } + +func CreatePublicRoom(as *appservice.AppService, cfg config.MatrixConfig, spaceID id.RoomID) error { + botuser := id.NewUserID("ampelmaennchen", cfg.Homeserver.Domain) + intent := as.Intent(botuser) + + r := gott.R[createRoomResult]{ + S: createRoomResult{ + Intent: intent, + CreateFunction: createPublicRoom, + Kind: "public", + Name: cfg.Rooms.PublicRoomName, + Alias: cfg.Rooms.PublicRoomAlias, + AvatarPath: cfg.Rooms.PublicRoomAvatarPath, + Parent: spaceID, + }, + }. + Bind(getRoom). + Bind(createRoom). + Tee(saveRoom). + Bind(uploadRoomAvatar). + Tee(setRoomAvatar). + Tee(saveRoom). + Tee(setRoomParent). + Tee(saveRoom) + + return r.E +} + +func CreateSpace(as *appservice.AppService, cfg config.MatrixConfig) (id.RoomID, error) { + botuser := id.NewUserID("ampelmaennchen", cfg.Homeserver.Domain) + intent := as.Intent(botuser) + + r := gott.R[createRoomResult]{ + S: createRoomResult{ + Intent: intent, + CreateFunction: createSpace, + Kind: "public", + Name: cfg.Rooms.SpaceName, + Alias: cfg.Rooms.SpaceAlias, + AvatarPath: cfg.Rooms.SpaceAvatarPath, + Parent: "", + }, + }. + Bind(getRoom). + Bind(createRoom). + Tee(saveRoom). + Bind(uploadRoomAvatar). + Tee(setRoomAvatar). + Tee(saveRoom). + Tee(setRoomParent). + Tee(saveRoom) + + return r.S.RoomID, r.E +}