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
}