ampelmaennchen.git

commit efe1b69f2c6a1bca388e37716a1e5931bbfcc101

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