Compare commits
5 commits
bff4b2675c
...
ea21d62c03
Author | SHA1 | Date | |
---|---|---|---|
snow flurry | ea21d62c03 | ||
snow flurry | 4268e4d7d9 | ||
snow flurry | 69c9a0333c | ||
snow flurry | 060b4ee1a1 | ||
snow flurry | fa882a1f4a |
4
.gitmodules
vendored
Normal file
4
.gitmodules
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[submodule "external/AsterNET"]
|
||||||
|
path = external/AsterNET
|
||||||
|
url = https://git.2ki.xyz/snow/AsterNET.git
|
||||||
|
branch = master
|
7
PhoneToolMX.Models/AllowNullAttribute.cs
Normal file
7
PhoneToolMX.Models/AllowNullAttribute.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace PhoneToolMX.Models
|
||||||
|
{
|
||||||
|
public class AllowNullAttribute : Attribute
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,20 @@ namespace PhoneToolMX.Data {
|
||||||
var entry = await AddAsync(entity);
|
var entry = await AddAsync(entity);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a DbSet with all required relationships
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TEntity">IOwnedModel</typeparam>
|
||||||
|
/// <returns>DbSet<TEntity></returns>
|
||||||
|
private IQueryable<TEntity> GetFullSet<TEntity>() where TEntity: class, IModel
|
||||||
|
{
|
||||||
|
var set = Set<TEntity>().AsQueryable();
|
||||||
|
return typeof(TEntity).GetProperties()
|
||||||
|
.Where(p => p.GetCustomAttributes(typeof(AlwaysIncludeAttribute), true)
|
||||||
|
.Length != 0)
|
||||||
|
.Aggregate(set, (current, prop) => current.Include(prop.Name));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all entities of a certain model owned by the <see cref="User"/>.
|
/// Gets all entities of a certain model owned by the <see cref="User"/>.
|
||||||
|
@ -48,14 +62,11 @@ namespace PhoneToolMX.Data {
|
||||||
public ICollection<TEntity> GetOwned<TEntity>(User owner) where TEntity : class, IOwnedModel
|
public ICollection<TEntity> GetOwned<TEntity>(User owner) where TEntity : class, IOwnedModel
|
||||||
{
|
{
|
||||||
if (owner == null) return null;
|
if (owner == null) return null;
|
||||||
var entity = Set<TEntity>().Where(x => x.Owners.Any(o => o.Id == owner.Id));
|
var entity = GetFullSet<TEntity>().Where(x => x.Owners.Any(o => o.Id == owner.Id));
|
||||||
// eager load all w/ AlwaysInclude
|
// eager load all w/ AlwaysInclude
|
||||||
entity = typeof(TEntity).GetProperties()
|
|
||||||
.Where(p => p.GetCustomAttributes(typeof(AlwaysIncludeAttribute), true)
|
|
||||||
.Length != 0)
|
|
||||||
.Aggregate(entity, (current, prop) => current.Include(prop.Name));
|
|
||||||
return entity.ToList();
|
return entity.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a model entity by its Id
|
/// Gets a model entity by its Id
|
||||||
|
@ -67,7 +78,7 @@ namespace PhoneToolMX.Data {
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public TEntity GetEntityById<TEntity>(int? id) where TEntity : class, IModel
|
public TEntity GetEntityById<TEntity>(int? id) where TEntity : class, IModel
|
||||||
{
|
{
|
||||||
return id == null ? null : Set<TEntity>().FirstOrDefault(o => o.Id == id);
|
return id == null ? null : GetFullSet<TEntity>().FirstOrDefault(o => o.Id == id);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -79,6 +90,7 @@ namespace PhoneToolMX.Data {
|
||||||
ext.HasKey(x => x.Id);
|
ext.HasKey(x => x.Id);
|
||||||
ext.Property(x => x.Id).UseIdentityColumn();
|
ext.Property(x => x.Id).UseIdentityColumn();
|
||||||
ext.Property(x => x.ExtId).HasComputedColumnSql("\"Id\" + 1000", stored: true);
|
ext.Property(x => x.ExtId).HasComputedColumnSql("\"Id\" + 1000", stored: true);
|
||||||
|
// Randomly generates a 24-char password
|
||||||
ext
|
ext
|
||||||
.Property(p => p.Password)
|
.Property(p => p.Password)
|
||||||
.HasDefaultValueSql("encode(gen_random_bytes(18), 'base64')");
|
.HasDefaultValueSql("encode(gen_random_bytes(18), 'base64')");
|
||||||
|
@ -89,7 +101,7 @@ namespace PhoneToolMX.Data {
|
||||||
.WithMany(x => x.Phones);
|
.WithMany(x => x.Phones);
|
||||||
phone.HasKey(p => p.Id);
|
phone.HasKey(p => p.Id);
|
||||||
phone.Property(p => p.Id).UseIdentityColumn();
|
phone.Property(p => p.Id).UseIdentityColumn();
|
||||||
// Randomly generates a 24-char password
|
phone.HasOne(p => p.PrimaryExtension).WithMany();
|
||||||
|
|
||||||
// Wallpapers, ringtones, etc
|
// Wallpapers, ringtones, etc
|
||||||
var cd = modelBuilder.Entity<CustomData>();
|
var cd = modelBuilder.Entity<CustomData>();
|
||||||
|
|
361
PhoneToolMX.Models/Migrations/20231020205952_PrimaryExtension.Designer.cs
generated
Normal file
361
PhoneToolMX.Models/Migrations/20231020205952_PrimaryExtension.Designer.cs
generated
Normal file
|
@ -0,0 +1,361 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using PhoneToolMX.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PhoneToolMX.Models.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(PTMXContext))]
|
||||||
|
[Migration("20231020205952_PrimaryExtension")]
|
||||||
|
partial class PrimaryExtension
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "6.0.23")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("ExtensionPhone", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ExtensionsId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("PhonesId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("ExtensionsId", "PhonesId");
|
||||||
|
|
||||||
|
b.HasIndex("PhonesId");
|
||||||
|
|
||||||
|
b.ToTable("ExtensionPhone");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ExtensionUser", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ExtensionsId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("OwnersId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("ExtensionsId", "OwnersId");
|
||||||
|
|
||||||
|
b.HasIndex("OwnersId");
|
||||||
|
|
||||||
|
b.ToTable("ExtensionUser");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.CustomData", b =>
|
||||||
|
{
|
||||||
|
b.Property<int?>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||||
|
|
||||||
|
b.Property<byte[]>("Data")
|
||||||
|
.HasColumnType("bytea");
|
||||||
|
|
||||||
|
b.Property<int>("DataType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("FriendlyName")
|
||||||
|
.HasMaxLength(16)
|
||||||
|
.HasColumnType("character varying(16)");
|
||||||
|
|
||||||
|
b.Property<int?>("PhoneId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<long>("Size")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PhoneId");
|
||||||
|
|
||||||
|
b.ToTable("CustomData");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.Extension", b =>
|
||||||
|
{
|
||||||
|
b.Property<int?>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("DirectoryName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("ExtId")
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasComputedColumnSql("\"Id\" + 1000", true);
|
||||||
|
|
||||||
|
b.Property<int?>("HoldMusicId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<bool>("Listed")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValueSql("encode(gen_random_bytes(18), 'base64')");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("HoldMusicId");
|
||||||
|
|
||||||
|
b.ToTable("Extensions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.Phone", b =>
|
||||||
|
{
|
||||||
|
b.Property<int?>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||||
|
|
||||||
|
b.Property<int?>("BackgroundId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("FriendlyName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<PhysicalAddress>("MacAddress")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("macaddr");
|
||||||
|
|
||||||
|
b.Property<int>("ModelId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int?>("PrimaryExtension")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BackgroundId");
|
||||||
|
|
||||||
|
b.HasIndex("ModelId");
|
||||||
|
|
||||||
|
b.ToTable("Phones");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.PhoneModel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int?>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("MaxExtensions")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("ModelName")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("PreVvxPolycom")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("PhoneModels");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 0,
|
||||||
|
MaxExtensions = 6L,
|
||||||
|
ModelName = "Polycom VVX300/310",
|
||||||
|
PreVvxPolycom = false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.Role", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Role");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("AccessFailedCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("EmailConfirmed")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<bool>("LockoutEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedEmail")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedUserName")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("PhoneNumberConfirmed")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("SecurityStamp")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("TwoFactorEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneUser", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("OwnersId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("PhonesId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("OwnersId", "PhonesId");
|
||||||
|
|
||||||
|
b.HasIndex("PhonesId");
|
||||||
|
|
||||||
|
b.ToTable("PhoneUser");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ExtensionPhone", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PhoneToolMX.Models.Extension", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ExtensionsId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("PhoneToolMX.Models.Phone", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PhonesId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ExtensionUser", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PhoneToolMX.Models.Extension", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ExtensionsId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("PhoneToolMX.Models.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OwnersId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.CustomData", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PhoneToolMX.Models.Phone", null)
|
||||||
|
.WithMany("Ringtones")
|
||||||
|
.HasForeignKey("PhoneId");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.Extension", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PhoneToolMX.Models.CustomData", "HoldMusic")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("HoldMusicId");
|
||||||
|
|
||||||
|
b.Navigation("HoldMusic");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.Phone", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PhoneToolMX.Models.CustomData", "Background")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BackgroundId");
|
||||||
|
|
||||||
|
b.HasOne("PhoneToolMX.Models.PhoneModel", "Model")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ModelId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Background");
|
||||||
|
|
||||||
|
b.Navigation("Model");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneUser", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PhoneToolMX.Models.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OwnersId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("PhoneToolMX.Models.Phone", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PhonesId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.Phone", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Ringtones");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PhoneToolMX.Models.Migrations
|
||||||
|
{
|
||||||
|
public partial class PrimaryExtension : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "PrimaryExtension",
|
||||||
|
table: "Phones",
|
||||||
|
type: "integer",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "PrimaryExtension",
|
||||||
|
table: "Phones");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
369
PhoneToolMX.Models/Migrations/20231020213049_PrimaryExtensionAsExtension.Designer.cs
generated
Normal file
369
PhoneToolMX.Models/Migrations/20231020213049_PrimaryExtensionAsExtension.Designer.cs
generated
Normal file
|
@ -0,0 +1,369 @@
|
||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
|
using PhoneToolMX.Data;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PhoneToolMX.Models.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(PTMXContext))]
|
||||||
|
[Migration("20231020213049_PrimaryExtensionAsExtension")]
|
||||||
|
partial class PrimaryExtensionAsExtension
|
||||||
|
{
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder
|
||||||
|
.HasAnnotation("ProductVersion", "6.0.23")
|
||||||
|
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||||
|
|
||||||
|
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||||
|
|
||||||
|
modelBuilder.Entity("ExtensionPhone", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ExtensionsId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int>("PhonesId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("ExtensionsId", "PhonesId");
|
||||||
|
|
||||||
|
b.HasIndex("PhonesId");
|
||||||
|
|
||||||
|
b.ToTable("ExtensionPhone");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ExtensionUser", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ExtensionsId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("OwnersId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("ExtensionsId", "OwnersId");
|
||||||
|
|
||||||
|
b.HasIndex("OwnersId");
|
||||||
|
|
||||||
|
b.ToTable("ExtensionUser");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.CustomData", b =>
|
||||||
|
{
|
||||||
|
b.Property<int?>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||||
|
|
||||||
|
b.Property<byte[]>("Data")
|
||||||
|
.HasColumnType("bytea");
|
||||||
|
|
||||||
|
b.Property<int>("DataType")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("FriendlyName")
|
||||||
|
.HasMaxLength(16)
|
||||||
|
.HasColumnType("character varying(16)");
|
||||||
|
|
||||||
|
b.Property<int?>("PhoneId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<long>("Size")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("PhoneId");
|
||||||
|
|
||||||
|
b.ToTable("CustomData");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.Extension", b =>
|
||||||
|
{
|
||||||
|
b.Property<int?>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||||
|
|
||||||
|
b.Property<string>("DirectoryName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("ExtId")
|
||||||
|
.ValueGeneratedOnAddOrUpdate()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasComputedColumnSql("\"Id\" + 1000", true);
|
||||||
|
|
||||||
|
b.Property<int?>("HoldMusicId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<bool>("Listed")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("Password")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasDefaultValueSql("encode(gen_random_bytes(18), 'base64')");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("HoldMusicId");
|
||||||
|
|
||||||
|
b.ToTable("Extensions");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.Phone", b =>
|
||||||
|
{
|
||||||
|
b.Property<int?>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||||
|
|
||||||
|
b.Property<int?>("BackgroundId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("FriendlyName")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<PhysicalAddress>("MacAddress")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("macaddr");
|
||||||
|
|
||||||
|
b.Property<int>("ModelId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int?>("PrimaryExtensionId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("BackgroundId");
|
||||||
|
|
||||||
|
b.HasIndex("ModelId");
|
||||||
|
|
||||||
|
b.HasIndex("PrimaryExtensionId");
|
||||||
|
|
||||||
|
b.ToTable("Phones");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.PhoneModel", b =>
|
||||||
|
{
|
||||||
|
b.Property<int?>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int?>("Id"));
|
||||||
|
|
||||||
|
b.Property<long>("MaxExtensions")
|
||||||
|
.HasColumnType("bigint");
|
||||||
|
|
||||||
|
b.Property<string>("ModelName")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("PreVvxPolycom")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("PhoneModels");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
Id = 0,
|
||||||
|
MaxExtensions = 6L,
|
||||||
|
ModelName = "Polycom VVX300/310",
|
||||||
|
PreVvxPolycom = false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.Role", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedName")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Role");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.User", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("Id")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("AccessFailedCount")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<string>("ConcurrencyStamp")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("Email")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("EmailConfirmed")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<bool>("LockoutEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||||
|
.HasColumnType("timestamp with time zone");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedEmail")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("NormalizedUserName")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PasswordHash")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<string>("PhoneNumber")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("PhoneNumberConfirmed")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("SecurityStamp")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<bool>("TwoFactorEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
b.Property<string>("UserName")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("Users");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneUser", b =>
|
||||||
|
{
|
||||||
|
b.Property<string>("OwnersId")
|
||||||
|
.HasColumnType("text");
|
||||||
|
|
||||||
|
b.Property<int>("PhonesId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.HasKey("OwnersId", "PhonesId");
|
||||||
|
|
||||||
|
b.HasIndex("PhonesId");
|
||||||
|
|
||||||
|
b.ToTable("PhoneUser");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ExtensionPhone", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PhoneToolMX.Models.Extension", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ExtensionsId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("PhoneToolMX.Models.Phone", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PhonesId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("ExtensionUser", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PhoneToolMX.Models.Extension", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ExtensionsId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("PhoneToolMX.Models.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OwnersId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.CustomData", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PhoneToolMX.Models.Phone", null)
|
||||||
|
.WithMany("Ringtones")
|
||||||
|
.HasForeignKey("PhoneId");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.Extension", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PhoneToolMX.Models.CustomData", "HoldMusic")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("HoldMusicId");
|
||||||
|
|
||||||
|
b.Navigation("HoldMusic");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.Phone", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PhoneToolMX.Models.CustomData", "Background")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("BackgroundId");
|
||||||
|
|
||||||
|
b.HasOne("PhoneToolMX.Models.PhoneModel", "Model")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ModelId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("PhoneToolMX.Models.Extension", "PrimaryExtension")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PrimaryExtensionId");
|
||||||
|
|
||||||
|
b.Navigation("Background");
|
||||||
|
|
||||||
|
b.Navigation("Model");
|
||||||
|
|
||||||
|
b.Navigation("PrimaryExtension");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneUser", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("PhoneToolMX.Models.User", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("OwnersId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("PhoneToolMX.Models.Phone", null)
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PhonesId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("PhoneToolMX.Models.Phone", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("Ringtones");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace PhoneToolMX.Models.Migrations
|
||||||
|
{
|
||||||
|
public partial class PrimaryExtensionAsExtension : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.RenameColumn(
|
||||||
|
name: "PrimaryExtension",
|
||||||
|
table: "Phones",
|
||||||
|
newName: "PrimaryExtensionId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_Phones_PrimaryExtensionId",
|
||||||
|
table: "Phones",
|
||||||
|
column: "PrimaryExtensionId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_Phones_Extensions_PrimaryExtensionId",
|
||||||
|
table: "Phones",
|
||||||
|
column: "PrimaryExtensionId",
|
||||||
|
principalTable: "Extensions",
|
||||||
|
principalColumn: "Id");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_Phones_Extensions_PrimaryExtensionId",
|
||||||
|
table: "Phones");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_Phones_PrimaryExtensionId",
|
||||||
|
table: "Phones");
|
||||||
|
|
||||||
|
migrationBuilder.RenameColumn(
|
||||||
|
name: "PrimaryExtensionId",
|
||||||
|
table: "Phones",
|
||||||
|
newName: "PrimaryExtension");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -141,12 +141,17 @@ namespace PhoneToolMX.Models.Migrations
|
||||||
b.Property<int>("ModelId")
|
b.Property<int>("ModelId")
|
||||||
.HasColumnType("integer");
|
.HasColumnType("integer");
|
||||||
|
|
||||||
|
b.Property<int?>("PrimaryExtensionId")
|
||||||
|
.HasColumnType("integer");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("BackgroundId");
|
b.HasIndex("BackgroundId");
|
||||||
|
|
||||||
b.HasIndex("ModelId");
|
b.HasIndex("ModelId");
|
||||||
|
|
||||||
|
b.HasIndex("PrimaryExtensionId");
|
||||||
|
|
||||||
b.ToTable("Phones");
|
b.ToTable("Phones");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -326,9 +331,15 @@ namespace PhoneToolMX.Models.Migrations
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("PhoneToolMX.Models.Extension", "PrimaryExtension")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PrimaryExtensionId");
|
||||||
|
|
||||||
b.Navigation("Background");
|
b.Navigation("Background");
|
||||||
|
|
||||||
b.Navigation("Model");
|
b.Navigation("Model");
|
||||||
|
|
||||||
|
b.Navigation("PrimaryExtension");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("PhoneUser", b =>
|
modelBuilder.Entity("PhoneUser", b =>
|
||||||
|
|
|
@ -12,6 +12,8 @@ namespace PhoneToolMX.Models
|
||||||
foreach (var prop in GetType().GetProperties().Where(p => p.CanWrite)) {
|
foreach (var prop in GetType().GetProperties().Where(p => p.CanWrite)) {
|
||||||
if (prop.GetValue(obj, null) is {} propVal) {
|
if (prop.GetValue(obj, null) is {} propVal) {
|
||||||
prop.SetValue(this, propVal, null);
|
prop.SetValue(this, propVal, null);
|
||||||
|
} else if (prop.GetCustomAttributes(typeof(AllowNullAttribute)).FirstOrDefault() != null) {
|
||||||
|
prop.SetValue(this, null, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,10 @@ namespace PhoneToolMX.Models {
|
||||||
|
|
||||||
[AlwaysInclude]
|
[AlwaysInclude]
|
||||||
public ICollection<Extension> Extensions { get; set; }
|
public ICollection<Extension> Extensions { get; set; }
|
||||||
|
|
||||||
public CustomData Background { get; set; }
|
public CustomData Background { get; set; }
|
||||||
public ICollection<CustomData> Ringtones { get; set; }
|
public ICollection<CustomData> Ringtones { get; set; }
|
||||||
|
|
||||||
|
public Extension PrimaryExtension { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -53,5 +53,7 @@ namespace PhoneToolMX.Models.ViewModels
|
||||||
HoldMusic = extEnt.HoldMusic?.Id,
|
HoldMusic = extEnt.HoldMusic?.Id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// TODO: fix hack
|
||||||
|
public string NotifyOnChange() => (ExtId == 0 ? Id + 1000 : ExtId).ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,12 @@ namespace PhoneToolMX.Models.ViewModels
|
||||||
public interface IViewModel
|
public interface IViewModel
|
||||||
{
|
{
|
||||||
public int? Id { get; set; }
|
public int? Id { get; set; }
|
||||||
|
|
||||||
public IOwnedModel ToEntity(PTMXContext ctx);
|
public IOwnedModel ToEntity(PTMXContext ctx);
|
||||||
public IOwnedModel ToEntity(PTMXContext ctx, IOwnedModel current);
|
public IOwnedModel ToEntity(PTMXContext ctx, IOwnedModel current);
|
||||||
|
|
||||||
public IViewModel FromEntity(IOwnedModel entity);
|
public IViewModel FromEntity(IOwnedModel entity);
|
||||||
|
|
||||||
|
public string NotifyOnChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
using PhoneToolMX.Data;
|
using PhoneToolMX.Data;
|
||||||
using PhoneToolMX.Models;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.Net.NetworkInformation;
|
using System.Net.NetworkInformation;
|
||||||
|
|
||||||
|
@ -10,7 +8,7 @@ namespace PhoneToolMX.Models.ViewModels
|
||||||
public class PhoneVM : IViewModel
|
public class PhoneVM : IViewModel
|
||||||
{
|
{
|
||||||
public int? Id { get; set; }
|
public int? Id { get; set; }
|
||||||
|
|
||||||
[Header("MAC Address")]
|
[Header("MAC Address")]
|
||||||
[Required]
|
[Required]
|
||||||
[RegularExpression("(?:[0-9a-fA-F]{2}[-:]?){6}", ErrorMessage = "Must be of the form XX:XX:XX:XX:XX:XX, XX-XX-XX-XX-XX-XX, or XXXXXXXXXXXX")]
|
[RegularExpression("(?:[0-9a-fA-F]{2}[-:]?){6}", ErrorMessage = "Must be of the form XX:XX:XX:XX:XX:XX, XX-XX-XX-XX-XX-XX, or XXXXXXXXXXXX")]
|
||||||
|
@ -25,18 +23,25 @@ namespace PhoneToolMX.Models.ViewModels
|
||||||
|
|
||||||
public ICollection<int?> Extensions { get; set; }
|
public ICollection<int?> Extensions { get; set; }
|
||||||
|
|
||||||
|
[AllowNull]
|
||||||
|
public int? PrimaryExtension { get; set; }
|
||||||
|
|
||||||
public int MaxExtensions { get; set; }
|
public int MaxExtensions { get; set; }
|
||||||
|
|
||||||
public IOwnedModel ToEntity(PTMXContext ctx)
|
public IOwnedModel ToEntity(PTMXContext ctx)
|
||||||
{
|
{
|
||||||
|
var exts = Extensions?.Select(x => ctx.Extensions.FirstOrDefault(e => e.Id == x)).ToList();
|
||||||
return new Phone
|
return new Phone
|
||||||
{
|
{
|
||||||
Id = Id,
|
Id = Id,
|
||||||
MacAddress = PhysicalAddress.Parse(MacAddress),
|
MacAddress = PhysicalAddress.Parse(MacAddress),
|
||||||
FriendlyName = FriendlyName,
|
FriendlyName = FriendlyName,
|
||||||
Model = ctx.PhoneModels.FirstOrDefault(p => p.Id == Model)!,
|
Model = ctx.PhoneModels.FirstOrDefault(p => p.Id == Model)!,
|
||||||
Extensions = Extensions?.Select(x => ctx.Extensions.FirstOrDefault(e => e.Id == x)).ToList(),
|
Extensions = exts,
|
||||||
Owners = new List<User>(),
|
Owners = new List<User>(),
|
||||||
|
PrimaryExtension = PrimaryExtension != null
|
||||||
|
? exts?.FirstOrDefault(x => x.Id == PrimaryExtension)
|
||||||
|
: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +63,13 @@ namespace PhoneToolMX.Models.ViewModels
|
||||||
Model = phoneEnt.Model!.Id,
|
Model = phoneEnt.Model!.Id,
|
||||||
Extensions = phoneEnt.Extensions?.Select(x => x.Id).ToList(),
|
Extensions = phoneEnt.Extensions?.Select(x => x.Id).ToList(),
|
||||||
MaxExtensions = (int)phoneEnt.Model!.MaxExtensions,
|
MaxExtensions = (int)phoneEnt.Model!.MaxExtensions,
|
||||||
|
PrimaryExtension = phoneEnt.PrimaryExtension?.Id,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// a bit of a hack, but we can just choose any extension that belongs to this phone
|
||||||
|
public string NotifyOnChange() => Extensions.Count > 0
|
||||||
|
? (Extensions.First() + 1000).ToString()
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
using AsterNET.Manager;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
@ -5,6 +6,7 @@ using PhoneToolMX.Data;
|
||||||
using PhoneToolMX.Helpers;
|
using PhoneToolMX.Helpers;
|
||||||
using PhoneToolMX.Models;
|
using PhoneToolMX.Models;
|
||||||
using PhoneToolMX.Models.ViewModels;
|
using PhoneToolMX.Models.ViewModels;
|
||||||
|
using PhoneToolMX.Services;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
@ -118,7 +120,7 @@ namespace PhoneToolMX.Controllers
|
||||||
|
|
||||||
SetMessage(
|
SetMessage(
|
||||||
FormMessageType.Success,
|
FormMessageType.Success,
|
||||||
$"{typeof(T).Name} {GetFriendlyName(new TViewModel().FromEntity(entity.Entity))} was created."
|
$"{typeof(T).Name} {GetFriendlyName(entity.Entity)} was created."
|
||||||
);
|
);
|
||||||
|
|
||||||
return RedirectToAction("Edit", new { id = entity.Entity.Id });
|
return RedirectToAction("Edit", new { id = entity.Entity.Id });
|
||||||
|
@ -170,9 +172,22 @@ namespace PhoneToolMX.Controllers
|
||||||
_context.Set<T>().Update(currentModel);
|
_context.Set<T>().Update(currentModel);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
// Try to notify the relevant endpoint to update its config
|
||||||
|
var amiStatus = "";
|
||||||
|
if (HttpContext.RequestServices.GetService<IAsteriskManager>() is {} ami) {
|
||||||
|
try { await ami.SendNotifyAsync(vm.NotifyOnChange());
|
||||||
|
amiStatus = "Your phone's configuration should be updated shortly.";
|
||||||
|
}
|
||||||
|
catch (BadResponseException e) { amiStatus = $"You'll need to manually update your phone settings (AMI Response: {e.Message})."; }
|
||||||
|
catch (ManagerException e) { amiStatus = $"You'll need to manually update your phone settings (AMI Client Error: {e.Message})."; }
|
||||||
|
catch (Exception e) {
|
||||||
|
amiStatus = $"You'll need to manually update your phone settings (Unknown Error: {e.Message}).";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SetMessage(
|
SetMessage(
|
||||||
FormMessageType.Success,
|
FormMessageType.Success,
|
||||||
$"{typeof(T).Name} {GetFriendlyName(vm)} was updated."
|
$"{typeof(T).Name} {GetFriendlyName(currentModel)} was updated.{" " + amiStatus}"
|
||||||
);
|
);
|
||||||
|
|
||||||
return RedirectToAction("Edit", new { id = entity.Id });
|
return RedirectToAction("Edit", new { id = entity.Id });
|
||||||
|
|
|
@ -17,8 +17,10 @@ namespace PhoneToolMX.Controllers
|
||||||
{
|
{
|
||||||
var myExts = _context.GetOwned<Extension>(await CurrentUser());
|
var myExts = _context.GetOwned<Extension>(await CurrentUser());
|
||||||
var phoneModels = _context.PhoneModels.ToList();
|
var phoneModels = _context.PhoneModels.ToList();
|
||||||
|
var selectedExts = pvm?.Extensions == null ? null : myExts.Where(x => pvm.Extensions.Contains(x.Id)).ToList();
|
||||||
ViewBag.MyExtensions = myExts;
|
ViewBag.MyExtensions = myExts;
|
||||||
ViewBag.SelectedExtensions = pvm?.Extensions == null ? null : myExts.Where(x => pvm.Extensions.Contains(x.Id)).ToList();
|
ViewBag.SelectedExtensions = selectedExts;
|
||||||
|
ViewBag.PrimaryExtension = selectedExts?.FirstOrDefault(x => x.Id == (pvm.PrimaryExtension ?? -1));
|
||||||
ViewBag.ModelNumbers = phoneModels;
|
ViewBag.ModelNumbers = phoneModels;
|
||||||
ViewBag.CurrentModel = pvm?.Model == null ? null : phoneModels.Where(m => m.Id == pvm.Model);
|
ViewBag.CurrentModel = pvm?.Model == null ? null : phoneModels.Where(m => m.Id == pvm.Model);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\external\AsterNET\Asterisk.2013\Asterisk.NET\AsterNET.csproj" />
|
||||||
<ProjectReference Include="..\PhoneToolMX.Models\PhoneToolMX.Models.csproj" />
|
<ProjectReference Include="..\PhoneToolMX.Models\PhoneToolMX.Models.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ using Microsoft.Extensions.Options;
|
||||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||||
using NuGet.Packaging;
|
using NuGet.Packaging;
|
||||||
using PhoneToolMX.Models;
|
using PhoneToolMX.Models;
|
||||||
|
using PhoneToolMX.Services;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
|
@ -39,11 +40,8 @@ builder.Services.AddIdentityCore<User>(opts =>
|
||||||
.AddUserManager<UserManager<User>>()
|
.AddUserManager<UserManager<User>>()
|
||||||
.AddEntityFrameworkStores<PTMXContext>();
|
.AddEntityFrameworkStores<PTMXContext>();
|
||||||
|
|
||||||
Console.WriteLine("Testing one two");
|
|
||||||
|
|
||||||
var proxyConfig = builder.Configuration.GetSection("Proxies");
|
var proxyConfig = builder.Configuration.GetSection("Proxies");
|
||||||
if (proxyConfig?.GetSection("TrustedProxies")?.Get<IList<string>>() is {} trustedProxies) {
|
if (proxyConfig?.GetSection("TrustedProxies")?.Get<IList<string>>() is {} trustedProxies) {
|
||||||
Console.WriteLine("Got trusted proxies!");
|
|
||||||
builder.Services.Configure<ForwardedHeadersOptions>(opts =>
|
builder.Services.Configure<ForwardedHeadersOptions>(opts =>
|
||||||
{
|
{
|
||||||
opts.KnownProxies.AddRange(trustedProxies.Select(IPAddress.Parse));
|
opts.KnownProxies.AddRange(trustedProxies.Select(IPAddress.Parse));
|
||||||
|
@ -115,6 +113,11 @@ builder.Services.AddAuthorization(opts =>
|
||||||
policy => policy.RequireRole("Administrator"));
|
policy => policy.RequireRole("Administrator"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// AMI for PJSIP notifications
|
||||||
|
if (builder.Configuration.GetSection("ami").Exists()) {
|
||||||
|
builder.Services.AddSingleton<IAsteriskManager, AsteriskManager>();
|
||||||
|
}
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
|
|
36
PhoneToolMX/Services/AsteriskManager.cs
Normal file
36
PhoneToolMX/Services/AsteriskManager.cs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
using System.Collections;
|
||||||
|
using System.Net.Security;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
using AsterNET.Manager;
|
||||||
|
|
||||||
|
namespace PhoneToolMX.Services
|
||||||
|
{
|
||||||
|
public class AsteriskManager : IAsteriskManager
|
||||||
|
{
|
||||||
|
private IConfigurationSection amiConf;
|
||||||
|
private ManagerConnection conn;
|
||||||
|
public AsteriskManager(IConfiguration config)
|
||||||
|
{
|
||||||
|
amiConf = config.GetRequiredSection("ami");
|
||||||
|
conn = new ManagerConnection(amiConf["host"],
|
||||||
|
int.Parse(amiConf["port"]),
|
||||||
|
amiConf["username"],
|
||||||
|
amiConf["secret"]);
|
||||||
|
conn.Login();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a <c>polycom-check-cfg</c> notification to the requested PJSIP endpoint.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoint">The PJSIP endpoint to notify, without the <c>PJSIP/</c> prefix.</param>
|
||||||
|
/// <exception cref="BadResponseException">If the request fails, this exception will be thrown.</exception>
|
||||||
|
public async Task SendNotifyAsync(string endpoint)
|
||||||
|
{
|
||||||
|
var response = await conn.SendActionAsync(new PJSIPNotifyAction(endpoint, "polycom-check-cfg"));
|
||||||
|
if (!response.IsSuccess()) {
|
||||||
|
throw new BadResponseException(response.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
PhoneToolMX/Services/BadResponseException.cs
Normal file
7
PhoneToolMX/Services/BadResponseException.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace PhoneToolMX.Services
|
||||||
|
{
|
||||||
|
public class BadResponseException : Exception
|
||||||
|
{
|
||||||
|
public BadResponseException(string message) : base(message) {}
|
||||||
|
}
|
||||||
|
}
|
7
PhoneToolMX/Services/IAsteriskManager.cs
Normal file
7
PhoneToolMX/Services/IAsteriskManager.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace PhoneToolMX.Services
|
||||||
|
{
|
||||||
|
public interface IAsteriskManager
|
||||||
|
{
|
||||||
|
public Task SendNotifyAsync(string endpoint);
|
||||||
|
}
|
||||||
|
}
|
25
PhoneToolMX/Services/PJSIPNotifyAction.cs
Normal file
25
PhoneToolMX/Services/PJSIPNotifyAction.cs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
namespace PhoneToolMX.Services
|
||||||
|
{
|
||||||
|
public class PJSIPNotifyAction : AsterNET.Manager.Action.ManagerAction
|
||||||
|
{
|
||||||
|
|
||||||
|
public override string Action => "PJSIPNotify";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The endpoint to which to send the NOTIFY.
|
||||||
|
/// </summary>
|
||||||
|
public string Endpoint { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The config section name from <c>pjsip_notify.conf</c> to use.
|
||||||
|
/// One of Option or Variable must be specified.
|
||||||
|
/// </summary>
|
||||||
|
public string Option { get; set; }
|
||||||
|
|
||||||
|
public PJSIPNotifyAction(string endpoint, string option)
|
||||||
|
{
|
||||||
|
Endpoint = endpoint;
|
||||||
|
Option = option;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,14 @@
|
||||||
<th>@Html.LabelFor(m => m.Extensions, "Extensions:")</th>
|
<th>@Html.LabelFor(m => m.Extensions, "Extensions:")</th>
|
||||||
<td>@Html.ListBoxFor(m => m.Extensions, new MultiSelectList(ViewBag.MyExtensions, "Id", "ListViewName", ViewBag.SelectedExtensions))
|
<td>@Html.ListBoxFor(m => m.Extensions, new MultiSelectList(ViewBag.MyExtensions, "Id", "ListViewName", ViewBag.SelectedExtensions))
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>@Html.LabelFor(m => m.PrimaryExtension, "Primary Extension:")</th>
|
||||||
|
<td>@if (ViewBag.SelectedExtensions == null || ((ICollection<Extension>)ViewBag.SelectedExtensions).Count == 0) {
|
||||||
|
@Html.DropDownListFor(m => m.PrimaryExtension, new List<SelectListItem>(), "-- No Extensions Selected --", new { @disabled=true })
|
||||||
|
} else {
|
||||||
|
@Html.DropDownListFor(m => m.PrimaryExtension, new SelectList(ViewBag.SelectedExtensions, "Id", "ListViewName", ViewBag.PrimaryExtension), "None")
|
||||||
|
}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2" align="right">
|
<td colspan="2" align="right">
|
||||||
<input type="reset" value="Reset" />
|
<input type="reset" value="Reset" />
|
||||||
|
|
|
@ -54,6 +54,14 @@ namespace PolyProv.Controllers
|
||||||
public IActionResult PhoneSettings(string addr)
|
public IActionResult PhoneSettings(string addr)
|
||||||
{
|
{
|
||||||
var phone = GetByMacStr(addr);
|
var phone = GetByMacStr(addr);
|
||||||
|
|
||||||
|
// ReSharper disable once InvertIf
|
||||||
|
if (phone?.PrimaryExtension is {} primary && phone.Extensions.FirstOrDefault() != primary) {
|
||||||
|
// primary extension exists and is not first, reorder so it's first
|
||||||
|
phone.Extensions.Remove(primary);
|
||||||
|
phone.Extensions = phone.Extensions.Prepend(primary).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
return phone != null ? View("Phone", phone) : NotFound();
|
return phone != null ? View("Phone", phone) : NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
external/AsterNET
vendored
Submodule
1
external/AsterNET
vendored
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit ae89dc5c61343bb8f3990fbf34656fc9555aae75
|
|
@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhoneToolMX.Models", "Phone
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PolyProv", "PolyProv\PolyProv.csproj", "{18199094-90AE-4C58-BE9E-65D486ECCC38}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PolyProv", "PolyProv\PolyProv.csproj", "{18199094-90AE-4C58-BE9E-65D486ECCC38}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsterNET", "external\AsterNET\Asterisk.2013\Asterisk.NET\AsterNET.csproj", "{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
@ -39,5 +41,11 @@ Global
|
||||||
{18199094-90AE-4C58-BE9E-65D486ECCC38}.Release|Any CPU.Build.0 = Release|Any CPU
|
{18199094-90AE-4C58-BE9E-65D486ECCC38}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{18199094-90AE-4C58-BE9E-65D486ECCC38}.Release|x64.ActiveCfg = Release|Any CPU
|
{18199094-90AE-4C58-BE9E-65D486ECCC38}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
{18199094-90AE-4C58-BE9E-65D486ECCC38}.Release|x64.Build.0 = Release|Any CPU
|
{18199094-90AE-4C58-BE9E-65D486ECCC38}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{DBBFEA95-BA6F-4D5D-93A3-7DC59A63BBDE}.Release|x64.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
Loading…
Reference in a new issue