diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8e46507
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,717 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# HTTP files (REST Client files)
+*.http
+
+# Docker
+docker-compose.override.yml
+
+# Environment files
+.env
+.env.local
+.env.*.local
+
+# OS generated files
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
diff --git a/src/GreenHome.Api/Controllers/VoiceCallTestController.cs b/src/GreenHome.Api/Controllers/VoiceCallTestController.cs
index 21a3458..6b66700 100644
--- a/src/GreenHome.Api/Controllers/VoiceCallTestController.cs
+++ b/src/GreenHome.Api/Controllers/VoiceCallTestController.cs
@@ -268,3 +268,5 @@ public class VoiceCallTestController : ControllerBase
}
}
+
+
diff --git a/src/GreenHome.Api/GreenHome.Api.csproj b/src/GreenHome.Api/GreenHome.Api.csproj
index 7c6e1d4..4f60859 100644
--- a/src/GreenHome.Api/GreenHome.Api.csproj
+++ b/src/GreenHome.Api/GreenHome.Api.csproj
@@ -1,26 +1,26 @@
-
-
-
- net9.0
- enable
- enable
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ net9.0
+ enable
+ enable
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/GreenHome.Api/Program.cs b/src/GreenHome.Api/Program.cs
index a7ddcbe..bb93df3 100644
--- a/src/GreenHome.Api/Program.cs
+++ b/src/GreenHome.Api/Program.cs
@@ -1,89 +1,89 @@
-using FluentValidation;
-using GreenHome.Application;
-using GreenHome.Infrastructure;
-using GreenHome.Sms.Ippanel;
-using GreenHome.VoiceCall.Avanak;
-using Microsoft.EntityFrameworkCore;
-var builder = WebApplication.CreateBuilder(args);
-
-// Add services to the container.
-builder.Services.AddControllers();
-builder.Services.AddEndpointsApiExplorer();
-builder.Services.AddSwaggerGen();
-
-// Application/Infrastructure DI
-builder.Services.AddAutoMapper(typeof(GreenHome.Application.MappingProfile));
-builder.Services.AddValidatorsFromAssemblyContaining();
-
-// CORS for Next.js dev (adjust origins as needed)
-const string CorsPolicy = "DefaultCors";
-builder.Services.AddCors(options =>
-{
- options.AddPolicy(CorsPolicy, policy =>
- policy
- .WithOrigins(
- "http://green.nabaksoft.ir",
- "https://green.nabaksoft.ir",
- "http://gh1.nabaksoft.ir",
- "https://gh1.nabaksoft.ir",
- "http://localhost:3000",
- "http://localhost:3000",
- "http://127.0.0.1:3000",
- "https://localhost:3000"
- )
- .AllowAnyHeader()
- .AllowAnyMethod()
- .AllowCredentials()
- );
-});
-
-builder.Services.AddDbContext(options =>
-{
- options.UseSqlServer(builder.Configuration.GetConnectionString("Default"));
-});
-builder.Services.AddScoped();
-builder.Services.AddScoped();
-builder.Services.AddScoped();
-builder.Services.AddScoped();
-builder.Services.AddScoped();
-
-// SMS Service Configuration
-builder.Services.AddIppanelSms(builder.Configuration);
-
-// Voice Call Service Configuration
-builder.Services.AddAvanakVoiceCall(builder.Configuration);
-
-var app = builder.Build();
-
-// Apply pending migrations automatically
-using (var scope = app.Services.CreateScope())
-{
- var services = scope.ServiceProvider;
- try
- {
- var context = services.GetRequiredService();
- context.Database.Migrate();
- }
- catch (Exception ex)
- {
- var logger = services.GetRequiredService>();
- logger.LogError(ex, "An error occurred while migrating the database.");
- }
-}
-
-// Configure the HTTP request pipeline.
-//if (app.Environment.IsDevelopment())
-{
- app.UseSwagger();
- app.UseSwaggerUI();
-}
-
-app.UseHttpsRedirection();
-
-app.UseCors(CorsPolicy);
-
-app.UseAuthorization();
-
-app.MapControllers();
-
-app.Run();
+using FluentValidation;
+using GreenHome.Application;
+using GreenHome.Infrastructure;
+using GreenHome.Sms.Ippanel;
+using GreenHome.VoiceCall.Avanak;
+using Microsoft.EntityFrameworkCore;
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+builder.Services.AddControllers();
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+
+// Application/Infrastructure DI
+builder.Services.AddAutoMapper(typeof(GreenHome.Application.MappingProfile));
+builder.Services.AddValidatorsFromAssemblyContaining();
+
+// CORS for Next.js dev (adjust origins as needed)
+const string CorsPolicy = "DefaultCors";
+builder.Services.AddCors(options =>
+{
+ options.AddPolicy(CorsPolicy, policy =>
+ policy
+ .WithOrigins(
+ "http://green.nabaksoft.ir",
+ "https://green.nabaksoft.ir",
+ "http://gh1.nabaksoft.ir",
+ "https://gh1.nabaksoft.ir",
+ "http://localhost:3000",
+ "http://localhost:3000",
+ "http://127.0.0.1:3000",
+ "https://localhost:3000"
+ )
+ .AllowAnyHeader()
+ .AllowAnyMethod()
+ .AllowCredentials()
+ );
+});
+
+builder.Services.AddDbContext(options =>
+{
+ options.UseSqlServer(builder.Configuration.GetConnectionString("Default"));
+});
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+builder.Services.AddScoped();
+
+// SMS Service Configuration
+builder.Services.AddIppanelSms(builder.Configuration);
+
+// Voice Call Service Configuration
+builder.Services.AddAvanakVoiceCall(builder.Configuration);
+
+var app = builder.Build();
+
+// Apply pending migrations automatically
+using (var scope = app.Services.CreateScope())
+{
+ var services = scope.ServiceProvider;
+ try
+ {
+ var context = services.GetRequiredService();
+ context.Database.Migrate();
+ }
+ catch (Exception ex)
+ {
+ var logger = services.GetRequiredService>();
+ logger.LogError(ex, "An error occurred while migrating the database.");
+ }
+}
+
+// Configure the HTTP request pipeline.
+//if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseHttpsRedirection();
+
+app.UseCors(CorsPolicy);
+
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();
diff --git a/src/GreenHome.Api/appsettings.json b/src/GreenHome.Api/appsettings.json
index 235719b..5f84305 100644
--- a/src/GreenHome.Api/appsettings.json
+++ b/src/GreenHome.Api/appsettings.json
@@ -2,12 +2,10 @@
"Logging": {
"LogLevel": {
"Default": "Information",
- "Microsoft.AspNetCore": "Warning"
+ "Microsoft.AspNetCore": "Warning",
+ "GreenHome.VoiceCall.Avanak": "Information"
}
},
- "ConnectionStrings": {
- "Default": "Server=.;Database=GreenHomeDb;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True"
- },
"IppanelSms": {
"BaseUrl": "https://edge.ippanel.com/v1",
"AuthorizationToken": "YTA1Zjk3N2EtNzkwOC00ZTg5LWFjZmYtZGEyZDAyNjNlZWQxM2Q2ZDVjYWE0MTA2Yzc1NDYzZDY1Y2VkMjlhMzcwNjA=",
diff --git a/src/GreenHome.Domain/TelemetryRecord.cs b/src/GreenHome.Domain/TelemetryRecord.cs
index 44d566a..33d7544 100644
--- a/src/GreenHome.Domain/TelemetryRecord.cs
+++ b/src/GreenHome.Domain/TelemetryRecord.cs
@@ -5,6 +5,7 @@ public sealed class TelemetryRecord
public int Id { get; set; }
public int DeviceId { get; set; }
public DateTime TimestampUtc { get; set; }
+ public DateTime ServerTimestampUtc { get; set; } // زمان ثبت در سرور
public decimal TemperatureC { get; set; } // decimal(18,2)
public decimal HumidityPercent { get; set; } // decimal(18,2)
public decimal SoilPercent { get; set; } // decimal(18,2)
diff --git a/src/GreenHome.Infrastructure/GreenHomeDbContext.cs b/src/GreenHome.Infrastructure/GreenHomeDbContext.cs
index ad00e31..fa35def 100644
--- a/src/GreenHome.Infrastructure/GreenHomeDbContext.cs
+++ b/src/GreenHome.Infrastructure/GreenHomeDbContext.cs
@@ -43,6 +43,7 @@ public sealed class GreenHomeDbContext : DbContext
b.Property(x => x.PersianDate).HasMaxLength(10);
b.HasIndex(x => new { x.DeviceId, x.PersianYear, x.PersianMonth });
b.HasIndex(x => new { x.DeviceId, x.TimestampUtc });
+ b.HasIndex(x => new { x.DeviceId, x.ServerTimestampUtc });
});
modelBuilder.Entity(b =>
diff --git a/src/GreenHome.Infrastructure/Migrations/20251127125625_AddServerTimestampUtc.Designer.cs b/src/GreenHome.Infrastructure/Migrations/20251127125625_AddServerTimestampUtc.Designer.cs
new file mode 100644
index 0000000..b6b9f10
--- /dev/null
+++ b/src/GreenHome.Infrastructure/Migrations/20251127125625_AddServerTimestampUtc.Designer.cs
@@ -0,0 +1,381 @@
+//
+using System;
+using GreenHome.Infrastructure;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace GreenHome.Infrastructure.Migrations
+{
+ [DbContext(typeof(GreenHomeDbContext))]
+ [Migration("20251127125625_AddServerTimestampUtc")]
+ partial class AddServerTimestampUtc
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder
+ .HasAnnotation("ProductVersion", "9.0.9")
+ .HasAnnotation("Relational:MaxIdentifierLength", 128);
+
+ SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
+
+ modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("AlertType")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("nvarchar(50)");
+
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("ErrorMessage")
+ .HasMaxLength(1000)
+ .HasColumnType("nvarchar(1000)");
+
+ b.Property("IsSent")
+ .HasColumnType("bit");
+
+ b.Property("Message")
+ .IsRequired()
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("MessageOutboxIds")
+ .HasMaxLength(500)
+ .HasColumnType("nvarchar(500)");
+
+ b.Property("SentAt")
+ .HasColumnType("datetime2");
+
+ b.Property("UserId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.HasIndex("DeviceId", "UserId", "AlertType", "SentAt");
+
+ b.ToTable("AlertNotifications", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.Device", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("DeviceName")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.Property("Location")
+ .IsRequired()
+ .HasMaxLength(250)
+ .HasColumnType("nvarchar(250)");
+
+ b.Property("NeshanLocation")
+ .IsRequired()
+ .HasMaxLength(80)
+ .HasColumnType("nvarchar(80)");
+
+ b.Property("UserId")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("Devices", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("DangerMaxTemperature")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("DangerMinTemperature")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("MaxGasPPM")
+ .HasColumnType("int");
+
+ b.Property("MaxHumidityPercent")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("MaxLux")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("MaxTemperature")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("MinGasPPM")
+ .HasColumnType("int");
+
+ b.Property("MinHumidityPercent")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("MinLux")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("MinTemperature")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("datetime2");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DeviceId")
+ .IsUnique();
+
+ b.ToTable("DeviceSettings", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
+ {
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("UserId")
+ .HasColumnType("int");
+
+ b.HasKey("DeviceId", "UserId");
+
+ b.HasIndex("UserId");
+
+ b.ToTable("DeviceUsers", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.TelemetryRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("DeviceId")
+ .HasColumnType("int");
+
+ b.Property("GasPPM")
+ .HasColumnType("int");
+
+ b.Property("HumidityPercent")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("Lux")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("PersianDate")
+ .IsRequired()
+ .HasMaxLength(10)
+ .HasColumnType("nvarchar(10)");
+
+ b.Property("PersianMonth")
+ .HasColumnType("int");
+
+ b.Property("PersianYear")
+ .HasColumnType("int");
+
+ b.Property("ServerTimestampUtc")
+ .HasColumnType("datetime2");
+
+ b.Property("SoilPercent")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("TemperatureC")
+ .HasColumnType("decimal(18,2)");
+
+ b.Property("TimestampUtc")
+ .HasColumnType("datetime2");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DeviceId", "ServerTimestampUtc");
+
+ b.HasIndex("DeviceId", "TimestampUtc");
+
+ b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
+
+ b.ToTable("Telemetry", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.User", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("Family")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("LastLoginAt")
+ .HasColumnType("datetime2");
+
+ b.Property("Mobile")
+ .IsRequired()
+ .HasMaxLength(11)
+ .HasColumnType("nvarchar(11)");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("nvarchar(100)");
+
+ b.Property("Role")
+ .HasColumnType("int");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Mobile")
+ .IsUnique();
+
+ b.ToTable("Users", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.VerificationCode", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("int");
+
+ SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"));
+
+ b.Property("Code")
+ .IsRequired()
+ .HasMaxLength(4)
+ .HasColumnType("nvarchar(4)");
+
+ b.Property("CreatedAt")
+ .HasColumnType("datetime2");
+
+ b.Property("ExpiresAt")
+ .HasColumnType("datetime2");
+
+ b.Property("IsUsed")
+ .HasColumnType("bit");
+
+ b.Property("Mobile")
+ .IsRequired()
+ .HasMaxLength(11)
+ .HasColumnType("nvarchar(11)");
+
+ b.Property("UsedAt")
+ .HasColumnType("datetime2");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Mobile", "Code", "IsUsed");
+
+ b.ToTable("VerificationCodes", (string)null);
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.AlertNotification", b =>
+ {
+ b.HasOne("GreenHome.Domain.Device", "Device")
+ .WithMany()
+ .HasForeignKey("DeviceId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.HasOne("GreenHome.Domain.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("Device");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.Device", b =>
+ {
+ b.HasOne("GreenHome.Domain.User", "User")
+ .WithMany()
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DeviceSettings", b =>
+ {
+ b.HasOne("GreenHome.Domain.Device", "Device")
+ .WithMany()
+ .HasForeignKey("DeviceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Device");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.DeviceUser", b =>
+ {
+ b.HasOne("GreenHome.Domain.Device", "Device")
+ .WithMany("DeviceUsers")
+ .HasForeignKey("DeviceId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.HasOne("GreenHome.Domain.User", "User")
+ .WithMany("DeviceUsers")
+ .HasForeignKey("UserId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Device");
+
+ b.Navigation("User");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.Device", b =>
+ {
+ b.Navigation("DeviceUsers");
+ });
+
+ modelBuilder.Entity("GreenHome.Domain.User", b =>
+ {
+ b.Navigation("DeviceUsers");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/GreenHome.Infrastructure/Migrations/20251127125625_AddServerTimestampUtc.cs b/src/GreenHome.Infrastructure/Migrations/20251127125625_AddServerTimestampUtc.cs
new file mode 100644
index 0000000..768c39b
--- /dev/null
+++ b/src/GreenHome.Infrastructure/Migrations/20251127125625_AddServerTimestampUtc.cs
@@ -0,0 +1,39 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace GreenHome.Infrastructure.Migrations
+{
+ ///
+ public partial class AddServerTimestampUtc : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "ServerTimestampUtc",
+ table: "Telemetry",
+ type: "datetime2",
+ nullable: false,
+ defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
+
+ migrationBuilder.CreateIndex(
+ name: "IX_Telemetry_DeviceId_ServerTimestampUtc",
+ table: "Telemetry",
+ columns: new[] { "DeviceId", "ServerTimestampUtc" });
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropIndex(
+ name: "IX_Telemetry_DeviceId_ServerTimestampUtc",
+ table: "Telemetry");
+
+ migrationBuilder.DropColumn(
+ name: "ServerTimestampUtc",
+ table: "Telemetry");
+ }
+ }
+}
diff --git a/src/GreenHome.Infrastructure/Migrations/GreenHomeDbContextModelSnapshot.cs b/src/GreenHome.Infrastructure/Migrations/GreenHomeDbContextModelSnapshot.cs
index 8818e41..d2f4a63 100644
--- a/src/GreenHome.Infrastructure/Migrations/GreenHomeDbContextModelSnapshot.cs
+++ b/src/GreenHome.Infrastructure/Migrations/GreenHomeDbContextModelSnapshot.cs
@@ -203,6 +203,9 @@ namespace GreenHome.Infrastructure.Migrations
b.Property("PersianYear")
.HasColumnType("int");
+ b.Property("ServerTimestampUtc")
+ .HasColumnType("datetime2");
+
b.Property("SoilPercent")
.HasColumnType("decimal(18,2)");
@@ -214,6 +217,8 @@ namespace GreenHome.Infrastructure.Migrations
b.HasKey("Id");
+ b.HasIndex("DeviceId", "ServerTimestampUtc");
+
b.HasIndex("DeviceId", "TimestampUtc");
b.HasIndex("DeviceId", "PersianYear", "PersianMonth");
diff --git a/src/GreenHome.Infrastructure/TelemetryService.cs b/src/GreenHome.Infrastructure/TelemetryService.cs
index bd0fb33..7e51e73 100644
--- a/src/GreenHome.Infrastructure/TelemetryService.cs
+++ b/src/GreenHome.Infrastructure/TelemetryService.cs
@@ -29,6 +29,9 @@ public sealed class TelemetryService : ITelemetryService
dto.DeviceId = device.Id; // Update DTO for alert service
}
}
+ // ذخیره زمان سرور در لحظه ثبت
+ entity.ServerTimestampUtc = DateTime.Now;
+
var dt = dto.TimestampUtc;
var py = PersianCalendar.GetYear(dt);
var pm = PersianCalendar.GetMonth(dt);
@@ -37,6 +40,16 @@ public sealed class TelemetryService : ITelemetryService
entity.PersianMonth = pm;
entity.PersianDate = $"{py:0000}/{pm:00}/{pd:00}";
+ if(entity.Lux < 0)
+ {
+ var lastItem = await dbContext.TelemetryRecords
+ .Where(x => x.DeviceId == entity.DeviceId)
+ .OrderByDescending(x => x.TimestampUtc)
+ .FirstOrDefaultAsync(cancellationToken);
+
+ entity.Lux = lastItem?.Lux ?? 0;
+ }
+
dbContext.TelemetryRecords.Add(entity);
await dbContext.SaveChangesAsync(cancellationToken);
return entity.Id;
@@ -51,24 +64,34 @@ public sealed class TelemetryService : ITelemetryService
}
if (filter.StartDateUtc.HasValue)
{
- var start = filter.StartDateUtc.Value.Date.AddDays(1);
- query = query.Where(x => x.TimestampUtc >= start);
+ //var start = filter.StartDateUtc.Value.Date.AddDays(1);
+ query = query.Where(x => x.ServerTimestampUtc >= filter.StartDateUtc.Value);
}
if (filter.EndDateUtc.HasValue)
{
- var end = filter.EndDateUtc.Value.Date.AddDays(1);
- query = query.Where(x => x.TimestampUtc < end);
+ query = query.Where(x => x.ServerTimestampUtc < filter.EndDateUtc.Value);
}
+ if(filter.Page <= 0) filter.Page = 1;
+ if(filter.PageSize <= 0) filter.PageSize = 1000000; // No limit
var total = await query.CountAsync(cancellationToken);
var skip = (filter.Page - 1) * filter.PageSize;
var items = await query
- .OrderByDescending(x => x.TimestampUtc)
+ .OrderByDescending(x => x.ServerTimestampUtc)
.Skip(skip)
.Take(filter.PageSize)
.ToListAsync(cancellationToken);
+ if(items.Any(x => x.Lux < 0))
+ {
+ for (int i = 1; i < items.Count; i++)
+ {
+ if (items[i].Lux < 0)
+ items[i].Lux = items[i - 1].Lux;
+ }
+ }
+
return new PagedResult
{
Items = mapper.Map>(items),