diff --git a/src/monitor-server/FlowForge.MonitorServer.sln b/src/monitor-server/FlowForge.MonitorServer.sln
index 1d502dd..bd4c559 100644
--- a/src/monitor-server/FlowForge.MonitorServer.sln
+++ b/src/monitor-server/FlowForge.MonitorServer.sln
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlowForge.MonitorServer", "src\FlowForge.MonitorServer\FlowForge.MonitorServer.csproj", "{C3D4E5F6-A7B8-9012-CDEF-123456789012}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FlowForge.Shared", "..\shared\FlowForge.Shared\FlowForge.Shared.csproj", "{F3A4B5C6-D7E8-9012-CDEF-123456789013}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -15,5 +17,9 @@ Global
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3D4E5F6-A7B8-9012-CDEF-123456789012}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F3A4B5C6-D7E8-9012-CDEF-123456789013}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F3A4B5C6-D7E8-9012-CDEF-123456789013}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F3A4B5C6-D7E8-9012-CDEF-123456789013}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F3A4B5C6-D7E8-9012-CDEF-123456789013}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/src/monitor-server/src/FlowForge.MonitorServer/Auth/TokenValidator.cs b/src/monitor-server/src/FlowForge.MonitorServer/Auth/TokenValidator.cs
new file mode 100644
index 0000000..b09ed59
--- /dev/null
+++ b/src/monitor-server/src/FlowForge.MonitorServer/Auth/TokenValidator.cs
@@ -0,0 +1,21 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.MonitorServer.Auth;
+
+public class TokenValidator
+{
+ private readonly string _expectedToken;
+
+ public TokenValidator(string expectedToken)
+ {
+ _expectedToken = expectedToken;
+ }
+
+ public bool Validate(string? token)
+ {
+ // TODO: Implement HMAC-based short-lived token validation
+ // For now, simple string comparison with the token injected at container creation
+ return !string.IsNullOrEmpty(token) && token == _expectedToken;
+ }
+}
diff --git a/src/monitor-server/src/FlowForge.MonitorServer/FlowForge.MonitorServer.csproj b/src/monitor-server/src/FlowForge.MonitorServer/FlowForge.MonitorServer.csproj
index 6718a44..2470c34 100644
--- a/src/monitor-server/src/FlowForge.MonitorServer/FlowForge.MonitorServer.csproj
+++ b/src/monitor-server/src/FlowForge.MonitorServer/FlowForge.MonitorServer.csproj
@@ -11,4 +11,8 @@
+
+
+
+
diff --git a/src/monitor-server/src/FlowForge.MonitorServer/Hubs/IPlcDataHubClient.cs b/src/monitor-server/src/FlowForge.MonitorServer/Hubs/IPlcDataHubClient.cs
new file mode 100644
index 0000000..f584f8b
--- /dev/null
+++ b/src/monitor-server/src/FlowForge.MonitorServer/Hubs/IPlcDataHubClient.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using FlowForge.Shared.Models.Monitor;
+
+namespace FlowForge.MonitorServer.Hubs;
+
+public interface IPlcDataHubClient
+{
+ Task ReceiveVariableValues(IReadOnlyList values);
+ Task ReceiveConnectionStatus(string status);
+ Task ReceiveError(string error);
+}
diff --git a/src/monitor-server/src/FlowForge.MonitorServer/Services/IMqttAdsClient.cs b/src/monitor-server/src/FlowForge.MonitorServer/Services/IMqttAdsClient.cs
new file mode 100644
index 0000000..fba2c9d
--- /dev/null
+++ b/src/monitor-server/src/FlowForge.MonitorServer/Services/IMqttAdsClient.cs
@@ -0,0 +1,15 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using FlowForge.Shared.Models.Monitor;
+
+namespace FlowForge.MonitorServer.Services;
+
+public interface IMqttAdsClient
+{
+ Task ConnectAsync(CancellationToken ct = default);
+ Task ReadVariableAsync(string variablePath, CancellationToken ct = default);
+ Task SubscribeAsync(string variablePath, Action callback, CancellationToken ct = default);
+ Task UnsubscribeAsync(string variablePath, CancellationToken ct = default);
+ Task DisconnectAsync(CancellationToken ct = default);
+}
diff --git a/src/monitor-server/src/FlowForge.MonitorServer/Services/MqttAdsClient.cs b/src/monitor-server/src/FlowForge.MonitorServer/Services/MqttAdsClient.cs
new file mode 100644
index 0000000..945c54d
--- /dev/null
+++ b/src/monitor-server/src/FlowForge.MonitorServer/Services/MqttAdsClient.cs
@@ -0,0 +1,39 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using FlowForge.Shared.Models.Monitor;
+
+namespace FlowForge.MonitorServer.Services;
+
+public class MqttAdsClient : IMqttAdsClient
+{
+ public Task ConnectAsync(CancellationToken ct)
+ {
+ // TODO: Connect to MQTT broker using MQTTnet
+ throw new NotImplementedException();
+ }
+
+ public Task ReadVariableAsync(string variablePath, CancellationToken ct)
+ {
+ // TODO: Publish ADS read request via MQTT, await response
+ throw new NotImplementedException();
+ }
+
+ public Task SubscribeAsync(string variablePath, Action callback, CancellationToken ct)
+ {
+ // TODO: Register ADS subscription via MQTT
+ throw new NotImplementedException();
+ }
+
+ public Task UnsubscribeAsync(string variablePath, CancellationToken ct)
+ {
+ // TODO: Unregister ADS subscription
+ throw new NotImplementedException();
+ }
+
+ public Task DisconnectAsync(CancellationToken ct)
+ {
+ // TODO: Disconnect from MQTT broker
+ throw new NotImplementedException();
+ }
+}
diff --git a/src/monitor-server/src/FlowForge.MonitorServer/Services/SubscriptionManager.cs b/src/monitor-server/src/FlowForge.MonitorServer/Services/SubscriptionManager.cs
new file mode 100644
index 0000000..6abba5b
--- /dev/null
+++ b/src/monitor-server/src/FlowForge.MonitorServer/Services/SubscriptionManager.cs
@@ -0,0 +1,39 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using System.Collections.Concurrent;
+
+namespace FlowForge.MonitorServer.Services;
+
+public class SubscriptionManager
+{
+ private readonly ConcurrentDictionary> _subscriptionsByConnection = new();
+
+ public void AddSubscription(string connectionId, string variablePath)
+ {
+ var paths = _subscriptionsByConnection.GetOrAdd(connectionId, _ => []);
+ lock (paths) { paths.Add(variablePath); }
+ }
+
+ public void RemoveSubscription(string connectionId, string variablePath)
+ {
+ if (_subscriptionsByConnection.TryGetValue(connectionId, out var paths))
+ {
+ lock (paths) { paths.Remove(variablePath); }
+ }
+ }
+
+ public IReadOnlySet GetSubscriptions(string connectionId)
+ {
+ if (_subscriptionsByConnection.TryGetValue(connectionId, out var paths))
+ {
+ lock (paths) { return paths.ToHashSet(); }
+ }
+ return new HashSet();
+ }
+
+ public void RemoveAllSubscriptions(string connectionId)
+ {
+ _subscriptionsByConnection.TryRemove(connectionId, out _);
+ }
+}
diff --git a/src/shared/FlowForge.Shared/Enums/Permission.cs b/src/shared/FlowForge.Shared/Enums/Permission.cs
new file mode 100644
index 0000000..97b8519
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Enums/Permission.cs
@@ -0,0 +1,18 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Enums;
+
+public enum Permission
+{
+ ProjectView,
+ ProjectCreate,
+ ProjectEdit,
+ ProjectDelete,
+ Build,
+ Deploy,
+ TargetManage,
+ Monitor,
+ AdminUsers,
+ AdminSystem
+}
diff --git a/src/shared/FlowForge.Shared/Enums/ProjectRole.cs b/src/shared/FlowForge.Shared/Enums/ProjectRole.cs
new file mode 100644
index 0000000..d22c91e
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Enums/ProjectRole.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Enums;
+
+public enum ProjectRole
+{
+ Viewer,
+ Editor,
+ Builder,
+ Deployer,
+ Owner
+}
diff --git a/src/shared/FlowForge.Shared/FlowForge.Shared.csproj b/src/shared/FlowForge.Shared/FlowForge.Shared.csproj
new file mode 100644
index 0000000..4900ca5
--- /dev/null
+++ b/src/shared/FlowForge.Shared/FlowForge.Shared.csproj
@@ -0,0 +1,10 @@
+
+
+
+ net9.0
+ enable
+ enable
+ FlowForge.Shared
+
+
+
diff --git a/src/shared/FlowForge.Shared/Models/Auth/UserInfoDto.cs b/src/shared/FlowForge.Shared/Models/Auth/UserInfoDto.cs
new file mode 100644
index 0000000..436ad4a
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Auth/UserInfoDto.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Auth;
+
+public record UserInfoDto
+{
+ public string Id { get; init; } = string.Empty;
+ public string UserName { get; init; } = string.Empty;
+ public string Email { get; init; } = string.Empty;
+ public string DisplayName { get; init; } = string.Empty;
+ public IReadOnlyList Roles { get; init; } = [];
+}
diff --git a/src/shared/FlowForge.Shared/Models/Build/BuildJobDto.cs b/src/shared/FlowForge.Shared/Models/Build/BuildJobDto.cs
new file mode 100644
index 0000000..18ad350
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Build/BuildJobDto.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Build;
+
+public record BuildJobDto
+{
+ public Guid Id { get; init; }
+ public Guid ProjectId { get; init; }
+ public string ProjectName { get; init; } = string.Empty;
+ public string RepoUrl { get; init; } = string.Empty;
+ public string Branch { get; init; } = "main";
+ public string TwinCatVersion { get; init; } = string.Empty;
+ public string RequestedBy { get; init; } = string.Empty;
+ public BuildStatus Status { get; init; }
+ public bool IncludeDeploy { get; init; }
+ public string? TargetAmsNetId { get; init; }
+ public DateTimeOffset CreatedAt { get; init; }
+}
diff --git a/src/shared/FlowForge.Shared/Models/Build/BuildProgressDto.cs b/src/shared/FlowForge.Shared/Models/Build/BuildProgressDto.cs
new file mode 100644
index 0000000..dd1c237
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Build/BuildProgressDto.cs
@@ -0,0 +1,12 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Build;
+
+public record BuildProgressDto
+{
+ public Guid BuildId { get; init; }
+ public string Stage { get; init; } = string.Empty;
+ public int Percentage { get; init; }
+ public string Message { get; init; } = string.Empty;
+}
diff --git a/src/shared/FlowForge.Shared/Models/Build/BuildResultDto.cs b/src/shared/FlowForge.Shared/Models/Build/BuildResultDto.cs
new file mode 100644
index 0000000..ba6d3d5
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Build/BuildResultDto.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Build;
+
+public record BuildResultDto
+{
+ public Guid BuildId { get; init; }
+ public bool Success { get; init; }
+ public IReadOnlyList Errors { get; init; } = [];
+ public string? CommitSha { get; init; }
+ public DateTimeOffset CompletedAt { get; init; }
+}
diff --git a/src/shared/FlowForge.Shared/Models/Build/BuildStatus.cs b/src/shared/FlowForge.Shared/Models/Build/BuildStatus.cs
new file mode 100644
index 0000000..1e6b0d5
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Build/BuildStatus.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Build;
+
+public enum BuildStatus
+{
+ Pending,
+ Claimed,
+ InProgress,
+ Completed,
+ Failed
+}
diff --git a/src/shared/FlowForge.Shared/Models/Deploy/DeployRequestDto.cs b/src/shared/FlowForge.Shared/Models/Deploy/DeployRequestDto.cs
new file mode 100644
index 0000000..af1ca3d
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Deploy/DeployRequestDto.cs
@@ -0,0 +1,11 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Deploy;
+
+public record DeployRequestDto
+{
+ public Guid ProjectId { get; init; }
+ public string TargetAmsNetId { get; init; } = string.Empty;
+ public string? ApproverId { get; init; }
+}
diff --git a/src/shared/FlowForge.Shared/Models/Deploy/DeployResultDto.cs b/src/shared/FlowForge.Shared/Models/Deploy/DeployResultDto.cs
new file mode 100644
index 0000000..ca0ba20
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Deploy/DeployResultDto.cs
@@ -0,0 +1,12 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Deploy;
+
+public record DeployResultDto
+{
+ public Guid DeployId { get; init; }
+ public bool Success { get; init; }
+ public string? Error { get; init; }
+ public DateTimeOffset CompletedAt { get; init; }
+}
diff --git a/src/shared/FlowForge.Shared/Models/Deploy/DeployStatus.cs b/src/shared/FlowForge.Shared/Models/Deploy/DeployStatus.cs
new file mode 100644
index 0000000..8a7fc26
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Deploy/DeployStatus.cs
@@ -0,0 +1,15 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Deploy;
+
+public enum DeployStatus
+{
+ Pending,
+ AwaitingApproval,
+ Approved,
+ InProgress,
+ Completed,
+ Failed,
+ Rejected
+}
diff --git a/src/shared/FlowForge.Shared/Models/Flow/FlowConnection.cs b/src/shared/FlowForge.Shared/Models/Flow/FlowConnection.cs
new file mode 100644
index 0000000..7ae7094
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Flow/FlowConnection.cs
@@ -0,0 +1,10 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Flow;
+
+public record FlowConnection
+{
+ public FlowPort From { get; init; } = new();
+ public FlowPort To { get; init; } = new();
+}
diff --git a/src/shared/FlowForge.Shared/Models/Flow/FlowDocument.cs b/src/shared/FlowForge.Shared/Models/Flow/FlowDocument.cs
new file mode 100644
index 0000000..264b30c
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Flow/FlowDocument.cs
@@ -0,0 +1,13 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Flow;
+
+public record FlowDocument
+{
+ public string Name { get; init; } = string.Empty;
+ public string Version { get; init; } = string.Empty;
+ public IReadOnlyList Nodes { get; init; } = [];
+ public IReadOnlyList Connections { get; init; } = [];
+ public Dictionary Metadata { get; init; } = [];
+}
diff --git a/src/shared/FlowForge.Shared/Models/Flow/FlowNode.cs b/src/shared/FlowForge.Shared/Models/Flow/FlowNode.cs
new file mode 100644
index 0000000..fb98898
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Flow/FlowNode.cs
@@ -0,0 +1,12 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Flow;
+
+public record FlowNode
+{
+ public string Id { get; init; } = string.Empty;
+ public string Type { get; init; } = string.Empty;
+ public NodePosition Position { get; init; } = new();
+ public Dictionary Parameters { get; init; } = [];
+}
diff --git a/src/shared/FlowForge.Shared/Models/Flow/FlowPort.cs b/src/shared/FlowForge.Shared/Models/Flow/FlowPort.cs
new file mode 100644
index 0000000..1a62aec
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Flow/FlowPort.cs
@@ -0,0 +1,10 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Flow;
+
+public record FlowPort
+{
+ public string NodeId { get; init; } = string.Empty;
+ public string PortName { get; init; } = string.Empty;
+}
diff --git a/src/shared/FlowForge.Shared/Models/Flow/NodePosition.cs b/src/shared/FlowForge.Shared/Models/Flow/NodePosition.cs
new file mode 100644
index 0000000..cd867cd
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Flow/NodePosition.cs
@@ -0,0 +1,10 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Flow;
+
+public record NodePosition
+{
+ public double X { get; init; }
+ public double Y { get; init; }
+}
diff --git a/src/shared/FlowForge.Shared/Models/Monitor/MonitorSessionDto.cs b/src/shared/FlowForge.Shared/Models/Monitor/MonitorSessionDto.cs
new file mode 100644
index 0000000..e0e48d5
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Monitor/MonitorSessionDto.cs
@@ -0,0 +1,12 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Monitor;
+
+public record MonitorSessionDto
+{
+ public string SessionId { get; init; } = string.Empty;
+ public string SignalREndpoint { get; init; } = string.Empty;
+ public string AuthToken { get; init; } = string.Empty;
+ public string TargetAmsNetId { get; init; } = string.Empty;
+}
diff --git a/src/shared/FlowForge.Shared/Models/Monitor/PlcVariableValueDto.cs b/src/shared/FlowForge.Shared/Models/Monitor/PlcVariableValueDto.cs
new file mode 100644
index 0000000..0bafc5e
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Monitor/PlcVariableValueDto.cs
@@ -0,0 +1,12 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Monitor;
+
+public record PlcVariableValueDto
+{
+ public string Path { get; init; } = string.Empty;
+ public object? Value { get; init; }
+ public string DataType { get; init; } = string.Empty;
+ public DateTimeOffset Timestamp { get; init; }
+}
diff --git a/src/shared/FlowForge.Shared/Models/Project/ProjectDetailDto.cs b/src/shared/FlowForge.Shared/Models/Project/ProjectDetailDto.cs
new file mode 100644
index 0000000..fdd5a0e
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Project/ProjectDetailDto.cs
@@ -0,0 +1,19 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+using FlowForge.Shared.Models.Flow;
+
+namespace FlowForge.Shared.Models.Project;
+
+public record ProjectDetailDto
+{
+ public Guid Id { get; init; }
+ public string Name { get; init; } = string.Empty;
+ public string Description { get; init; } = string.Empty;
+ public string RepoUrl { get; init; } = string.Empty;
+ public string Branch { get; init; } = "main";
+ public string? LastCommitSha { get; init; }
+ public FlowDocument? Flow { get; init; }
+ public DateTimeOffset CreatedAt { get; init; }
+ public DateTimeOffset UpdatedAt { get; init; }
+}
diff --git a/src/shared/FlowForge.Shared/Models/Project/ProjectSummaryDto.cs b/src/shared/FlowForge.Shared/Models/Project/ProjectSummaryDto.cs
new file mode 100644
index 0000000..0f8e399
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Project/ProjectSummaryDto.cs
@@ -0,0 +1,15 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Project;
+
+public record ProjectSummaryDto
+{
+ public Guid Id { get; init; }
+ public string Name { get; init; } = string.Empty;
+ public string Description { get; init; } = string.Empty;
+ public string RepoUrl { get; init; } = string.Empty;
+ public string? LastCommitSha { get; init; }
+ public DateTimeOffset CreatedAt { get; init; }
+ public DateTimeOffset UpdatedAt { get; init; }
+}
diff --git a/src/shared/FlowForge.Shared/Models/Target/PlcTargetDto.cs b/src/shared/FlowForge.Shared/Models/Target/PlcTargetDto.cs
new file mode 100644
index 0000000..8bb3d03
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Target/PlcTargetDto.cs
@@ -0,0 +1,16 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Target;
+
+public record PlcTargetDto
+{
+ public Guid Id { get; init; }
+ public string Name { get; init; } = string.Empty;
+ public string AmsNetId { get; init; } = string.Empty;
+ public string TwinCatVersion { get; init; } = string.Empty;
+ public IReadOnlyList Labels { get; init; } = [];
+ public Guid? GroupId { get; init; }
+ public bool IsProductionTarget { get; init; }
+ public bool DeployLocked { get; init; }
+}
diff --git a/src/shared/FlowForge.Shared/Models/Target/TargetGroupDto.cs b/src/shared/FlowForge.Shared/Models/Target/TargetGroupDto.cs
new file mode 100644
index 0000000..7850dfc
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Models/Target/TargetGroupDto.cs
@@ -0,0 +1,12 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Models.Target;
+
+public record TargetGroupDto
+{
+ public Guid Id { get; init; }
+ public string Name { get; init; } = string.Empty;
+ public string Description { get; init; } = string.Empty;
+ public IReadOnlyList Targets { get; init; } = [];
+}
diff --git a/src/shared/FlowForge.Shared/Mqtt/MqttTopics.cs b/src/shared/FlowForge.Shared/Mqtt/MqttTopics.cs
new file mode 100644
index 0000000..a89f4bc
--- /dev/null
+++ b/src/shared/FlowForge.Shared/Mqtt/MqttTopics.cs
@@ -0,0 +1,27 @@
+// Copyright (c) 2026 Qubernetic (Biró, Csaba Attila)
+// SPDX-License-Identifier: AGPL-3.0-or-later
+
+namespace FlowForge.Shared.Mqtt;
+
+public static class MqttTopics
+{
+ private const string Prefix = "flowforge";
+
+ public static string BuildNotify(string twinCatVersion) =>
+ $"{Prefix}/build/notify/{twinCatVersion}";
+
+ public static string BuildProgress(Guid buildId) =>
+ $"{Prefix}/build/progress/{buildId}";
+
+ public static string DeployRequest(string targetId) =>
+ $"{Prefix}/deploy/request/{targetId}";
+
+ public static string AdsRead(string amsNetId) =>
+ $"{Prefix}/ads/read/{amsNetId}";
+
+ public static string AdsWrite(string amsNetId) =>
+ $"{Prefix}/ads/write/{amsNetId}";
+
+ public static string AdsNotification(string amsNetId) =>
+ $"{Prefix}/ads/notification/{amsNetId}";
+}