diff --git a/docs/data-sources/notifications.md b/docs/data-sources/notifications.md new file mode 100644 index 0000000..40d2dc6 --- /dev/null +++ b/docs/data-sources/notifications.md @@ -0,0 +1,46 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "immich_notifications Data Source - immich" +subcategory: "" +description: |- + Retrieves a list of notifications for the current user. +--- + +# immich_notifications (Data Source) + +Retrieves a list of notifications for the current user. + +## Example Usage + +```terraform +data "immich_notifications" "my_unread" { + unread_only = true +} + +output "unread_notifications" { + value = data.immich_notifications.my_unread.notifications +} +``` + + +## Schema + +### Optional + +- `unread_only` (Boolean) Filter for only unread notifications. + +### Read-Only + +- `notifications` (Attributes List) List of notifications. (see [below for nested schema](#nestedatt--notifications)) + + +### Nested Schema for `notifications` + +Read-Only: + +- `created_at` (String) Timestamp when the notification was created. +- `description` (String) Notification message body. +- `id` (String) Unique identifier for the notification. +- `level` (String) Severity level. +- `title` (String) Notification title. +- `type` (String) Type of notification. diff --git a/docs/resources/admin_notification.md b/docs/resources/admin_notification.md new file mode 100644 index 0000000..bec12f7 --- /dev/null +++ b/docs/resources/admin_notification.md @@ -0,0 +1,36 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "immich_admin_notification Resource - immich" +subcategory: "" +description: |- + Sends a system-wide notification as an administrator. Note: This resource triggers a notification upon creation. Deleting the resource will attempt to remove the notification. +--- + +# immich_admin_notification (Resource) + +Sends a system-wide notification as an administrator. Note: This resource triggers a notification upon creation. Deleting the resource will attempt to remove the notification. + +## Example Usage + +```terraform +resource "immich_admin_notification" "announcement" { + type = "SYSTEM" + level = "INFO" + title = "Maintenance Scheduled" + description = "Immich will be down for maintenance on Sunday at 2 AM UTC." +} +``` + + +## Schema + +### Required + +- `description` (String) Notification message body. +- `level` (String) Severity level (`INFO`, `WARNING`, `ERROR`). +- `title` (String) Notification title. +- `type` (String) Type of notification (e.g. `SYSTEM`). + +### Read-Only + +- `id` (String) Unique identifier for the created notification. diff --git a/docs/resources/system_config.md b/docs/resources/system_config.md index 2418451..3eef95a 100644 --- a/docs/resources/system_config.md +++ b/docs/resources/system_config.md @@ -37,6 +37,24 @@ resource "immich_system_config" "example" { url = "http://immich-machine-learning:3003" clip_model = "ViT-L-14__openai" } + + notifications = { + smtp = { + enabled = true + host = "smtp.example.com" + port = 587 + username = "user@example.com" + password = "secure-smtp-password" + from = "immich@example.com" + secure = true + } + } + + templates = { + email = { + welcome_template = "Welcome to Immich, {{name}}!" + } + } } ``` @@ -46,9 +64,11 @@ resource "immich_system_config" "example" { ### Optional - `machine_learning` (Attributes) (see [below for nested schema](#nestedatt--machine_learning)) +- `notifications` (Attributes) (see [below for nested schema](#nestedatt--notifications)) - `oauth` (Attributes) (see [below for nested schema](#nestedatt--oauth)) - `password_login` (Attributes) (see [below for nested schema](#nestedatt--password_login)) - `storage_template` (Attributes) (see [below for nested schema](#nestedatt--storage_template)) +- `templates` (Attributes) (see [below for nested schema](#nestedatt--templates)) ### Nested Schema for `machine_learning` @@ -61,6 +81,30 @@ Optional: - `url` (String) URL of the machine learning server. + +### Nested Schema for `notifications` + +Optional: + +- `smtp` (Attributes) (see [below for nested schema](#nestedatt--notifications--smtp)) + + +### Nested Schema for `notifications.smtp` + +Optional: + +- `enabled` (Boolean) Enable SMTP email notifications. +- `from` (String) Sender email address. +- `host` (String) SMTP server hostname. +- `ignore_cert` (Boolean) Whether to ignore certificate validation errors. +- `password` (String, Sensitive) SMTP authentication password. +- `port` (Number) SMTP server port. +- `reply_to` (String) Reply-to email address. +- `secure` (Boolean) Whether to use TLS/SSL. +- `username` (String) SMTP authentication username. + + + ### Nested Schema for `oauth` @@ -94,3 +138,20 @@ Optional: Optional: - `template` (String) Storage template (e.g. `{{y}}/{{y}}-{{m}}-{{d}}/{{filename}}`). + + + +### Nested Schema for `templates` + +Optional: + +- `email` (Attributes) (see [below for nested schema](#nestedatt--templates--email)) + + +### Nested Schema for `templates.email` + +Optional: + +- `album_invite_template` (String) Email template for album invitations. +- `album_update_template` (String) Email template for album updates. +- `welcome_template` (String) Email template for welcome emails. diff --git a/examples/data-sources/immich_notifications/data-source.tf b/examples/data-sources/immich_notifications/data-source.tf new file mode 100644 index 0000000..84c397b --- /dev/null +++ b/examples/data-sources/immich_notifications/data-source.tf @@ -0,0 +1,7 @@ +data "immich_notifications" "my_unread" { + unread_only = true +} + +output "unread_notifications" { + value = data.immich_notifications.my_unread.notifications +} diff --git a/examples/resources/immich_admin_notification/resource.tf b/examples/resources/immich_admin_notification/resource.tf new file mode 100644 index 0000000..7c56cd0 --- /dev/null +++ b/examples/resources/immich_admin_notification/resource.tf @@ -0,0 +1,6 @@ +resource "immich_admin_notification" "announcement" { + type = "SYSTEM" + level = "INFO" + title = "Maintenance Scheduled" + description = "Immich will be down for maintenance on Sunday at 2 AM UTC." +} diff --git a/examples/resources/immich_system_config/resource.tf b/examples/resources/immich_system_config/resource.tf index 88f0c99..a3e021e 100644 --- a/examples/resources/immich_system_config/resource.tf +++ b/examples/resources/immich_system_config/resource.tf @@ -22,4 +22,22 @@ resource "immich_system_config" "example" { url = "http://immich-machine-learning:3003" clip_model = "ViT-L-14__openai" } + + notifications = { + smtp = { + enabled = true + host = "smtp.example.com" + port = 587 + username = "user@example.com" + password = "secure-smtp-password" + from = "immich@example.com" + secure = true + } + } + + templates = { + email = { + welcome_template = "Welcome to Immich, {{name}}!" + } + } } diff --git a/internal/client/notifications.go b/internal/client/notifications.go new file mode 100644 index 0000000..100a579 --- /dev/null +++ b/internal/client/notifications.go @@ -0,0 +1,83 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +type Notification struct { + ID string `json:"id"` + Type string `json:"type"` + Level string `json:"level"` + Title string `json:"title"` + Description string `json:"description"` + CreatedAt string `json:"createdAt"` + ReadAt *string `json:"readAt"` + Data map[string]interface{} `json:"data"` +} + +type CreateAdminNotificationRequest struct { + Type string `json:"type"` // SYSTEM + Level string `json:"level"` // INFO, WARNING, ERROR + Title string `json:"title"` + Description string `json:"description"` + Data map[string]interface{} `json:"data,omitempty"` +} + +func (c *Client) GetNotifications(unread bool) ([]Notification, error) { + url := fmt.Sprintf("%s/notifications?unread=%v", c.HostURL, unread) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + body, err := c.doRequest(req) + if err != nil { + return nil, err + } + + var notifications []Notification + err = json.Unmarshal(body, ¬ifications) + if err != nil { + return nil, err + } + + return notifications, nil +} + +func (c *Client) CreateAdminNotification(notification CreateAdminNotificationRequest) (*Notification, error) { + rb, err := json.Marshal(notification) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", fmt.Sprintf("%s/admin/notifications", c.HostURL), bytes.NewBuffer(rb)) + if err != nil { + return nil, err + } + + body, err := c.doRequest(req) + if err != nil { + return nil, err + } + + var newNotification Notification + err = json.Unmarshal(body, &newNotification) + if err != nil { + return nil, err + } + + return &newNotification, nil +} + +func (c *Client) DeleteNotification(id string) error { + req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/notifications/%s", c.HostURL, id), nil) + if err != nil { + return err + } + + _, err = c.doRequest(req) + return err +} diff --git a/internal/provider/admin_notification_resource.go b/internal/provider/admin_notification_resource.go new file mode 100644 index 0000000..57f2fbc --- /dev/null +++ b/internal/provider/admin_notification_resource.go @@ -0,0 +1,160 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tfmm/terraform-provider-immich/internal/client" +) + +// Ensure the implementation satisfies the expected interfaces. +var _ resource.Resource = &adminNotificationResource{} +var _ resource.ResourceWithImportState = &adminNotificationResource{} + +func NewAdminNotificationResource() resource.Resource { + return &adminNotificationResource{} +} + +// adminNotificationResource defines the resource implementation. +type adminNotificationResource struct { + client *client.Client +} + +// adminNotificationResourceModel describes the resource data model. +type adminNotificationResourceModel struct { + ID types.String `tfsdk:"id"` + Type types.String `tfsdk:"type"` + Level types.String `tfsdk:"level"` + Title types.String `tfsdk:"title"` + Description types.String `tfsdk:"description"` +} + +func (r *adminNotificationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_admin_notification" +} + +func (r *adminNotificationResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Sends a system-wide notification as an administrator. Note: This resource triggers a notification upon creation. Deleting the resource will attempt to remove the notification.", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Unique identifier for the created notification.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "type": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Type of notification (e.g. `SYSTEM`).", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "level": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Severity level (`INFO`, `WARNING`, `ERROR`).", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "title": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Notification title.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "description": schema.StringAttribute{ + Required: true, + MarkdownDescription: "Notification message body.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +func (r *adminNotificationResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *adminNotificationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data adminNotificationResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + createReq := client.CreateAdminNotificationRequest{ + Type: data.Type.ValueString(), + Level: data.Level.ValueString(), + Title: data.Title.ValueString(), + Description: data.Description.ValueString(), + } + + notification, err := r.client.CreateAdminNotification(createReq) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to send admin notification, got error: %s", err)) + return + } + + data.ID = types.StringValue(notification.ID) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *adminNotificationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Notifications are usually transient and might not be easily refreshable by ID alone + // if they disappear from the system. + // We'll keep what's in state unless we can find it. +} + +func (r *adminNotificationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Updating a sent notification is usually not supported. +} + +func (r *adminNotificationResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data adminNotificationResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + err := r.client.DeleteNotification(data.ID.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete notification, got error: %s", err)) + return + } +} + +func (r *adminNotificationResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} diff --git a/internal/provider/notifications_data_source.go b/internal/provider/notifications_data_source.go new file mode 100644 index 0000000..91dec5b --- /dev/null +++ b/internal/provider/notifications_data_source.go @@ -0,0 +1,142 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/tfmm/terraform-provider-immich/internal/client" +) + +// Ensure the implementation satisfies the expected interfaces. +var _ datasource.DataSource = ¬ificationsDataSource{} + +func NewNotificationsDataSource() datasource.DataSource { + return ¬ificationsDataSource{} +} + +// notificationsDataSource defines the data source implementation. +type notificationsDataSource struct { + client *client.Client +} + +// notificationsDataSourceModel describes the data source data model. +type notificationsDataSourceModel struct { + UnreadOnly types.Bool `tfsdk:"unread_only"` + Notifications []notificationsModel_ds `tfsdk:"notifications"` +} + +type notificationsModel_ds struct { + ID types.String `tfsdk:"id"` + Type types.String `tfsdk:"type"` + Level types.String `tfsdk:"level"` + Title types.String `tfsdk:"title"` + Description types.String `tfsdk:"description"` + CreatedAt types.String `tfsdk:"created_at"` +} + +func (d *notificationsDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_notifications" +} + +func (d *notificationsDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Retrieves a list of notifications for the current user.", + + Attributes: map[string]schema.Attribute{ + "unread_only": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Filter for only unread notifications.", + }, + "notifications": schema.ListNestedAttribute{ + Computed: true, + MarkdownDescription: "List of notifications.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Unique identifier for the notification.", + }, + "type": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Type of notification.", + }, + "level": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Severity level.", + }, + "title": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Notification title.", + }, + "description": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Notification message body.", + }, + "created_at": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "Timestamp when the notification was created.", + }, + }, + }, + }, + }, + } +} + +func (d *notificationsDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *notificationsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data notificationsDataSourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + unread := false + if !data.UnreadOnly.IsNull() { + unread = data.UnreadOnly.ValueBool() + } + + notifications, err := d.client.GetNotifications(unread) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read notifications, got error: %s", err)) + return + } + + data.Notifications = []notificationsModel_ds{} + for _, n := range notifications { + nState := notificationsModel_ds{ + ID: types.StringValue(n.ID), + Type: types.StringValue(n.Type), + Level: types.StringValue(n.Level), + Title: types.StringValue(n.Title), + Description: types.StringValue(n.Description), + CreatedAt: types.StringValue(n.CreatedAt), + } + data.Notifications = append(data.Notifications, nState) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 0b1a1d3..2ee2611 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -99,6 +99,7 @@ func (p *immichProvider) Resources(ctx context.Context) []func() resource.Resour NewStackResource, NewTagResource, NewWorkflowResource, + NewAdminNotificationResource, } } @@ -109,6 +110,7 @@ func (p *immichProvider) DataSources(ctx context.Context) []func() datasource.Da NewLibrariesDataSource, NewActivitiesDataSource, NewServerDataSource, + NewNotificationsDataSource, } } diff --git a/internal/provider/system_config_resource.go b/internal/provider/system_config_resource.go index c7fa463..4c1897c 100644 --- a/internal/provider/system_config_resource.go +++ b/internal/provider/system_config_resource.go @@ -32,6 +32,8 @@ type systemConfigResourceModel struct { OAuth *oauthModel `tfsdk:"oauth"` StorageTemplate *storageTemplateModel `tfsdk:"storage_template"` MachineLearning *machineLearningModel `tfsdk:"machine_learning"` + Notifications *notificationsModel `tfsdk:"notifications"` + Templates *templatesModel `tfsdk:"templates"` } type passwordLoginModel struct { @@ -45,6 +47,32 @@ type machineLearningModel struct { FacialRecognitionModel types.String `tfsdk:"facial_recognition_model"` } +type notificationsModel struct { + SMTP *smtpModel `tfsdk:"smtp"` +} + +type smtpModel struct { + Enabled types.Bool `tfsdk:"enabled"` + Host types.String `tfsdk:"host"` + Port types.Int64 `tfsdk:"port"` + Username types.String `tfsdk:"username"` + Password types.String `tfsdk:"password"` + From types.String `tfsdk:"from"` + ReplyTo types.String `tfsdk:"reply_to"` + Secure types.Bool `tfsdk:"secure"` + IgnoreCert types.Bool `tfsdk:"ignore_cert"` +} + +type templatesModel struct { + Email *emailTemplatesModel `tfsdk:"email"` +} + +type emailTemplatesModel struct { + AlbumInviteTemplate types.String `tfsdk:"album_invite_template"` + AlbumUpdateTemplate types.String `tfsdk:"album_update_template"` + WelcomeTemplate types.String `tfsdk:"welcome_template"` +} + type oauthModel struct { Enabled types.Bool `tfsdk:"enabled"` IssuerUrl types.String `tfsdk:"issuer_url"` @@ -172,6 +200,78 @@ func (r *systemConfigResource) Schema(ctx context.Context, req resource.SchemaRe }, }, }, + "notifications": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "smtp": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "enabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Enable SMTP email notifications.", + }, + "host": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "SMTP server hostname.", + }, + "port": schema.Int64Attribute{ + Optional: true, + MarkdownDescription: "SMTP server port.", + }, + "username": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "SMTP authentication username.", + }, + "password": schema.StringAttribute{ + Optional: true, + Sensitive: true, + MarkdownDescription: "SMTP authentication password.", + }, + "from": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Sender email address.", + }, + "reply_to": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Reply-to email address.", + }, + "secure": schema.BoolAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Whether to use TLS/SSL.", + }, + "ignore_cert": schema.BoolAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "Whether to ignore certificate validation errors.", + }, + }, + }, + }, + }, + "templates": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "email": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "album_invite_template": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Email template for album invitations.", + }, + "album_update_template": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Email template for album updates.", + }, + "welcome_template": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "Email template for welcome emails.", + }, + }, + }, + }, + }, }, } } @@ -317,6 +417,42 @@ func (r *systemConfigResource) mapModelToClient(model systemConfigResourceModel, config.MachineLearning["facialRecognitionModel"] = model.MachineLearning.FacialRecognitionModel.ValueString() } + if model.Notifications != nil { + if config.Notifications == nil { + config.Notifications = make(map[string]interface{}) + } + if model.Notifications.SMTP != nil { + smtp := make(map[string]interface{}) + smtp["enabled"] = model.Notifications.SMTP.Enabled.ValueBool() + smtp["from"] = model.Notifications.SMTP.From.ValueString() + smtp["replyTo"] = model.Notifications.SMTP.ReplyTo.ValueString() + + transport := make(map[string]interface{}) + transport["host"] = model.Notifications.SMTP.Host.ValueString() + transport["port"] = model.Notifications.SMTP.Port.ValueInt64() + transport["username"] = model.Notifications.SMTP.Username.ValueString() + transport["password"] = model.Notifications.SMTP.Password.ValueString() + transport["secure"] = model.Notifications.SMTP.Secure.ValueBool() + transport["ignoreCert"] = model.Notifications.SMTP.IgnoreCert.ValueBool() + + smtp["transport"] = transport + config.Notifications["smtp"] = smtp + } + } + + if model.Templates != nil { + if config.Templates == nil { + config.Templates = make(map[string]interface{}) + } + if model.Templates.Email != nil { + email := make(map[string]interface{}) + email["albumInviteTemplate"] = model.Templates.Email.AlbumInviteTemplate.ValueString() + email["albumUpdateTemplate"] = model.Templates.Email.AlbumUpdateTemplate.ValueString() + email["welcomeTemplate"] = model.Templates.Email.WelcomeTemplate.ValueString() + config.Templates["email"] = email + } + } + return config } @@ -399,5 +535,65 @@ func (r *systemConfigResource) mapClientToModel(config client.SystemConfig, mode } } + if config.Notifications != nil { + if model.Notifications == nil { + model.Notifications = ¬ificationsModel{} + } + if smtpConfig, ok := config.Notifications["smtp"].(map[string]interface{}); ok { + if model.Notifications.SMTP == nil { + model.Notifications.SMTP = &smtpModel{} + } + if v, ok := smtpConfig["enabled"].(bool); ok { + model.Notifications.SMTP.Enabled = types.BoolValue(v) + } + if v, ok := smtpConfig["from"].(string); ok { + model.Notifications.SMTP.From = types.StringValue(v) + } + if v, ok := smtpConfig["replyTo"].(string); ok { + model.Notifications.SMTP.ReplyTo = types.StringValue(v) + } + if transport, ok := smtpConfig["transport"].(map[string]interface{}); ok { + if v, ok := transport["host"].(string); ok { + model.Notifications.SMTP.Host = types.StringValue(v) + } + if v, ok := transport["port"].(float64); ok { + model.Notifications.SMTP.Port = types.Int64Value(int64(v)) + } else if v, ok := transport["port"].(int64); ok { + model.Notifications.SMTP.Port = types.Int64Value(v) + } + if v, ok := transport["username"].(string); ok { + model.Notifications.SMTP.Username = types.StringValue(v) + } + // password usually not returned or masked + if v, ok := transport["secure"].(bool); ok { + model.Notifications.SMTP.Secure = types.BoolValue(v) + } + if v, ok := transport["ignoreCert"].(bool); ok { + model.Notifications.SMTP.IgnoreCert = types.BoolValue(v) + } + } + } + } + + if config.Templates != nil { + if model.Templates == nil { + model.Templates = &templatesModel{} + } + if emailConfig, ok := config.Templates["email"].(map[string]interface{}); ok { + if model.Templates.Email == nil { + model.Templates.Email = &emailTemplatesModel{} + } + if v, ok := emailConfig["albumInviteTemplate"].(string); ok { + model.Templates.Email.AlbumInviteTemplate = types.StringValue(v) + } + if v, ok := emailConfig["albumUpdateTemplate"].(string); ok { + model.Templates.Email.AlbumUpdateTemplate = types.StringValue(v) + } + if v, ok := emailConfig["welcomeTemplate"].(string); ok { + model.Templates.Email.WelcomeTemplate = types.StringValue(v) + } + } + } + return model }