Merge pull request #1861 from sbwalker/dev
added support for url mapping and viitors
This commit is contained in:
@ -164,6 +164,14 @@ namespace Oqtane.Controllers
|
||||
authorized = User.IsInRole(RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId);
|
||||
}
|
||||
break;
|
||||
case EntityNames.Visitor:
|
||||
authorized = false;
|
||||
var visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString();
|
||||
if (int.TryParse(Request.Cookies[visitorCookie], out int visitorId))
|
||||
{
|
||||
authorized = (visitorId == entityId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return authorized;
|
||||
}
|
||||
|
119
Oqtane.Server/Controllers/UrlMappingController.cs
Normal file
119
Oqtane.Server/Controllers/UrlMappingController.cs
Normal file
@ -0,0 +1,119 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using System.Net;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
[Route(ControllerRoutes.ApiRoute)]
|
||||
public class UrlMappingController : Controller
|
||||
{
|
||||
private readonly IUrlMappingRepository _urlMappings;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public UrlMappingController(IUrlMappingRepository urlMappings, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_urlMappings = urlMappings;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
|
||||
// GET: api/<controller>?siteid=x&ismapped=y
|
||||
[HttpGet]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public IEnumerable<UrlMapping> Get(string siteid, string ismapped)
|
||||
{
|
||||
int SiteId;
|
||||
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
|
||||
{
|
||||
return _urlMappings.GetUrlMappings(SiteId, bool.Parse(ismapped));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Get Attempt {SiteId}", siteid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// GET api/<controller>/5
|
||||
[HttpGet("{id}")]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public UrlMapping Get(int id)
|
||||
{
|
||||
var urlMapping = _urlMappings.GetUrlMapping(id);
|
||||
if (urlMapping != null && (urlMapping.SiteId == _alias.SiteId))
|
||||
{
|
||||
return urlMapping;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Get Attempt {UrlMappingId}", id);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// POST api/<controller>
|
||||
[HttpPost]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public UrlMapping Post([FromBody] UrlMapping urlMapping)
|
||||
{
|
||||
if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId)
|
||||
{
|
||||
urlMapping = _urlMappings.AddUrlMapping(urlMapping);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "UrlMapping Added {UrlMapping}", urlMapping);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Post Attempt {Role}", urlMapping);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
urlMapping = null;
|
||||
}
|
||||
return urlMapping;
|
||||
}
|
||||
|
||||
// PUT api/<controller>/5
|
||||
[HttpPut("{id}")]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public UrlMapping Put(int id, [FromBody] UrlMapping urlMapping)
|
||||
{
|
||||
if (ModelState.IsValid && urlMapping.SiteId == _alias.SiteId && _urlMappings.GetUrlMapping(urlMapping.UrlMappingId, false) != null)
|
||||
{
|
||||
urlMapping = _urlMappings.UpdateUrlMapping(urlMapping);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "UrlMapping Updated {UrlMapping}", urlMapping);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Put Attempt {UrlMapping}", urlMapping);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
urlMapping = null;
|
||||
}
|
||||
return urlMapping;
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/5
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public void Delete(int id)
|
||||
{
|
||||
var urlMapping = _urlMappings.GetUrlMapping(id);
|
||||
if (urlMapping != null && urlMapping.SiteId == _alias.SiteId)
|
||||
{
|
||||
_urlMappings.DeleteUrlMapping(id);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "UrlMapping Deleted {UrlMappingId}", id);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized UrlMapping Delete Attempt {UrlMappingId}", id);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
Oqtane.Server/Controllers/VisitorController.cs
Normal file
46
Oqtane.Server/Controllers/VisitorController.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using System.Net;
|
||||
using System;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
[Route(ControllerRoutes.ApiRoute)]
|
||||
public class VisitorController : Controller
|
||||
{
|
||||
private readonly IVisitorRepository _visitors;
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
|
||||
public VisitorController(IVisitorRepository visitors, ILogManager logger, ITenantManager tenantManager)
|
||||
{
|
||||
_visitors = visitors;
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
}
|
||||
|
||||
// GET: api/<controller>?siteid=x&fromdate=y
|
||||
[HttpGet]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public IEnumerable<Visitor> Get(string siteid, string fromdate)
|
||||
{
|
||||
int SiteId;
|
||||
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
|
||||
{
|
||||
return _visitors.GetVisitors(SiteId, DateTime.Parse(fromdate));
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Visitor Get Attempt {SiteId}", siteid);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -98,6 +98,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddTransient<ISqlRepository, SqlRepository>();
|
||||
services.AddTransient<IUpgradeManager, UpgradeManager>();
|
||||
services.AddTransient<ILanguageRepository, LanguageRepository>();
|
||||
services.AddTransient<IVisitorRepository, VisitorRepository>();
|
||||
services.AddTransient<IUrlMappingRepository, UrlMappingRepository>();
|
||||
// obsolete - replaced by ITenantManager
|
||||
services.AddTransient<ITenantResolver, TenantResolver>();
|
||||
|
||||
|
@ -582,12 +582,19 @@ namespace Oqtane.Infrastructure
|
||||
TenantId = tenant.TenantId,
|
||||
Name = install.SiteName,
|
||||
LogoFileId = null,
|
||||
FaviconFileId = null,
|
||||
PwaIsEnabled = false,
|
||||
PwaAppIconFileId = null,
|
||||
PwaSplashIconFileId = null,
|
||||
AllowRegistration = false,
|
||||
CaptureBrokenUrls = true,
|
||||
VisitorTracking = true,
|
||||
DefaultThemeType = (!string.IsNullOrEmpty(install.DefaultTheme)) ? install.DefaultTheme : Constants.DefaultTheme,
|
||||
DefaultContainerType = (!string.IsNullOrEmpty(install.DefaultContainer)) ? install.DefaultContainer : Constants.DefaultContainer,
|
||||
AdminContainerType = (!string.IsNullOrEmpty(install.DefaultAdminContainer)) ? install.DefaultAdminContainer : Constants.DefaultAdminContainer,
|
||||
SiteTemplateType = install.SiteTemplate,
|
||||
Runtime = (!string.IsNullOrEmpty(install.Runtime)) ? install.Runtime : _configManager.GetSection("Runtime").Value,
|
||||
RenderMode = (!string.IsNullOrEmpty(install.RenderMode)) ? install.RenderMode : _configManager.GetSection("RenderMode").Value
|
||||
RenderMode = (!string.IsNullOrEmpty(install.RenderMode)) ? install.RenderMode : _configManager.GetSection("RenderMode").Value,
|
||||
};
|
||||
site = sites.AddSite(site);
|
||||
|
||||
|
@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json;
|
||||
using Oqtane.Extensions;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Shared;
|
||||
@ -37,9 +38,6 @@ namespace Oqtane.Infrastructure
|
||||
|
||||
switch (version)
|
||||
{
|
||||
case "1.0.0":
|
||||
Upgrade_1_0_0(tenant, scope);
|
||||
break;
|
||||
case "2.0.2":
|
||||
Upgrade_2_0_2(tenant, scope);
|
||||
break;
|
||||
@ -49,61 +47,13 @@ namespace Oqtane.Infrastructure
|
||||
case "2.2.0":
|
||||
Upgrade_2_2_0(tenant, scope);
|
||||
break;
|
||||
case "3.0.1":
|
||||
Upgrade_3_0_1(tenant, scope);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// **Note: this code is commented out on purpose - it provides an example of how to programmatically add a page to all existing sites on upgrade
|
||||
/// </summary>
|
||||
/// <param name="tenant"></param>
|
||||
/// <param name="scope"></param>
|
||||
private void Upgrade_1_0_0(Tenant tenant, IServiceScope scope)
|
||||
{
|
||||
//var pageTemplates = new List<PageTemplate>();
|
||||
//
|
||||
//pageTemplates.Add(new PageTemplate
|
||||
//{
|
||||
// Name = "Test",
|
||||
// Parent = "",
|
||||
// Order = 1,
|
||||
// Path = "test",
|
||||
// Icon = Icons.Badge,
|
||||
// IsNavigation = true,
|
||||
// IsPersonalizable = false,
|
||||
// IsClickable = true,
|
||||
// PagePermissions = new List<Permission>
|
||||
// {
|
||||
// new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
// new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||
// new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
// }.EncodePermissions(),
|
||||
// PageTemplateModules = new List<PageTemplateModule>
|
||||
// {
|
||||
// new PageTemplateModule
|
||||
// {
|
||||
// ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Login.Index).ToModuleDefinitionName(), Title = "Test", Pane = "Content",
|
||||
// ModulePermissions = new List<Permission>
|
||||
// {
|
||||
// new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
// new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||
// new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
// }.EncodePermissions(),
|
||||
// Content = ""
|
||||
// }
|
||||
// }
|
||||
//});
|
||||
//
|
||||
//if (pageTemplates.Count != 0)
|
||||
//{
|
||||
// var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
|
||||
// foreach (Site site in sites.GetSites().ToList())
|
||||
// {
|
||||
// sites.CreatePages(site, pageTemplates);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
private void Upgrade_2_0_2(Tenant tenant, IServiceScope scope)
|
||||
{
|
||||
if (tenant.Name == TenantNames.Master)
|
||||
@ -163,5 +113,74 @@ namespace Oqtane.Infrastructure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Upgrade_3_0_1(Tenant tenant, IServiceScope scope)
|
||||
{
|
||||
var pageTemplates = new List<PageTemplate>();
|
||||
|
||||
pageTemplates.Add(new PageTemplate
|
||||
{
|
||||
Name = "Url Mappings",
|
||||
Parent = "Admin",
|
||||
Order = 33,
|
||||
Path = "admin/urlmappings",
|
||||
Icon = Icons.LinkBroken,
|
||||
IsNavigation = true,
|
||||
IsPersonalizable = false,
|
||||
PagePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
PageTemplateModules = new List<PageTemplateModule>
|
||||
{
|
||||
new PageTemplateModule
|
||||
{
|
||||
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UrlMappings.Index).ToModuleDefinitionName(), Title = "Url Mappings", Pane = PaneNames.Admin,
|
||||
ModulePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
Content = ""
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pageTemplates.Add(new PageTemplate
|
||||
{
|
||||
Name = "Visitor Management",
|
||||
Parent = "Admin",
|
||||
Order = 35,
|
||||
Path = "admin/visitors",
|
||||
Icon = Icons.Eye,
|
||||
IsNavigation = true,
|
||||
IsPersonalizable = false,
|
||||
PagePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
PageTemplateModules = new List<PageTemplateModule>
|
||||
{
|
||||
new PageTemplateModule
|
||||
{
|
||||
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Admin,
|
||||
ModulePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
Content = ""
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var sites = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
|
||||
foreach (Site site in sites.GetSites().ToList())
|
||||
{
|
||||
sites.CreatePages(site, pageTemplates);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,51 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
|
||||
namespace Oqtane.Migrations.EntityBuilders
|
||||
{
|
||||
public class UrlMappingEntityBuilder : BaseEntityBuilder<UrlMappingEntityBuilder>
|
||||
{
|
||||
private const string _entityTableName = "UrlMapping";
|
||||
private readonly PrimaryKey<UrlMappingEntityBuilder> _primaryKey = new("PK_UrlMapping", x => x.UrlMappingId);
|
||||
private readonly ForeignKey<UrlMappingEntityBuilder> _urlMappingForeignKey = new("FK_UrlMapping_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade);
|
||||
|
||||
public UrlMappingEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||
{
|
||||
EntityTableName = _entityTableName;
|
||||
PrimaryKey = _primaryKey;
|
||||
ForeignKeys.Add(_urlMappingForeignKey);
|
||||
}
|
||||
|
||||
protected override UrlMappingEntityBuilder BuildTable(ColumnsBuilder table)
|
||||
{
|
||||
UrlMappingId = AddAutoIncrementColumn(table, "UrlMappingId");
|
||||
SiteId = AddIntegerColumn(table, "SiteId");
|
||||
Url = AddStringColumn(table, "Url", 500);
|
||||
MappedUrl = AddStringColumn(table, "MappedUrl", 500);
|
||||
Requests = AddIntegerColumn(table, "Requests");
|
||||
CreatedOn = AddDateTimeColumn(table, "CreatedOn");
|
||||
RequestedOn = AddDateTimeColumn(table, "RequestedOn");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public OperationBuilder<AddColumnOperation> UrlMappingId { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> SiteId { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> Url { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> MappedUrl { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> Requests { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> CreatedOn { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> RequestedOn { get; private set; }
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
||||
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
|
||||
namespace Oqtane.Migrations.EntityBuilders
|
||||
{
|
||||
public class VisitorEntityBuilder : BaseEntityBuilder<VisitorEntityBuilder>
|
||||
{
|
||||
private const string _entityTableName = "Visitor";
|
||||
private readonly PrimaryKey<VisitorEntityBuilder> _primaryKey = new("PK_Visitor", x => x.VisitorId);
|
||||
private readonly ForeignKey<VisitorEntityBuilder> _visitorForeignKey = new("FK_Visitor_Site", x => x.SiteId, "Site", "SiteId", ReferentialAction.Cascade);
|
||||
|
||||
public VisitorEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
|
||||
{
|
||||
EntityTableName = _entityTableName;
|
||||
PrimaryKey = _primaryKey;
|
||||
ForeignKeys.Add(_visitorForeignKey);
|
||||
}
|
||||
|
||||
protected override VisitorEntityBuilder BuildTable(ColumnsBuilder table)
|
||||
{
|
||||
VisitorId = AddAutoIncrementColumn(table, "VisitorId");
|
||||
SiteId = AddIntegerColumn(table, "SiteId");
|
||||
UserId = AddIntegerColumn(table, "UserId", true);
|
||||
Visits = AddIntegerColumn(table, "Visits");
|
||||
IPAddress = AddStringColumn(table,"IPAddress", 50);
|
||||
UserAgent = AddStringColumn(table, "UserAgent", 256);
|
||||
Language = AddStringColumn(table, "Language", 50);
|
||||
CreatedOn = AddDateTimeColumn(table, "CreatedOn");
|
||||
VisitedOn = AddDateTimeColumn(table, "VisitedOn");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public OperationBuilder<AddColumnOperation> VisitorId { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> SiteId { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> UserId { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> Visits { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> IPAddress { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> UserAgent { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> Language { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> CreatedOn { get; private set; }
|
||||
|
||||
public OperationBuilder<AddColumnOperation> VisitedOn { get; private set; }
|
||||
}
|
||||
}
|
29
Oqtane.Server/Migrations/Tenant/03000102_AddVisitorTable.cs
Normal file
29
Oqtane.Server/Migrations/Tenant/03000102_AddVisitorTable.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations.EntityBuilders;
|
||||
using Oqtane.Repository;
|
||||
|
||||
namespace Oqtane.Migrations.Tenant
|
||||
{
|
||||
[DbContext(typeof(TenantDBContext))]
|
||||
[Migration("Tenant.03.00.01.02")]
|
||||
public class AddVisitorTable : MultiDatabaseMigration
|
||||
{
|
||||
public AddVisitorTable(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var visitorEntityBuilder = new VisitorEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
visitorEntityBuilder.Create();
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var visitorEntityBuilder = new VisitorEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
visitorEntityBuilder.Drop();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations.EntityBuilders;
|
||||
using Oqtane.Repository;
|
||||
|
||||
namespace Oqtane.Migrations.Tenant
|
||||
{
|
||||
[DbContext(typeof(TenantDBContext))]
|
||||
[Migration("Tenant.03.00.01.03")]
|
||||
public class AddUrlMappingTable : MultiDatabaseMigration
|
||||
{
|
||||
public AddUrlMappingTable(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var urlMappingEntityBuilder = new UrlMappingEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
urlMappingEntityBuilder.Create();
|
||||
urlMappingEntityBuilder.AddIndex("IX_UrlMapping", new[] { "SiteId", "Url" }, true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var urlMappingEntityBuilder = new UrlMappingEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
urlMappingEntityBuilder.Drop();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Oqtane.Databases.Interfaces;
|
||||
using Oqtane.Migrations.EntityBuilders;
|
||||
using Oqtane.Repository;
|
||||
|
||||
namespace Oqtane.Migrations.Tenant
|
||||
{
|
||||
[DbContext(typeof(TenantDBContext))]
|
||||
[Migration("Tenant.03.00.01.04")]
|
||||
public class AddSiteVisitorTracking : MultiDatabaseMigration
|
||||
{
|
||||
public AddSiteVisitorTracking(IDatabase database) : base(database)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
|
||||
siteEntityBuilder.AddBooleanColumn("VisitorTracking", true);
|
||||
siteEntityBuilder.UpdateColumn("VisitorTracking", "1", "bool", "");
|
||||
siteEntityBuilder.AddBooleanColumn("CaptureBrokenUrls", true);
|
||||
siteEntityBuilder.UpdateColumn("CaptureBrokenUrls", "1", "bool", "");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase);
|
||||
|
||||
siteEntityBuilder.DropColumn("VisitorTracking");
|
||||
siteEntityBuilder.DropColumn("CaptureBrokenUrls");
|
||||
}
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@
|
||||
<body>
|
||||
@(Html.AntiForgeryToken())
|
||||
<app>
|
||||
<component type="typeof(Oqtane.App)" render-mode="@Model.RenderMode" param-AntiForgeryToken="@Model.AntiForgeryToken" param-Runtime="@Model.Runtime" param-RenderMode="@Model.RenderMode.ToString()" />
|
||||
<component type="typeof(Oqtane.App)" render-mode="@Model.RenderMode" param-AntiForgeryToken="@Model.AntiForgeryToken" param-Runtime="@Model.Runtime" param-RenderMode="@Model.RenderMode.ToString()" param-VisitorId="@Model.VisitorId" />
|
||||
</app>
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
|
@ -14,6 +14,10 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Antiforgery;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Oqtane.Pages
|
||||
{
|
||||
@ -26,8 +30,10 @@ namespace Oqtane.Pages
|
||||
private readonly IAntiforgery _antiforgery;
|
||||
private readonly ISiteRepository _sites;
|
||||
private readonly IPageRepository _pages;
|
||||
private readonly IUrlMappingRepository _urlMappings;
|
||||
private readonly IVisitorRepository _visitors;
|
||||
|
||||
public HostModel(IConfiguration configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, ISiteRepository sites, IPageRepository pages)
|
||||
public HostModel(IConfiguration configuration, ITenantManager tenantManager, ILocalizationManager localizationManager, ILanguageRepository languages, IAntiforgery antiforgery, ISiteRepository sites, IPageRepository pages, IUrlMappingRepository urlMappings, IVisitorRepository visitors)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_tenantManager = tenantManager;
|
||||
@ -36,11 +42,14 @@ namespace Oqtane.Pages
|
||||
_antiforgery = antiforgery;
|
||||
_sites = sites;
|
||||
_pages = pages;
|
||||
_urlMappings = urlMappings;
|
||||
_visitors = visitors;
|
||||
}
|
||||
|
||||
public string AntiForgeryToken = "";
|
||||
public string Runtime = "Server";
|
||||
public RenderMode RenderMode = RenderMode.Server;
|
||||
public int VisitorId = -1;
|
||||
public string HeadResources = "";
|
||||
public string BodyResources = "";
|
||||
public string Title = "";
|
||||
@ -48,7 +57,7 @@ namespace Oqtane.Pages
|
||||
public string PWAScript = "";
|
||||
public string ThemeType = "";
|
||||
|
||||
public void OnGet()
|
||||
public IActionResult OnGet()
|
||||
{
|
||||
AntiForgeryToken = _antiforgery.GetAndStoreTokens(HttpContext).RequestToken;
|
||||
|
||||
@ -92,6 +101,11 @@ namespace Oqtane.Pages
|
||||
Title = site.Name;
|
||||
ThemeType = site.DefaultThemeType;
|
||||
|
||||
if (site.VisitorTracking)
|
||||
{
|
||||
TrackVisitor(site.SiteId);
|
||||
}
|
||||
|
||||
var page = _pages.GetPage(route.PagePath, site.SiteId);
|
||||
if (page != null)
|
||||
{
|
||||
@ -111,6 +125,37 @@ namespace Oqtane.Pages
|
||||
ThemeType = page.ThemeType;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// page does not exist
|
||||
var url = route.SiteUrl + "/" + route.PagePath;
|
||||
var urlMapping = _urlMappings.GetUrlMapping(site.SiteId, url);
|
||||
if (urlMapping == null)
|
||||
{
|
||||
if (site.CaptureBrokenUrls)
|
||||
{
|
||||
urlMapping = new UrlMapping();
|
||||
urlMapping.SiteId = site.SiteId;
|
||||
urlMapping.Url = url;
|
||||
urlMapping.MappedUrl = "";
|
||||
urlMapping.Requests = 1;
|
||||
urlMapping.CreatedOn = DateTime.UtcNow;
|
||||
urlMapping.RequestedOn = DateTime.UtcNow;
|
||||
_urlMappings.AddUrlMapping(urlMapping);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
urlMapping.Requests += 1;
|
||||
urlMapping.RequestedOn = DateTime.UtcNow;
|
||||
_urlMappings.UpdateUrlMapping(urlMapping);
|
||||
|
||||
if (!string.IsNullOrEmpty(urlMapping.MappedUrl))
|
||||
{
|
||||
return RedirectPermanent(urlMapping.MappedUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// include global resources
|
||||
@ -139,6 +184,64 @@ namespace Oqtane.Pages
|
||||
}
|
||||
}
|
||||
}
|
||||
return Page();
|
||||
}
|
||||
|
||||
private void TrackVisitor(int SiteId)
|
||||
{
|
||||
var VisitorCookie = "APP_VISITOR_" + SiteId.ToString();
|
||||
if (!int.TryParse(Request.Cookies[VisitorCookie], out VisitorId))
|
||||
{
|
||||
var visitor = new Visitor();
|
||||
visitor.SiteId = SiteId;
|
||||
visitor.IPAddress = HttpContext.Connection.RemoteIpAddress.ToString();
|
||||
visitor.UserAgent = Request.Headers[HeaderNames.UserAgent];
|
||||
visitor.Language = Request.Headers[HeaderNames.AcceptLanguage];
|
||||
if (visitor.Language.Contains(","))
|
||||
{
|
||||
visitor.Language = visitor.Language.Substring(0, visitor.Language.IndexOf(","));
|
||||
}
|
||||
visitor.UserId = null;
|
||||
visitor.Visits = 1;
|
||||
visitor.CreatedOn = DateTime.UtcNow;
|
||||
visitor.VisitedOn = DateTime.UtcNow;
|
||||
visitor = _visitors.AddVisitor(visitor);
|
||||
|
||||
Response.Cookies.Append(
|
||||
VisitorCookie,
|
||||
visitor.VisitorId.ToString(),
|
||||
new CookieOptions()
|
||||
{
|
||||
Expires = DateTimeOffset.UtcNow.AddYears(1),
|
||||
IsEssential = true
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
var visitor = _visitors.GetVisitor(VisitorId);
|
||||
if (visitor != null)
|
||||
{
|
||||
visitor.IPAddress = HttpContext.Connection.RemoteIpAddress.ToString();
|
||||
visitor.UserAgent = Request.Headers[HeaderNames.UserAgent];
|
||||
visitor.Language = Request.Headers[HeaderNames.AcceptLanguage];
|
||||
if (visitor.Language.Contains(","))
|
||||
{
|
||||
visitor.Language = visitor.Language.Substring(0, visitor.Language.IndexOf(","));
|
||||
}
|
||||
if (User.HasClaim(item => item.Type == ClaimTypes.PrimarySid))
|
||||
{
|
||||
visitor.UserId = int.Parse(User.Claims.First(item => item.Type == ClaimTypes.PrimarySid).Value);
|
||||
}
|
||||
visitor.Visits += 1;
|
||||
visitor.VisitedOn = DateTime.UtcNow;
|
||||
_visitors.UpdateVisitor(visitor);
|
||||
}
|
||||
else
|
||||
{
|
||||
Response.Cookies.Delete(VisitorCookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string CreatePWAScript(Alias alias, Site site, Route route)
|
||||
|
@ -29,5 +29,7 @@ namespace Oqtane.Repository
|
||||
public virtual DbSet<Folder> Folder { get; set; }
|
||||
public virtual DbSet<File> File { get; set; }
|
||||
public virtual DbSet<Language> Language { get; set; }
|
||||
public virtual DbSet<Visitor> Visitor { get; set; }
|
||||
public virtual DbSet<UrlMapping> UrlMapping { get; set; }
|
||||
}
|
||||
}
|
||||
|
17
Oqtane.Server/Repository/Interfaces/IUrlMappingRepository.cs
Normal file
17
Oqtane.Server/Repository/Interfaces/IUrlMappingRepository.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
public interface IUrlMappingRepository
|
||||
{
|
||||
IEnumerable<UrlMapping> GetUrlMappings(int siteId, bool isMapped);
|
||||
UrlMapping AddUrlMapping(UrlMapping urlMapping);
|
||||
UrlMapping UpdateUrlMapping(UrlMapping urlMapping);
|
||||
UrlMapping GetUrlMapping(int urlMappingId);
|
||||
UrlMapping GetUrlMapping(int urlMappingId, bool tracking);
|
||||
UrlMapping GetUrlMapping(int siteId, string url);
|
||||
void DeleteUrlMapping(int urlMappingId);
|
||||
}
|
||||
}
|
15
Oqtane.Server/Repository/Interfaces/IVisitorRepository.cs
Normal file
15
Oqtane.Server/Repository/Interfaces/IVisitorRepository.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
public interface IVisitorRepository
|
||||
{
|
||||
IEnumerable<Visitor> GetVisitors(int siteId, DateTime fromDate);
|
||||
Visitor AddVisitor(Visitor visitor);
|
||||
Visitor UpdateVisitor(Visitor visitor);
|
||||
Visitor GetVisitor(int visitorId);
|
||||
void DeleteVisitor(int visitorId);
|
||||
}
|
||||
}
|
@ -615,13 +615,70 @@ namespace Oqtane.Repository
|
||||
}
|
||||
}
|
||||
});
|
||||
pageTemplates.Add(new PageTemplate
|
||||
{
|
||||
Name = "Url Mappings",
|
||||
Parent = "Admin",
|
||||
Order = 15,
|
||||
Path = "admin/urlmappings",
|
||||
Icon = Icons.LinkBroken,
|
||||
IsNavigation = true,
|
||||
IsPersonalizable = false,
|
||||
PagePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
PageTemplateModules = new List<PageTemplateModule>
|
||||
{
|
||||
new PageTemplateModule
|
||||
{
|
||||
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.UrlMappings.Index).ToModuleDefinitionName(), Title = "Url Mappings", Pane = PaneNames.Admin,
|
||||
ModulePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
Content = ""
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
pageTemplates.Add(new PageTemplate
|
||||
{
|
||||
Name = "Visitor Management",
|
||||
Parent = "Admin",
|
||||
Order = 17,
|
||||
Path = "admin/visitors",
|
||||
Icon = Icons.Eye,
|
||||
IsNavigation = true,
|
||||
IsPersonalizable = false,
|
||||
PagePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
PageTemplateModules = new List<PageTemplateModule>
|
||||
{
|
||||
new PageTemplateModule
|
||||
{
|
||||
ModuleDefinitionName = typeof(Oqtane.Modules.Admin.Visitors.Index).ToModuleDefinitionName(), Title = "Visitor Management", Pane = PaneNames.Admin,
|
||||
ModulePermissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.View, RoleNames.Admin, true),
|
||||
new Permission(PermissionNames.Edit, RoleNames.Admin, true)
|
||||
}.EncodePermissions(),
|
||||
Content = ""
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// host pages
|
||||
pageTemplates.Add(new PageTemplate
|
||||
{
|
||||
Name = "Event Log",
|
||||
Parent = "Admin",
|
||||
Order = 15,
|
||||
Order = 19,
|
||||
Path = "admin/log",
|
||||
Icon = Icons.MagnifyingGlass,
|
||||
IsNavigation = false,
|
||||
@ -649,7 +706,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "Site Management",
|
||||
Parent = "Admin",
|
||||
Order = 17,
|
||||
Order = 21,
|
||||
Path = "admin/sites",
|
||||
Icon = Icons.Globe,
|
||||
IsNavigation = false,
|
||||
@ -677,7 +734,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "Module Management",
|
||||
Parent = "Admin",
|
||||
Order = 19,
|
||||
Order = 23,
|
||||
Path = "admin/modules",
|
||||
Icon = Icons.Browser,
|
||||
IsNavigation = false,
|
||||
@ -705,7 +762,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "Theme Management",
|
||||
Parent = "Admin",
|
||||
Order = 21,
|
||||
Order = 25,
|
||||
Path = "admin/themes",
|
||||
Icon = Icons.Brush,
|
||||
IsNavigation = false,
|
||||
@ -733,7 +790,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "Language Management",
|
||||
Parent = "Admin",
|
||||
Order = 23,
|
||||
Order = 27,
|
||||
Path = "admin/languages",
|
||||
Icon = Icons.Text,
|
||||
IsNavigation = false,
|
||||
@ -765,7 +822,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "Scheduled Jobs",
|
||||
Parent = "Admin",
|
||||
Order = 25,
|
||||
Order = 29,
|
||||
Path = "admin/jobs",
|
||||
Icon = Icons.Timer,
|
||||
IsNavigation = false,
|
||||
@ -793,7 +850,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "Sql Management",
|
||||
Parent = "Admin",
|
||||
Order = 27,
|
||||
Order = 31,
|
||||
Path = "admin/sql",
|
||||
Icon = Icons.Spreadsheet,
|
||||
IsNavigation = false,
|
||||
@ -821,7 +878,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "System Info",
|
||||
Parent = "Admin",
|
||||
Order = 29,
|
||||
Order = 33,
|
||||
Path = "admin/system",
|
||||
Icon = Icons.MedicalCross,
|
||||
IsNavigation = false,
|
||||
@ -849,7 +906,7 @@ namespace Oqtane.Repository
|
||||
{
|
||||
Name = "System Update",
|
||||
Parent = "Admin",
|
||||
Order = 31,
|
||||
Order = 35,
|
||||
Path = "admin/update",
|
||||
Icon = Icons.Aperture,
|
||||
IsNavigation = false,
|
||||
|
73
Oqtane.Server/Repository/UrlMappingRepository.cs
Normal file
73
Oqtane.Server/Repository/UrlMappingRepository.cs
Normal file
@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
public class UrlMappingRepository : IUrlMappingRepository
|
||||
{
|
||||
private TenantDBContext _db;
|
||||
|
||||
public UrlMappingRepository(TenantDBContext context)
|
||||
{
|
||||
_db = context;
|
||||
}
|
||||
|
||||
public IEnumerable<UrlMapping> GetUrlMappings(int siteId, bool isMapped)
|
||||
{
|
||||
if (isMapped)
|
||||
{
|
||||
return _db.UrlMapping.Where(item => item.SiteId == siteId && !string.IsNullOrEmpty(item.MappedUrl)).Take(200);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _db.UrlMapping.Where(item => item.SiteId == siteId && string.IsNullOrEmpty(item.MappedUrl)).Take(200);
|
||||
}
|
||||
}
|
||||
|
||||
public UrlMapping AddUrlMapping(UrlMapping urlMapping)
|
||||
{
|
||||
_db.UrlMapping.Add(urlMapping);
|
||||
_db.SaveChanges();
|
||||
return urlMapping;
|
||||
}
|
||||
|
||||
public UrlMapping UpdateUrlMapping(UrlMapping urlMapping)
|
||||
{
|
||||
_db.Entry(urlMapping).State = EntityState.Modified;
|
||||
_db.SaveChanges();
|
||||
return urlMapping;
|
||||
}
|
||||
|
||||
public UrlMapping GetUrlMapping(int urlMappingId)
|
||||
{
|
||||
return GetUrlMapping(urlMappingId, true);
|
||||
}
|
||||
|
||||
public UrlMapping GetUrlMapping(int urlMappingId, bool tracking)
|
||||
{
|
||||
if (tracking)
|
||||
{
|
||||
return _db.UrlMapping.Find(urlMappingId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return _db.UrlMapping.AsNoTracking().FirstOrDefault(item => item.UrlMappingId == urlMappingId);
|
||||
}
|
||||
}
|
||||
|
||||
public UrlMapping GetUrlMapping(int siteId, string url)
|
||||
{
|
||||
return _db.UrlMapping.Where(item => item.SiteId == siteId && item.Url == url).FirstOrDefault();
|
||||
}
|
||||
|
||||
public void DeleteUrlMapping(int urlMappingId)
|
||||
{
|
||||
UrlMapping urlMapping = _db.UrlMapping.Find(urlMappingId);
|
||||
_db.UrlMapping.Remove(urlMapping);
|
||||
_db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
51
Oqtane.Server/Repository/VisitorRepository.cs
Normal file
51
Oqtane.Server/Repository/VisitorRepository.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Oqtane.Models;
|
||||
|
||||
namespace Oqtane.Repository
|
||||
{
|
||||
public class VisitorRepository : IVisitorRepository
|
||||
{
|
||||
private TenantDBContext _db;
|
||||
|
||||
public VisitorRepository(TenantDBContext context)
|
||||
{
|
||||
_db = context;
|
||||
}
|
||||
|
||||
public IEnumerable<Visitor> GetVisitors(int siteId, DateTime fromDate)
|
||||
{
|
||||
return _db.Visitor.AsNoTracking()
|
||||
.Include(item => item.User) // eager load users
|
||||
.Where(item => item.SiteId == siteId && item.VisitedOn >= fromDate);
|
||||
}
|
||||
|
||||
public Visitor AddVisitor(Visitor visitor)
|
||||
{
|
||||
_db.Visitor.Add(visitor);
|
||||
_db.SaveChanges();
|
||||
return visitor;
|
||||
}
|
||||
|
||||
public Visitor UpdateVisitor(Visitor visitor)
|
||||
{
|
||||
_db.Entry(visitor).State = EntityState.Modified;
|
||||
_db.SaveChanges();
|
||||
return visitor;
|
||||
}
|
||||
|
||||
public Visitor GetVisitor(int visitorId)
|
||||
{
|
||||
return _db.Visitor.Find(visitorId);
|
||||
}
|
||||
|
||||
public void DeleteVisitor(int visitorId)
|
||||
{
|
||||
Visitor visitor = _db.Visitor.Find(visitorId);
|
||||
_db.Visitor.Remove(visitor);
|
||||
_db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user