Initial commit
This commit is contained in:
commit
1f92f82b2c
454
.gitignore
vendored
Normal file
454
.gitignore
vendored
Normal file
|
@ -0,0 +1,454 @@
|
|||
## 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/master/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 of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# Tye
|
||||
.tye/
|
||||
|
||||
# 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
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# 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
|
||||
|
||||
##
|
||||
## Visual studio for Mac
|
||||
##
|
||||
|
||||
|
||||
# globs
|
||||
Makefile.in
|
||||
*.userprefs
|
||||
*.usertasks
|
||||
config.make
|
||||
config.status
|
||||
aclocal.m4
|
||||
install-sh
|
||||
autom4te.cache/
|
||||
*.tar.gz
|
||||
tarballs/
|
||||
test-results/
|
||||
|
||||
# Mac bundle stuff
|
||||
*.dmg
|
||||
*.app
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
##
|
||||
## Visual Studio Code
|
||||
##
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
7
PhoneToolMX.Models/AlwaysIncludeAttribute.cs
Normal file
7
PhoneToolMX.Models/AlwaysIncludeAttribute.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace PhoneToolMX.Models
|
||||
{
|
||||
[System.AttributeUsage(AttributeTargets.Property)]
|
||||
public class AlwaysIncludeAttribute : System.Attribute
|
||||
{
|
||||
}
|
||||
}
|
25
PhoneToolMX.Models/CheckExtensionsAttribute.cs
Normal file
25
PhoneToolMX.Models/CheckExtensionsAttribute.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using PhoneToolMX.Models.ViewModels;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Data;
|
||||
|
||||
namespace PhoneToolMX.Models
|
||||
{
|
||||
public class CheckExtensionsAttribute : ValidationAttribute
|
||||
{
|
||||
private int? MaxExtensions;
|
||||
|
||||
public override string FormatErrorMessage(string name)
|
||||
{
|
||||
return MaxExtensions != null
|
||||
? $"Up to {MaxExtensions} extensions can be assigned to this phone"
|
||||
: "Too many extensions assigned to this phone";
|
||||
}
|
||||
public override bool IsValid(object value)
|
||||
{
|
||||
if (value is not PhoneVM phone) throw new ConstraintException($"{GetType().Name} is only allowed for PhoneVM");
|
||||
if (phone.MaxExtensions <= 0 || phone.Extensions == null) return true;
|
||||
MaxExtensions = phone.MaxExtensions;
|
||||
return !(phone.Extensions.Count > phone.MaxExtensions);
|
||||
}
|
||||
}
|
||||
}
|
126
PhoneToolMX.Models/Data/PTMXContext.cs
Normal file
126
PhoneToolMX.Models/Data/PTMXContext.cs
Normal file
|
@ -0,0 +1,126 @@
|
|||
// using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
using PhoneToolMX.Models;
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
// ReSharper disable CheckNamespace
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
|
||||
namespace PhoneToolMX.Data {
|
||||
public class PTMXContext : DbContext
|
||||
{
|
||||
public DbSet<Phone> Phones { get; set; }
|
||||
public DbSet<PhoneModel> PhoneModels { get; set; }
|
||||
public DbSet<Extension> Extensions { get; set; }
|
||||
public DbSet<CustomData> CustomData { get; set; }
|
||||
public DbSet<User> Users { get; set; }
|
||||
|
||||
public PTMXContext(DbContextOptions<PTMXContext> options) : base(options)
|
||||
{
|
||||
}
|
||||
|
||||
#region Public helpers
|
||||
|
||||
/// <summary>
|
||||
/// Adds an <see cref="IOwnedModel"/> entity to the database, marking the given <see cref="User"/> as its owner.
|
||||
/// </summary>
|
||||
/// <param name="owner">The <see cref="User"/> that owns this entity.</param>
|
||||
/// <param name="entity">The entity to be created.</param>
|
||||
/// <typeparam name="TEntity">A model conforming to <see cref="IOwnedModel"/>.</typeparam>
|
||||
/// <returns>The entity entry for the created entity.</returns>
|
||||
public async Task<EntityEntry<TEntity>> AddOwnable<TEntity>(User owner, TEntity entity) where TEntity: OwnedBase
|
||||
{
|
||||
var set = Set<TEntity>();
|
||||
entity.Owners ??= new List<User>();
|
||||
entity.Owners.Add(owner);
|
||||
var entry = await AddAsync(entity);
|
||||
return entry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all entities of a certain model owned by the <see cref="User"/>.
|
||||
/// </summary>
|
||||
/// <param name="owner">The <see cref="User"/> object to be considered the owner.</param>
|
||||
/// <typeparam name="TEntity">A model conforming to <see cref="IOwnedModel"/></typeparam>
|
||||
/// <returns>All entities of the model owned by the given user</returns>
|
||||
public ICollection<TEntity> GetOwned<TEntity>(User owner) where TEntity : class, IOwnedModel
|
||||
{
|
||||
if (owner == null) return null;
|
||||
var entity = Set<TEntity>().Where(x => x.Owners.Any(o => o.Id == owner.Id));
|
||||
// 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a model entity by its Id
|
||||
/// </summary>
|
||||
/// <param name="id">Id of the model, or null if you're like that</param>
|
||||
/// <typeparam name="TEntity">A model confirming to <see cref="IModel"/> that has a given DbSet</typeparam>
|
||||
/// <returns>The model defined by the Id, or null if none exist.</returns>
|
||||
/// <remarks>If null is provided as the id, null will be returned. <c>id</c> is nullable solely for compatibility.
|
||||
/// </remarks>
|
||||
public TEntity GetEntityById<TEntity>(int? id) where TEntity : class, IModel
|
||||
{
|
||||
return id == null ? null : Set<TEntity>().FirstOrDefault(o => o.Id == id);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
// Core of PTMX: Phones and extensions
|
||||
var ext = modelBuilder.Entity<Extension>();
|
||||
ext.HasKey(x => x.Id);
|
||||
ext.Property(x => x.Id).UseIdentityColumn();
|
||||
ext.Property(x => x.ExtId).HasComputedColumnSql("\"Id\" + 1000", stored: true);
|
||||
|
||||
var phone = modelBuilder.Entity<Phone>();
|
||||
phone
|
||||
.HasMany(p => p.Extensions)
|
||||
.WithMany(x => x.Phones);
|
||||
phone.HasKey(p => p.Id);
|
||||
phone.Property(p => p.Id).UseIdentityColumn();
|
||||
// Randomly generates a 24-char password
|
||||
phone
|
||||
.Property(p => p.Password)
|
||||
.HasDefaultValueSql("encode(gen_random_bytes(18), 'base64')");
|
||||
|
||||
// Wallpapers, ringtones, etc
|
||||
var cd = modelBuilder.Entity<CustomData>();
|
||||
cd.HasKey(c => c.Id);
|
||||
cd.Property(c => c.Id).UseIdentityColumn();
|
||||
|
||||
// Phone models, for custom tests
|
||||
var pm = modelBuilder.Entity<PhoneModel>();
|
||||
pm.HasKey(p => p.Id);
|
||||
pm.Property(p => p.Id).UseIdentityColumn();
|
||||
pm.HasData(new PhoneModel
|
||||
{
|
||||
Id = 0,
|
||||
ModelName = "Polycom VVX300/310",
|
||||
MaxExtensions = 6,
|
||||
PreVvxPolycom = false,
|
||||
});
|
||||
|
||||
// Authz/RBAC
|
||||
var userEnt = modelBuilder.Entity<User>();
|
||||
userEnt
|
||||
.HasMany(u => u.Phones)
|
||||
.WithMany(p => p.Owners);
|
||||
userEnt
|
||||
.HasMany(u => u.Extensions)
|
||||
.WithMany(x => x.Owners);
|
||||
userEnt
|
||||
.HasKey(u => u.Id);
|
||||
|
||||
modelBuilder.Entity<Role>()
|
||||
.HasKey(r => r.Id);
|
||||
}
|
||||
}
|
||||
}
|
16
PhoneToolMX.Models/HeaderAttribute.cs
Normal file
16
PhoneToolMX.Models/HeaderAttribute.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace PhoneToolMX.Models
|
||||
{
|
||||
[System.AttributeUsage(AttributeTargets.Property)]
|
||||
public class HeaderAttribute : System.Attribute
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public bool Primary { get; set; }
|
||||
public bool Small { get; set; }
|
||||
public HeaderAttribute(string title)
|
||||
{
|
||||
Title = title;
|
||||
}
|
||||
|
||||
public HeaderAttribute() {}
|
||||
}
|
||||
}
|
358
PhoneToolMX.Models/Migrations/20231015015926_InitialCreate.Designer.cs
generated
Normal file
358
PhoneToolMX.Models/Migrations/20231015015926_InitialCreate.Designer.cs
generated
Normal file
|
@ -0,0 +1,358 @@
|
|||
// <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("20231015015926_InitialCreate")]
|
||||
partial class InitialCreate
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.16")
|
||||
.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.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<string>("Password")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValueSql("encode(gen_random_bytes(18), 'base64')");
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
288
PhoneToolMX.Models/Migrations/20231015015926_InitialCreate.cs
Normal file
288
PhoneToolMX.Models/Migrations/20231015015926_InitialCreate.cs
Normal file
|
@ -0,0 +1,288 @@
|
|||
using System;
|
||||
using System.Net.NetworkInformation;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PhoneToolMX.Models.Migrations
|
||||
{
|
||||
public partial class InitialCreate : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PhoneModels",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ModelName = table.Column<string>(type: "text", nullable: true),
|
||||
MaxExtensions = table.Column<long>(type: "bigint", nullable: false),
|
||||
PreVvxPolycom = table.Column<bool>(type: "boolean", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PhoneModels", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Role",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
Name = table.Column<string>(type: "text", nullable: true),
|
||||
NormalizedName = table.Column<string>(type: "text", nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "text", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Role", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false),
|
||||
UserName = table.Column<string>(type: "text", nullable: true),
|
||||
NormalizedUserName = table.Column<string>(type: "text", nullable: true),
|
||||
Email = table.Column<string>(type: "text", nullable: true),
|
||||
NormalizedEmail = table.Column<string>(type: "text", nullable: true),
|
||||
EmailConfirmed = table.Column<bool>(type: "boolean", nullable: false),
|
||||
PasswordHash = table.Column<string>(type: "text", nullable: true),
|
||||
SecurityStamp = table.Column<string>(type: "text", nullable: true),
|
||||
ConcurrencyStamp = table.Column<string>(type: "text", nullable: true),
|
||||
PhoneNumber = table.Column<string>(type: "text", nullable: true),
|
||||
PhoneNumberConfirmed = table.Column<bool>(type: "boolean", nullable: false),
|
||||
TwoFactorEnabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
LockoutEnd = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
|
||||
LockoutEnabled = table.Column<bool>(type: "boolean", nullable: false),
|
||||
AccessFailedCount = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CustomData",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
FriendlyName = table.Column<string>(type: "character varying(16)", maxLength: 16, nullable: true),
|
||||
DataType = table.Column<int>(type: "integer", nullable: false),
|
||||
Size = table.Column<long>(type: "bigint", nullable: false),
|
||||
Data = table.Column<byte[]>(type: "bytea", nullable: true),
|
||||
PhoneId = table.Column<int>(type: "integer", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CustomData", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Extensions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ExtId = table.Column<int>(type: "integer", nullable: false, computedColumnSql: "\"Id\" + 1000", stored: true),
|
||||
DirectoryName = table.Column<string>(type: "text", nullable: false),
|
||||
Listed = table.Column<bool>(type: "boolean", nullable: false),
|
||||
HoldMusicId = table.Column<int>(type: "integer", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Extensions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Extensions_CustomData_HoldMusicId",
|
||||
column: x => x.HoldMusicId,
|
||||
principalTable: "CustomData",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Phones",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
MacAddress = table.Column<PhysicalAddress>(type: "macaddr", nullable: false),
|
||||
FriendlyName = table.Column<string>(type: "text", nullable: false),
|
||||
ModelId = table.Column<int>(type: "integer", nullable: false),
|
||||
BackgroundId = table.Column<int>(type: "integer", nullable: true),
|
||||
Password = table.Column<string>(type: "text", nullable: true, defaultValueSql: "encode(gen_random_bytes(18), 'base64')")
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Phones", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Phones_CustomData_BackgroundId",
|
||||
column: x => x.BackgroundId,
|
||||
principalTable: "CustomData",
|
||||
principalColumn: "Id");
|
||||
table.ForeignKey(
|
||||
name: "FK_Phones_PhoneModels_ModelId",
|
||||
column: x => x.ModelId,
|
||||
principalTable: "PhoneModels",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ExtensionUser",
|
||||
columns: table => new
|
||||
{
|
||||
ExtensionsId = table.Column<int>(type: "integer", nullable: false),
|
||||
OwnersId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ExtensionUser", x => new { x.ExtensionsId, x.OwnersId });
|
||||
table.ForeignKey(
|
||||
name: "FK_ExtensionUser_Extensions_ExtensionsId",
|
||||
column: x => x.ExtensionsId,
|
||||
principalTable: "Extensions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ExtensionUser_Users_OwnersId",
|
||||
column: x => x.OwnersId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ExtensionPhone",
|
||||
columns: table => new
|
||||
{
|
||||
ExtensionsId = table.Column<int>(type: "integer", nullable: false),
|
||||
PhonesId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ExtensionPhone", x => new { x.ExtensionsId, x.PhonesId });
|
||||
table.ForeignKey(
|
||||
name: "FK_ExtensionPhone_Extensions_ExtensionsId",
|
||||
column: x => x.ExtensionsId,
|
||||
principalTable: "Extensions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_ExtensionPhone_Phones_PhonesId",
|
||||
column: x => x.PhonesId,
|
||||
principalTable: "Phones",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PhoneUser",
|
||||
columns: table => new
|
||||
{
|
||||
OwnersId = table.Column<string>(type: "text", nullable: false),
|
||||
PhonesId = table.Column<int>(type: "integer", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_PhoneUser", x => new { x.OwnersId, x.PhonesId });
|
||||
table.ForeignKey(
|
||||
name: "FK_PhoneUser_Phones_PhonesId",
|
||||
column: x => x.PhonesId,
|
||||
principalTable: "Phones",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_PhoneUser_Users_OwnersId",
|
||||
column: x => x.OwnersId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.InsertData(
|
||||
table: "PhoneModels",
|
||||
columns: new[] { "Id", "MaxExtensions", "ModelName", "PreVvxPolycom" },
|
||||
values: new object[] { 0, 6L, "Polycom VVX300/310", false });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CustomData_PhoneId",
|
||||
table: "CustomData",
|
||||
column: "PhoneId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExtensionPhone_PhonesId",
|
||||
table: "ExtensionPhone",
|
||||
column: "PhonesId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Extensions_HoldMusicId",
|
||||
table: "Extensions",
|
||||
column: "HoldMusicId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ExtensionUser_OwnersId",
|
||||
table: "ExtensionUser",
|
||||
column: "OwnersId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Phones_BackgroundId",
|
||||
table: "Phones",
|
||||
column: "BackgroundId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Phones_ModelId",
|
||||
table: "Phones",
|
||||
column: "ModelId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_PhoneUser_PhonesId",
|
||||
table: "PhoneUser",
|
||||
column: "PhonesId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_CustomData_Phones_PhoneId",
|
||||
table: "CustomData",
|
||||
column: "PhoneId",
|
||||
principalTable: "Phones",
|
||||
principalColumn: "Id");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_CustomData_Phones_PhoneId",
|
||||
table: "CustomData");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ExtensionPhone");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ExtensionUser");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PhoneUser");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Role");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Extensions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Phones");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "CustomData");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "PhoneModels");
|
||||
}
|
||||
}
|
||||
}
|
356
PhoneToolMX.Models/Migrations/PTMXContextModelSnapshot.cs
Normal file
356
PhoneToolMX.Models/Migrations/PTMXContextModelSnapshot.cs
Normal file
|
@ -0,0 +1,356 @@
|
|||
// <auto-generated />
|
||||
using System;
|
||||
using System.Net.NetworkInformation;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using PhoneToolMX.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PhoneToolMX.Models.Migrations
|
||||
{
|
||||
[DbContext(typeof(PTMXContext))]
|
||||
partial class PTMXContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "6.0.16")
|
||||
.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", (string)null);
|
||||
});
|
||||
|
||||
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", (string)null);
|
||||
});
|
||||
|
||||
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", (string)null);
|
||||
});
|
||||
|
||||
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.HasKey("Id");
|
||||
|
||||
b.HasIndex("HoldMusicId");
|
||||
|
||||
b.ToTable("Extensions", (string)null);
|
||||
});
|
||||
|
||||
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<string>("Password")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("text")
|
||||
.HasDefaultValueSql("encode(gen_random_bytes(18), 'base64')");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("BackgroundId");
|
||||
|
||||
b.HasIndex("ModelId");
|
||||
|
||||
b.ToTable("Phones", (string)null);
|
||||
});
|
||||
|
||||
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", (string)null);
|
||||
|
||||
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", (string)null);
|
||||
});
|
||||
|
||||
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", (string)null);
|
||||
});
|
||||
|
||||
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", (string)null);
|
||||
});
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
21
PhoneToolMX.Models/Models/CustomData.cs
Normal file
21
PhoneToolMX.Models/Models/CustomData.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using PhoneToolMX.Models;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace PhoneToolMX.Models {
|
||||
public enum CustomDataType {
|
||||
Background,
|
||||
MusicTone,
|
||||
}
|
||||
public class CustomData : IModel {
|
||||
public int? Id { get; set; }
|
||||
|
||||
[MaxLength(16)]
|
||||
[Header("Name")]
|
||||
public string FriendlyName { get; set; }
|
||||
public CustomDataType DataType { get; set; }
|
||||
[Header("Size")]
|
||||
public uint Size { get; set; }
|
||||
public byte[] Data { get; set; }
|
||||
}
|
||||
}
|
8
PhoneToolMX.Models/Models/ErrorViewModel.cs
Normal file
8
PhoneToolMX.Models/Models/ErrorViewModel.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace PhoneToolMX.Models;
|
||||
|
||||
public class ErrorViewModel
|
||||
{
|
||||
public string RequestId { get; set; }
|
||||
|
||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
}
|
25
PhoneToolMX.Models/Models/Extension.cs
Normal file
25
PhoneToolMX.Models/Models/Extension.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using PhoneToolMX.Data;
|
||||
using PhoneToolMX.Models;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace PhoneToolMX.Models {
|
||||
public class Extension: OwnedBase {
|
||||
public ICollection<Phone> Phones { get; set; }
|
||||
|
||||
[Header("Ext.", Primary = true, Small = true)]
|
||||
public int ExtId { get; set; }
|
||||
|
||||
[Header("Directory Name")]
|
||||
[Required]
|
||||
public string DirectoryName { get; set; }
|
||||
|
||||
[Header("Listed?", Small = true)]
|
||||
public bool Listed { get; set; }
|
||||
|
||||
public CustomData HoldMusic { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public string ListViewName => $"{DirectoryName} ({ExtId})";
|
||||
}
|
||||
}
|
19
PhoneToolMX.Models/Models/IModel.cs
Normal file
19
PhoneToolMX.Models/Models/IModel.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Reflection;
|
||||
|
||||
namespace PhoneToolMX.Models
|
||||
{
|
||||
public interface IModel
|
||||
{
|
||||
public int? Id { get; set; }
|
||||
|
||||
public void Commit(IModel obj)
|
||||
{
|
||||
if (GetType() != obj.GetType()) throw new ArgumentException("Input object must be the same model");
|
||||
foreach (var prop in GetType().GetProperties().Where(p => p.CanWrite)) {
|
||||
if (prop.GetValue(obj, null) is {} propVal) {
|
||||
prop.SetValue(this, propVal, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
PhoneToolMX.Models/Models/IOwnedModel.cs
Normal file
10
PhoneToolMX.Models/Models/IOwnedModel.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
using PhoneToolMX.Models.ViewModels;
|
||||
|
||||
namespace PhoneToolMX.Models
|
||||
{
|
||||
public interface IOwnedModel : IModel
|
||||
{
|
||||
public ICollection<User> Owners { get; set; }
|
||||
public bool IsOwnedBy(User user);
|
||||
}
|
||||
}
|
17
PhoneToolMX.Models/Models/OwnedBase.cs
Normal file
17
PhoneToolMX.Models/Models/OwnedBase.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using PhoneToolMX.Models.ViewModels;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace PhoneToolMX.Models
|
||||
{
|
||||
public class OwnedBase : IOwnedModel
|
||||
{
|
||||
public int? Id { get; set; }
|
||||
public ICollection<User> Owners { get; set; }
|
||||
|
||||
public bool IsOwnedBy(User user)
|
||||
{
|
||||
return (Owners != null && Owners.Any(o => o.Id == user.Id));
|
||||
}
|
||||
}
|
||||
}
|
28
PhoneToolMX.Models/Models/Phone.cs
Normal file
28
PhoneToolMX.Models/Models/Phone.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using PhoneToolMX.Data;
|
||||
using PhoneToolMX.Models;
|
||||
using PhoneToolMX.Models.ViewModels;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace PhoneToolMX.Models {
|
||||
public class Phone: OwnedBase {
|
||||
[Required]
|
||||
public PhysicalAddress MacAddress { get; set; }
|
||||
|
||||
[Header("Name", Primary = true)]
|
||||
[Required]
|
||||
public string FriendlyName { get; set; }
|
||||
|
||||
[AlwaysInclude]
|
||||
[Required]
|
||||
public PhoneModel Model { get; set; }
|
||||
|
||||
[AlwaysInclude]
|
||||
public ICollection<Extension> Extensions { get; set; }
|
||||
public CustomData Background { get; set; }
|
||||
public ICollection<CustomData> Ringtones { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
30
PhoneToolMX.Models/Models/PhoneModel.cs
Normal file
30
PhoneToolMX.Models/Models/PhoneModel.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace PhoneToolMX.Models
|
||||
{
|
||||
public class PhoneModel
|
||||
{
|
||||
public int? Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The friendly name of the phone (e.g., "Polycom VVX300/310")
|
||||
/// </summary>
|
||||
public string ModelName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum lines the phone can support.
|
||||
/// </summary>
|
||||
public uint MaxExtensions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// This setting impacts whether the model is pre-VVX era.
|
||||
/// All Polycom phones that can be provisioned by Polycom UC
|
||||
/// before the VVX era (mainly, SPIP phones) have a limit of
|
||||
/// 300KiB per ringtone.
|
||||
///
|
||||
/// All Polycom phones, VVX or otherwise, have a limit of
|
||||
/// 600KiB total across all ringtones.
|
||||
/// </summary>
|
||||
public bool PreVvxPolycom { get; set; }
|
||||
}
|
||||
}
|
17
PhoneToolMX.Models/Models/Role.cs
Normal file
17
PhoneToolMX.Models/Models/Role.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace PhoneToolMX.Models
|
||||
{
|
||||
public class Role : IdentityRole<string>
|
||||
{
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
public override string Id { get; set; }
|
||||
|
||||
public Role() { }
|
||||
public Role(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
}
|
11
PhoneToolMX.Models/Models/User.cs
Normal file
11
PhoneToolMX.Models/Models/User.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using Microsoft.AspNetCore.Identity;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace PhoneToolMX.Models {
|
||||
public class User : IdentityUser<string>
|
||||
{
|
||||
public ICollection<Phone> Phones;
|
||||
public ICollection<Extension> Extensions;
|
||||
}
|
||||
}
|
19
PhoneToolMX.Models/PhoneToolMX.Models.csproj
Normal file
19
PhoneToolMX.Models/PhoneToolMX.Models.csproj
Normal file
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>disable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.23" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="6.0.23" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.23">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.22" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
57
PhoneToolMX.Models/ViewModels/ExtensionVM.cs
Normal file
57
PhoneToolMX.Models/ViewModels/ExtensionVM.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
using PhoneToolMX.Data;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace PhoneToolMX.Models.ViewModels
|
||||
{
|
||||
public class ExtensionVM: IViewModel
|
||||
{
|
||||
|
||||
public int? Id { get; set; }
|
||||
|
||||
[Header("Ext.", Primary = true, Small = true)]
|
||||
public int ExtId { get; set; }
|
||||
|
||||
[Header("Directory Name")]
|
||||
[Required]
|
||||
[MaxLength(15)]
|
||||
public string DirectoryName { get; set; }
|
||||
|
||||
[Header("Listed?", Small = true)]
|
||||
[Required]
|
||||
public bool Listed { get; set; }
|
||||
|
||||
public int? HoldMusic { get; set; }
|
||||
|
||||
public IOwnedModel ToEntity(PTMXContext ctx)
|
||||
{
|
||||
return new Extension
|
||||
{
|
||||
Id = Id,
|
||||
ExtId = ExtId,
|
||||
DirectoryName = DirectoryName,
|
||||
Listed = Listed,
|
||||
HoldMusic = HoldMusic == null ? null : ctx.CustomData.FirstOrDefault(c => c.Id == HoldMusic),
|
||||
Owners = new List<User>(),
|
||||
};
|
||||
}
|
||||
public IOwnedModel ToEntity(PTMXContext ctx, IOwnedModel current)
|
||||
{
|
||||
var ent = ToEntity(ctx);
|
||||
ent.Owners = current.Owners;
|
||||
return ent;
|
||||
}
|
||||
|
||||
public IViewModel FromEntity(IOwnedModel entity)
|
||||
{
|
||||
if (entity is not Extension extEnt) throw new ArgumentException("entity must be of type Extension");
|
||||
return new ExtensionVM
|
||||
{
|
||||
Id = extEnt.Id,
|
||||
ExtId = extEnt.ExtId,
|
||||
DirectoryName = extEnt.DirectoryName,
|
||||
Listed = extEnt.Listed,
|
||||
HoldMusic = extEnt.HoldMusic?.Id,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
15
PhoneToolMX.Models/ViewModels/IViewModel.cs
Normal file
15
PhoneToolMX.Models/ViewModels/IViewModel.cs
Normal file
|
@ -0,0 +1,15 @@
|
|||
using PhoneToolMX.Data;
|
||||
using PhoneToolMX.Models;
|
||||
|
||||
namespace PhoneToolMX.Models.ViewModels
|
||||
{
|
||||
public interface IViewModel
|
||||
{
|
||||
public int? Id { get; set; }
|
||||
|
||||
public IOwnedModel ToEntity(PTMXContext ctx);
|
||||
public IOwnedModel ToEntity(PTMXContext ctx, IOwnedModel current);
|
||||
|
||||
public IViewModel FromEntity(IOwnedModel entity);
|
||||
}
|
||||
}
|
64
PhoneToolMX.Models/ViewModels/PhoneVM.cs
Normal file
64
PhoneToolMX.Models/ViewModels/PhoneVM.cs
Normal file
|
@ -0,0 +1,64 @@
|
|||
using PhoneToolMX.Data;
|
||||
using PhoneToolMX.Models;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Net.NetworkInformation;
|
||||
|
||||
namespace PhoneToolMX.Models.ViewModels
|
||||
{
|
||||
[CheckExtensions]
|
||||
public class PhoneVM : IViewModel
|
||||
{
|
||||
public int? Id { get; set; }
|
||||
|
||||
[Header("MAC Address")]
|
||||
[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")]
|
||||
public string MacAddress { get; set; }
|
||||
|
||||
[Header("Name", Primary = true)]
|
||||
[Required]
|
||||
public string FriendlyName { get; set; }
|
||||
|
||||
[Required]
|
||||
public int? Model { get; set; }
|
||||
|
||||
public ICollection<int?> Extensions { get; set; }
|
||||
|
||||
public int MaxExtensions { get; set; }
|
||||
|
||||
public IOwnedModel ToEntity(PTMXContext ctx)
|
||||
{
|
||||
return new Phone
|
||||
{
|
||||
Id = Id,
|
||||
MacAddress = PhysicalAddress.Parse(MacAddress),
|
||||
FriendlyName = FriendlyName,
|
||||
Model = ctx.PhoneModels.FirstOrDefault(p => p.Id == Model)!,
|
||||
Extensions = Extensions?.Select(x => ctx.Extensions.FirstOrDefault(e => e.Id == x)).ToList(),
|
||||
Owners = new List<User>(),
|
||||
};
|
||||
}
|
||||
|
||||
public IOwnedModel ToEntity(PTMXContext ctx, IOwnedModel current)
|
||||
{
|
||||
var ent = ToEntity(ctx);
|
||||
ent.Owners = current.Owners;
|
||||
return ent;
|
||||
}
|
||||
|
||||
public IViewModel FromEntity(IOwnedModel entity)
|
||||
{
|
||||
if (entity is not Phone phoneEnt) throw new ArgumentException("entity must be of type Phone");
|
||||
return new PhoneVM
|
||||
{
|
||||
Id = entity.Id,
|
||||
MacAddress = phoneEnt.MacAddress.ToString(),
|
||||
FriendlyName = phoneEnt.FriendlyName,
|
||||
Model = phoneEnt.Model!.Id,
|
||||
Extensions = phoneEnt.Extensions?.Select(x => x.Id).ToList(),
|
||||
MaxExtensions = (int)phoneEnt.Model!.MaxExtensions,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
36
PhoneToolMX/.vscode/launch.json
vendored
Normal file
36
PhoneToolMX/.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
// Use IntelliSense to find out which attributes exist for C# debugging
|
||||
// Use hover for the description of the existing attributes
|
||||
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
|
||||
"name": ".NET Core Launch (web)",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/bin/Debug/net6.0/PhoneToolMX.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"stopAtEntry": false,
|
||||
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
|
||||
"serverReadyAction": {
|
||||
"action": "openExternally",
|
||||
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
|
||||
},
|
||||
"env": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"ASPNETCORE_URLS": "http://localhost:5001"
|
||||
},
|
||||
"sourceFileMap": {
|
||||
"/Views": "${workspaceFolder}/Views"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": ".NET Core Attach",
|
||||
"type": "coreclr",
|
||||
"request": "attach"
|
||||
}
|
||||
]
|
||||
}
|
41
PhoneToolMX/.vscode/tasks.json
vendored
Normal file
41
PhoneToolMX/.vscode/tasks.json
vendored
Normal file
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"label": "build",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"build",
|
||||
"${workspaceFolder}/PhoneToolMX.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "publish",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"publish",
|
||||
"${workspaceFolder}/PhoneToolMX.csproj",
|
||||
"/property:GenerateFullPaths=true",
|
||||
"/consoleloggerparameters:NoSummary"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "watch",
|
||||
"command": "dotnet",
|
||||
"type": "process",
|
||||
"args": [
|
||||
"watch",
|
||||
"run",
|
||||
"--project",
|
||||
"${workspaceFolder}/PhoneToolMX.csproj"
|
||||
],
|
||||
"problemMatcher": "$msCompile"
|
||||
}
|
||||
]
|
||||
}
|
225
PhoneToolMX/Controllers/BaseController.cs
Normal file
225
PhoneToolMX/Controllers/BaseController.cs
Normal file
|
@ -0,0 +1,225 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PhoneToolMX.Data;
|
||||
using PhoneToolMX.Helpers;
|
||||
using PhoneToolMX.Models;
|
||||
using PhoneToolMX.Models.ViewModels;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace PhoneToolMX.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Route("[controller]")]
|
||||
public abstract class BaseController<T, TViewModel> : Controller
|
||||
where T: OwnedBase, IModel
|
||||
where TViewModel : IViewModel, new()
|
||||
{
|
||||
protected private readonly PTMXContext _context;
|
||||
private readonly UserManager<User> _userManager;
|
||||
|
||||
protected BaseController(UserManager<User> mgr, PTMXContext ctx)
|
||||
{
|
||||
_context = ctx;
|
||||
_userManager = mgr;
|
||||
}
|
||||
|
||||
protected private virtual Task PreForm(TViewModel vm)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#region Helper methods
|
||||
|
||||
/// <summary>
|
||||
/// Gets the "friendly" name for an owned model object.
|
||||
/// </summary>
|
||||
/// <param name="vm">The model object to inspect</param>
|
||||
/// <returns>The friendly name, or its ID if none was defined</returns>
|
||||
private static string GetFriendlyName(IViewModel vm)
|
||||
{
|
||||
if (vm is not TViewModel model) return vm.Id.ToString();
|
||||
return typeof(TViewModel).GetProperties().Where(pi =>
|
||||
{
|
||||
var headers = pi.GetCustomAttributes(typeof(HeaderAttribute), false);
|
||||
return headers.Length != 0 && ((HeaderAttribute)headers[0]).Primary;
|
||||
}).Select(pi => pi.GetValue(model)?.ToString()).FirstOrDefault() ?? model.Id.ToString();
|
||||
}
|
||||
|
||||
private static string GetFriendlyName(IOwnedModel model)
|
||||
{
|
||||
var vm = new TViewModel().FromEntity(model);
|
||||
return GetFriendlyName(vm);
|
||||
}
|
||||
|
||||
protected private async Task<User> CurrentUser()
|
||||
{
|
||||
var claim = HttpContext.User.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.NameIdentifier));
|
||||
if (claim == null) {
|
||||
throw new InvalidCredentialException("fuck this noise, nameid claim missing");
|
||||
}
|
||||
var user = await _userManager.FindByIdAsync(claim.Value);
|
||||
return user;
|
||||
}
|
||||
|
||||
private async Task<IActionResult> FormView(TViewModel vm)
|
||||
{
|
||||
await PreForm(vm);
|
||||
return View("_Form", vm);
|
||||
}
|
||||
|
||||
private void SetMessage(FormMessage msg)
|
||||
{
|
||||
TempData["Message"] = msg.ToString();
|
||||
}
|
||||
|
||||
private void SetMessage(FormMessageType type, string msg)
|
||||
{
|
||||
SetMessage(new FormMessage
|
||||
{
|
||||
Type = type,
|
||||
Message = msg,
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<IActionResult> ValidationError(TViewModel vm)
|
||||
{
|
||||
var plural = ModelState.ErrorCount > 1 ? "s" : string.Empty;
|
||||
SetMessage(
|
||||
FormMessageType.Error,
|
||||
$"Error{plural} occurred in validation. Make the changes required and click Submit to try again."
|
||||
);
|
||||
return await FormView(vm);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Create
|
||||
|
||||
[HttpGet("New")]
|
||||
public async Task<IActionResult> New()
|
||||
{
|
||||
ViewData["Title"] = $"Add new {typeof(T).Name.ToLower()}";
|
||||
return await FormView(default);
|
||||
}
|
||||
|
||||
[HttpPost("New")]
|
||||
public async Task<IActionResult> NewPost(TViewModel vm)
|
||||
{
|
||||
ViewData["Title"] = $"New {typeof(T).Name}";
|
||||
|
||||
if (!ModelState.IsValid) return await ValidationError(vm);
|
||||
|
||||
if (vm.ToEntity(_context) is not T model) throw new InvalidOperationException($"{typeof(TViewModel).FullName}.ToEntity() somehow produced something other than a {typeof(T).FullName}");
|
||||
var entity = await _context.AddOwnable(await CurrentUser(), model);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
SetMessage(
|
||||
FormMessageType.Success,
|
||||
$"{typeof(T).Name} {GetFriendlyName(vm)} was created."
|
||||
);
|
||||
|
||||
return RedirectToAction("Edit", new { id = entity.Entity.Id });
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Read
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
ViewData["Title"] = $"My {typeof(T).Name}s";
|
||||
return View("Index", _context
|
||||
.GetOwned<T>(await CurrentUser())
|
||||
.Select(m => (TViewModel)new TViewModel().FromEntity(m))
|
||||
.ToList());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Update
|
||||
|
||||
[HttpGet("Edit")]
|
||||
public async Task<IActionResult> Edit(int id)
|
||||
{
|
||||
var model = _context.GetOwned<T>(await CurrentUser()).FirstOrDefault(o => o.Id == id);
|
||||
if (model == null) return NotFound();
|
||||
ViewData["Title"] = $"Editing {typeof(T).Name.ToLower()} {GetFriendlyName(new TViewModel().FromEntity(model))}";
|
||||
return await FormView((TViewModel)new TViewModel().FromEntity(model));
|
||||
}
|
||||
|
||||
[HttpPost("Edit")]
|
||||
public async Task<IActionResult> EditPost(TViewModel vm)
|
||||
{
|
||||
if (vm?.Id == null) {
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid) return await ValidationError(vm);
|
||||
|
||||
// merge VM's changes with DbModel
|
||||
var currentModel = _context.GetEntityById<T>(vm.Id);
|
||||
if (vm.ToEntity(_context, currentModel) is not T entity) throw new InvalidOperationException($"{typeof(TViewModel).FullName}.ToEntity() somehow produced something other than a {typeof(T).FullName}");
|
||||
currentModel.Commit(entity);
|
||||
|
||||
// and commit back
|
||||
_context.Set<T>().Update(currentModel);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
SetMessage(
|
||||
FormMessageType.Success,
|
||||
$"{typeof(T).Name} {GetFriendlyName(vm)} was updated."
|
||||
);
|
||||
|
||||
return RedirectToAction("Edit", new { id = entity.Id });
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Delete
|
||||
|
||||
[HttpGet("Delete")]
|
||||
public async Task<IActionResult> Delete(int id)
|
||||
{
|
||||
var model = _context.GetEntityById<T>(id);
|
||||
|
||||
if (model?.IsOwnedBy(await CurrentUser()) != true) {
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
ViewData["Title"] = $"Delete {GetFriendlyName(model)}";
|
||||
TempData["ModelDelete"] = model.Id;
|
||||
return View("DeleteConfirm", model);
|
||||
}
|
||||
|
||||
[HttpPost("Delete")]
|
||||
public async Task<IActionResult> PostDelete(int id)
|
||||
{
|
||||
if ((int?)TempData["ModelDelete"] != id) {
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var model = _context.GetEntityById<T>(id);
|
||||
|
||||
if (model?.IsOwnedBy(await CurrentUser()) != true) {
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
_context.Set<T>().Remove(model);
|
||||
await _context.SaveChangesAsync();
|
||||
SetMessage(
|
||||
FormMessageType.Success,
|
||||
$"{typeof(T).Name} {GetFriendlyName(new TViewModel().FromEntity(model))} was successfully deleted."
|
||||
);
|
||||
|
||||
return RedirectToAction("Index");
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
23
PhoneToolMX/Controllers/ExtensionController.cs
Normal file
23
PhoneToolMX/Controllers/ExtensionController.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PhoneToolMX.Models;
|
||||
using PhoneToolMX.Data;
|
||||
using PhoneToolMX.Helpers;
|
||||
using PhoneToolMX.Models.ViewModels;
|
||||
|
||||
namespace PhoneToolMX.Controllers {
|
||||
[Authorize]
|
||||
public class ExtensionController : BaseController<Extension, ExtensionVM>
|
||||
{
|
||||
public ExtensionController(UserManager<User> mgr, PTMXContext ctx) : base(mgr, ctx) {}
|
||||
|
||||
protected override private Task PreForm(ExtensionVM e)
|
||||
{
|
||||
var holdMusics = _context.CustomData.Where(d => d.DataType == CustomDataType.MusicTone).ToList();
|
||||
ViewBag.HoldMusics = holdMusics;
|
||||
ViewBag.SelectedMusic = e?.HoldMusic == null ? null : holdMusics.FirstOrDefault(d => d.Id == e.HoldMusic);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
27
PhoneToolMX/Controllers/HomeController.cs
Normal file
27
PhoneToolMX/Controllers/HomeController.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System.Diagnostics;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PhoneToolMX.Models;
|
||||
|
||||
namespace PhoneToolMX.Controllers;
|
||||
|
||||
public class HomeController : Controller
|
||||
{
|
||||
private readonly ILogger<HomeController> _logger;
|
||||
|
||||
public HomeController(ILogger<HomeController> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
|
||||
public IActionResult Error()
|
||||
{
|
||||
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
|
||||
}
|
||||
}
|
13
PhoneToolMX/Controllers/ModelController.cs
Normal file
13
PhoneToolMX/Controllers/ModelController.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace PhoneToolMX.Controllers
|
||||
{
|
||||
public class ModelController : Controller
|
||||
{
|
||||
// GET
|
||||
public IActionResult Index()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
}
|
||||
}
|
26
PhoneToolMX/Controllers/PhoneController.cs
Normal file
26
PhoneToolMX/Controllers/PhoneController.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PhoneToolMX.Data;
|
||||
using PhoneToolMX.Helpers;
|
||||
using PhoneToolMX.Models;
|
||||
using PhoneToolMX.Models.ViewModels;
|
||||
|
||||
namespace PhoneToolMX.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
public class PhoneController : BaseController<Phone, PhoneVM>
|
||||
{
|
||||
public PhoneController(UserManager<User> mgr, PTMXContext ctx) : base(mgr, ctx) {}
|
||||
|
||||
protected override private async Task PreForm(PhoneVM pvm)
|
||||
{
|
||||
var myExts = _context.GetOwned<Extension>(await CurrentUser());
|
||||
var phoneModels = _context.PhoneModels.ToList();
|
||||
ViewBag.MyExtensions = myExts;
|
||||
ViewBag.SelectedExtensions = pvm?.Extensions == null ? null : myExts.Where(x => pvm.Extensions.Contains(x.Id)).ToList();
|
||||
ViewBag.ModelNumbers = phoneModels;
|
||||
ViewBag.CurrentModel = pvm?.Model == null ? null : phoneModels.Where(m => m.Id == pvm.Model);
|
||||
}
|
||||
}
|
||||
}
|
2
PhoneToolMX/Folder.DotSettings
Normal file
2
PhoneToolMX/Folder.DotSettings
Normal file
|
@ -0,0 +1,2 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=aors/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
18
PhoneToolMX/Helpers/FormMessage.cs
Normal file
18
PhoneToolMX/Helpers/FormMessage.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System.Text.Json;
|
||||
|
||||
namespace PhoneToolMX.Helpers {
|
||||
public enum FormMessageType {
|
||||
Success,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
}
|
||||
|
||||
public class FormMessage {
|
||||
public FormMessageType Type { get; set; }
|
||||
public string Message { get; set; }
|
||||
|
||||
public string CssClassName() => $"msg-{Type.ToString().ToLower()}";
|
||||
public override string ToString() => JsonSerializer.Serialize(this);
|
||||
}
|
||||
}
|
91
PhoneToolMX/Helpers/HtmlTable.cs
Normal file
91
PhoneToolMX/Helpers/HtmlTable.cs
Normal file
|
@ -0,0 +1,91 @@
|
|||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.AspNetCore.Mvc.Routing;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using PhoneToolMX.Models;
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace PhoneToolMX.Helpers
|
||||
{
|
||||
public static class HtmlTable
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates an HTML table for a given collection of model objects.
|
||||
/// </summary>
|
||||
/// <param name="helper">HtmlHelper instance in the view</param>
|
||||
/// <param name="headers">List of table headers</param>
|
||||
/// <param name="rowFunc">Function used to determine the structure of each row. All objects will be converted to strings with ToString().</param>
|
||||
/// <typeparam name="TModel"></typeparam>
|
||||
/// <returns>An HTML table representing the list.</returns>
|
||||
/// <remarks>
|
||||
/// https://xkcd.com/1319/
|
||||
/// </remarks>
|
||||
public static IHtmlContent MakeTable<TModel>(this IHtmlHelper<ICollection<TModel>> helper)
|
||||
{
|
||||
var builder = new HtmlContentBuilder();
|
||||
var urlFactory = helper.ViewContext.HttpContext.RequestServices.GetRequiredService<IUrlHelperFactory>();
|
||||
var url = urlFactory.GetUrlHelper(helper.ViewContext);
|
||||
var props = typeof(TModel).GetProperties().Where(pi => pi.GetCustomAttributes(typeof(HeaderAttribute), false).Length != 0).ToList();
|
||||
var idProp = typeof(TModel).GetProperty("Id");
|
||||
var headerList = props.Select(pi =>
|
||||
{
|
||||
var attr = (HeaderAttribute)pi.GetCustomAttributes(typeof(HeaderAttribute), false)[0];
|
||||
return attr;
|
||||
}).ToList();
|
||||
headerList.Add(new HeaderAttribute
|
||||
{
|
||||
Title = "Actions",
|
||||
Small = true,
|
||||
});
|
||||
|
||||
builder.AppendHtmlLine("<table id=\"listview\">")
|
||||
.AppendHtmlLine("<thead><tr>");
|
||||
foreach (var header in headerList) {
|
||||
builder.AppendFormat("<th{0}>{1}</th>",
|
||||
(header.Small) ? " width=2" : string.Empty,
|
||||
header.Title);
|
||||
}
|
||||
builder.AppendHtmlLine("</tr></thead>")
|
||||
.AppendHtmlLine("<tbody>");
|
||||
|
||||
// build body
|
||||
if (helper.ViewData.Model is {} rows) {
|
||||
foreach (var row in rows) {
|
||||
var cells = props
|
||||
.Select(pi =>
|
||||
{
|
||||
var val = pi.GetValue(row, null);
|
||||
if (val is bool someBool) {
|
||||
// idk, yes/no feels better to me than true/false for user-facing stuff
|
||||
return someBool ? "Yes" : "No";
|
||||
}
|
||||
return val?.ToString() ?? "(null)";
|
||||
})
|
||||
.ToList();
|
||||
var cellId = idProp?.GetValue(row, null)?.ToString() ?? "INVALID_ID";
|
||||
builder.AppendHtmlLine("<tr>");
|
||||
foreach (var cell in cells) {
|
||||
builder.AppendFormat("<td>{0}</td>", cell);
|
||||
}
|
||||
// Actions cell
|
||||
builder.AppendHtml("<td align=\"right\">")
|
||||
// ReSharper disable Mvc.ActionNotResolved
|
||||
.AppendHtml($"<a href=\"{url.Action("Edit", new {id = cellId})}\"><img src=\"{url.Content("~/images/edit_x16.gif")}\" alt=\"Edit\" /></a>")
|
||||
.AppendHtml($"<a href=\"{url.Action("Delete", new {id = cellId})}\"><img src=\"{url.Content("~/images/del_x16.gif")}\" alt=\"Delete\" /></a>")
|
||||
.AppendHtmlLine("</td>")
|
||||
.AppendHtmlLine("</tr>");
|
||||
}
|
||||
if (rows.Count == 0) {
|
||||
builder.AppendFormat("<tr><td colspan=\"{0}\" align=\"center\">No entries found</td></tr>", headerList.Count);
|
||||
}
|
||||
} else {
|
||||
builder.AppendFormat("<tr><td colspan=\"{0}\" align=\"center\">Invalid model found</td></tr>", headerList.Count);
|
||||
}
|
||||
|
||||
// close remaining table tags
|
||||
return builder.AppendHtmlLine("</tbody>")
|
||||
.AppendHtmlLine("</table>");
|
||||
}
|
||||
}
|
||||
}
|
35
PhoneToolMX/PhoneToolMX.csproj
Normal file
35
PhoneToolMX/PhoneToolMX.csproj
Normal file
|
@ -0,0 +1,35 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.23" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="6.0.23" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.23" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Analyzers" Version="6.0.23" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.23">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="6.0.23" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.13" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.22" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PhoneToolMX.Models\PhoneToolMX.Models.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Authorization\" />
|
||||
<Folder Include="ViewModels\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<_ContentIncludedByDefault Remove="wwwroot\js\site.js" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
131
PhoneToolMX/Program.cs
Normal file
131
PhoneToolMX/Program.cs
Normal file
|
@ -0,0 +1,131 @@
|
|||
using PhoneToolMX.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using PhoneToolMX.Models;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Claims;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// Add services to the container.
|
||||
builder.Services.AddControllersWithViews();
|
||||
builder.Services.AddDbContext<PTMXContext>(
|
||||
options => options.UseNpgsql(builder.Configuration.GetConnectionString("DbConnection"),
|
||||
b => b.MigrationsAssembly("PhoneToolMX.Models")));
|
||||
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
|
||||
|
||||
builder.Services.AddIdentityCore<User>(opts =>
|
||||
{
|
||||
opts.ClaimsIdentity.UserIdClaimType = "sub";
|
||||
opts.ClaimsIdentity.UserNameClaimType = "preferred_username";
|
||||
opts.ClaimsIdentity.EmailClaimType = "email";
|
||||
})
|
||||
.AddRoles<Role>()
|
||||
.AddRoleManager<RoleManager<Role>>()
|
||||
.AddSignInManager<SignInManager<User>>()
|
||||
.AddUserManager<UserManager<User>>()
|
||||
.AddEntityFrameworkStores<PTMXContext>();
|
||||
|
||||
// Using OIDC
|
||||
builder.Services.AddAuthentication(opts =>
|
||||
{
|
||||
opts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
|
||||
opts.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddCookie()
|
||||
.AddOpenIdConnect((OpenIdConnectOptions opts) => {
|
||||
// pull from appsettings
|
||||
var oidcConfig = builder.Configuration.GetRequiredSection("OpenIdConnect");
|
||||
opts.ClientId = oidcConfig.GetValue<string>("ClientId");
|
||||
opts.ClientSecret = oidcConfig.GetValue<string>("ClientSecret");
|
||||
opts.MetadataAddress = oidcConfig.GetValue<string>("MetadataUrl");
|
||||
opts.Authority = oidcConfig.GetValue<string>("Authority");
|
||||
|
||||
opts.GetClaimsFromUserInfoEndpoint = true;
|
||||
opts.SaveTokens = false;
|
||||
opts.ResponseType = "code";
|
||||
opts.Scope.Add("openid");
|
||||
|
||||
opts.Events = new OpenIdConnectEvents
|
||||
{
|
||||
OnUserInformationReceived = async ctx =>
|
||||
{
|
||||
// Create the user in UserManager for future authz
|
||||
var _userManager = ctx.HttpContext.RequestServices.GetRequiredService<UserManager<User>>();
|
||||
var curUser = ctx.Principal;
|
||||
var user = new User
|
||||
{
|
||||
Id = curUser?.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.NameIdentifier))
|
||||
?.Value,
|
||||
UserName = curUser?.Claims.FirstOrDefault(c => c.Type.Equals("preferred_username"))?.Value,
|
||||
Email = curUser?.Claims.FirstOrDefault(c => c.Type.Equals("email"))
|
||||
?.Value,
|
||||
EmailConfirmed = true, // always confirmed (or rather, expect the IdP to confirm it)
|
||||
Phones = null, // no phones or extensions by default
|
||||
Extensions = null,
|
||||
};
|
||||
if (user.Id == null) {
|
||||
throw new InvalidCredentialException($"Missing required OIDC claim \"{ClaimTypes.NameIdentifier}\"");
|
||||
}
|
||||
|
||||
if (await _userManager.FindByIdAsync(user.Id) == null) {
|
||||
var res = await _userManager.CreateAsync(user);
|
||||
if (res.Succeeded == false) {
|
||||
throw new InvalidCredentialException($"Creating identity failed: {res.Errors.FirstOrDefault()!.Description}");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// if dev, disable secure
|
||||
if (!builder.Environment.IsDevelopment()) return;
|
||||
opts.NonceCookie.SecurePolicy = CookieSecurePolicy.None;
|
||||
opts.NonceCookie.SameSite = SameSiteMode.Unspecified;
|
||||
opts.CorrelationCookie.SecurePolicy = CookieSecurePolicy.None;
|
||||
opts.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
|
||||
});
|
||||
builder.Services.AddAuthorization(opts =>
|
||||
{
|
||||
opts.AddPolicy("RequireAdmin",
|
||||
policy => policy.RequireRole("Administrator"));
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Home/Error");
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
app.UseHsts();
|
||||
} else {
|
||||
app.UseDeveloperExceptionPage();
|
||||
app.UseMigrationsEndPoint();
|
||||
}
|
||||
|
||||
using (var scope = app.Services.CreateScope()) {
|
||||
var services = scope.ServiceProvider;
|
||||
|
||||
var context = services.GetRequiredService<PTMXContext>();
|
||||
context.Database.EnsureCreated();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapControllerRoute(
|
||||
name: "default",
|
||||
pattern: "{controller=Home}/{action=Index}")
|
||||
.RequireAuthorization();
|
||||
|
||||
app.Run();
|
28
PhoneToolMX/Properties/launchSettings.json
Normal file
28
PhoneToolMX/Properties/launchSettings.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:5205",
|
||||
"sslPort": 44330
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"PhoneToolMX": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5001",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": false,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
PhoneToolMX/Views/Extension/DeleteConfirm.cshtml
Normal file
10
PhoneToolMX/Views/Extension/DeleteConfirm.cshtml
Normal file
|
@ -0,0 +1,10 @@
|
|||
@model PhoneToolMX.Models.ViewModels.ExtensionVM
|
||||
|
||||
@using (Html.BeginForm(FormMethod.Post)) {
|
||||
<p>Are you sure you want to delete extension @(Model?.ExtId)?</p>
|
||||
|
||||
<div align="center">
|
||||
<input type="hidden" name="id" value="@Model?.Id"/>
|
||||
<input type="submit" value="Yes, Delete"/>
|
||||
</div>
|
||||
}
|
6
PhoneToolMX/Views/Extension/Index.cshtml
Normal file
6
PhoneToolMX/Views/Extension/Index.cshtml
Normal file
|
@ -0,0 +1,6 @@
|
|||
@using PhoneToolMX.Helpers
|
||||
@model ICollection<PhoneToolMX.Models.ViewModels.ExtensionVM>
|
||||
|
||||
<p>This is a list of all extensions you can edit.</p>
|
||||
|
||||
@Html.MakeTable()
|
33
PhoneToolMX/Views/Extension/_Form.cshtml
Normal file
33
PhoneToolMX/Views/Extension/_Form.cshtml
Normal file
|
@ -0,0 +1,33 @@
|
|||
@model PhoneToolMX.Models.ViewModels.ExtensionVM
|
||||
|
||||
<!--suppress HtmlDeprecatedAttribute -->
|
||||
<p>Fill out the fields below, then click "Submit" to @ViewData["Action"] the line.</p>
|
||||
|
||||
@using (Html.BeginForm(FormMethod.Post)) {
|
||||
<table cellpadding="2" cellspacing="2" border="0" width="100%">
|
||||
@if (Model != null) {
|
||||
@Html.HiddenFor(m => m.Id)
|
||||
}
|
||||
<tr>
|
||||
<th>@Html.LabelFor(m => m.DirectoryName, "Name in Directory:")</th>
|
||||
<td>@Html.TextBoxFor(m => m.DirectoryName)@Html.ValidationMessageFor(m => m.DirectoryName)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>@Html.LabelFor(m => m.HoldMusic, "Hold Music:")</th>
|
||||
<td>@if (ViewBag.HoldMusics == null || ((ICollection<CustomData>)ViewBag.HoldMusics).Count == 0) {
|
||||
@Html.DropDownListFor(m => m.HoldMusic, new List<SelectListItem>(), "-- No music available --", new { @disabled=true })
|
||||
} else {
|
||||
@Html.DropDownListFor(m => m.HoldMusic, new SelectList(ViewBag.HoldMusics, "Id", "FriendlyName"))
|
||||
}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2">@Html.CheckBoxFor(m => m.Listed) @Html.LabelFor(m => m.Listed, "Show extension in global directory")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" align="right">
|
||||
<input type="reset" value="Reset" />
|
||||
<input type="submit" value="Submit" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
}
|
24
PhoneToolMX/Views/Home/Index.cshtml
Normal file
24
PhoneToolMX/Views/Home/Index.cshtml
Normal file
|
@ -0,0 +1,24 @@
|
|||
@{
|
||||
ViewData["Title"] = "Home Page";
|
||||
}
|
||||
|
||||
<p>Welcome to PhoneToolMX! Select one of the options from the sidebar to begin.</p>
|
||||
|
||||
<hr />
|
||||
|
||||
<h2>News</h2>
|
||||
|
||||
<h3>Oct 17, 2023 :: Version 1.0</h3>
|
||||
|
||||
<p>Finally, initial release! As far as user-facing features go, it's still pretty light.
|
||||
But the scaffolding is there for new features like:</p>
|
||||
|
||||
<ul>
|
||||
<li>Online Voicemail</li>
|
||||
<li>Custom Media (Wallpapers, Hold Music, Ringtones)</li>
|
||||
<li>Call Groups (Multiple Extensions -> One Number)</li>
|
||||
</ul>
|
||||
|
||||
<p>Even though this is v1.0, expect bugs, and please
|
||||
<a href="https://git.2ki.xyz/flurry/PhoneToolMX/issues">report them if you see them</a>.</p>
|
||||
@this.Url.Action("Error")
|
6
PhoneToolMX/Views/Home/Privacy.cshtml
Normal file
6
PhoneToolMX/Views/Home/Privacy.cshtml
Normal file
|
@ -0,0 +1,6 @@
|
|||
@{
|
||||
ViewData["Title"] = "Privacy Policy";
|
||||
}
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
|
||||
<p>Use this page to detail your site's privacy policy.</p>
|
7
PhoneToolMX/Views/Model/Index.cshtml
Normal file
7
PhoneToolMX/Views/Model/Index.cshtml
Normal file
|
@ -0,0 +1,7 @@
|
|||
@using PhoneToolMX.Helpers
|
||||
@model ICollection<PhoneModel>
|
||||
@{
|
||||
ViewData["Title"] = "Phone Models";
|
||||
}
|
||||
|
||||
@Html.MakeTable()
|
13
PhoneToolMX/Views/Phone/DeleteConfirm.cshtml
Normal file
13
PhoneToolMX/Views/Phone/DeleteConfirm.cshtml
Normal file
|
@ -0,0 +1,13 @@
|
|||
@model PhoneToolMX.Models.ViewModels.PhoneVM
|
||||
@{
|
||||
ViewData["Title"] = $"Deactivate {Model?.FriendlyName}";
|
||||
}
|
||||
|
||||
@using (Html.BeginForm(FormMethod.Post)) {
|
||||
<p>Are you sure you want to deactivate phone @(Model?.FriendlyName)? This will not delete any associated extensions.</p>
|
||||
|
||||
<div align="center">
|
||||
<input type="hidden" name="id" value="@Model?.Id"/>
|
||||
<input type="submit" value="Yes, Delete"/>
|
||||
</div>
|
||||
}
|
6
PhoneToolMX/Views/Phone/Index.cshtml
Normal file
6
PhoneToolMX/Views/Phone/Index.cshtml
Normal file
|
@ -0,0 +1,6 @@
|
|||
@using PhoneToolMX.Helpers
|
||||
@model ICollection<PhoneToolMX.Models.ViewModels.PhoneVM>
|
||||
|
||||
<p>This is a list of all phones you manage.</p>
|
||||
|
||||
@Html.MakeTable()
|
34
PhoneToolMX/Views/Phone/_Form.cshtml
Normal file
34
PhoneToolMX/Views/Phone/_Form.cshtml
Normal file
|
@ -0,0 +1,34 @@
|
|||
@model PhoneToolMX.Models.ViewModels.PhoneVM
|
||||
|
||||
<!--suppress HtmlDeprecatedAttribute -->
|
||||
<p>Fill out the fields below, then click "Submit" to @ViewData["Action"] the phone.</p>
|
||||
|
||||
@using (Html.BeginForm(FormMethod.Post)) {
|
||||
<table cellpadding="2" cellspacing="2" border="0" width="100%">
|
||||
@if (Model != null) {
|
||||
@Html.HiddenFor(m => m.Id)
|
||||
}
|
||||
<tr>
|
||||
<th>@Html.LabelFor(m => m.MacAddress, "MAC Address:")</th>
|
||||
<td>@Html.TextBoxFor(m => m.MacAddress)@Html.ValidationMessageFor(m => m.MacAddress)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>@Html.LabelFor(m => m.FriendlyName, "Phone Name:")</th>
|
||||
<td>@Html.TextBoxFor(m => m.FriendlyName)@Html.ValidationMessageFor(m => m.FriendlyName)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>@Html.LabelFor(m => m.Model, "Phone Model:")</th>
|
||||
<td>@Html.DropDownListFor(m => m.Model, new SelectList(ViewBag.ModelNumbers, "Id", "ModelName", ViewBag.CurrentModel))@Html.ValidationMessageFor(m => m.Model)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>@Html.LabelFor(m => m.Extensions, "Extensions:")</th>
|
||||
<td>@Html.ListBoxFor(m => m.Extensions, new MultiSelectList(ViewBag.MyExtensions, "Id", "ListViewName", ViewBag.SelectedExtensions))
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan="2" align="right">
|
||||
<input type="reset" value="Reset" />
|
||||
<input type="submit" value="Submit" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
}
|
14
PhoneToolMX/Views/Shared/Error.cshtml
Normal file
14
PhoneToolMX/Views/Shared/Error.cshtml
Normal file
|
@ -0,0 +1,14 @@
|
|||
@model ErrorViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Something went wrong!";
|
||||
}
|
||||
|
||||
<p>An unknown error occurred while processing your request.</p>
|
||||
|
||||
@if (Model?.ShowRequestId == true) {
|
||||
<p>Provide the following request ID to the application administrator, or <a href="https://git.2ki.xyz/flurry/PhoneToolMX/issues">include it in a bug report</a>:</p>
|
||||
|
||||
<ul>
|
||||
<li>@Model.RequestId</li>
|
||||
</ul>
|
||||
}
|
89
PhoneToolMX/Views/Shared/_Layout.cshtml
Normal file
89
PhoneToolMX/Views/Shared/_Layout.cshtml
Normal file
|
@ -0,0 +1,89 @@
|
|||
@using PhoneToolMX.Helpers
|
||||
@using System.Text.Json
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
@{
|
||||
var userIdent = (System.Security.Claims.ClaimsIdentity)User.Identity;
|
||||
}
|
||||
|
||||
<html lang="en">
|
||||
@* ReSharper disable once MissingTitleTag *@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
||||
<link rel="stylesheet" href="/main.css" />
|
||||
<title>@ViewData["Title"] - PhoneToolMX</title>
|
||||
</head>
|
||||
<body>
|
||||
<table id="main" cellspacing="0" cellpadding="2">
|
||||
<!-- header img -->
|
||||
<tr>
|
||||
<td id="header" colspan="3">
|
||||
<img alt="Website banner" src="/images/banner.gif"/></td>
|
||||
</tr>
|
||||
<!-- nav sidebar -->
|
||||
<tr>
|
||||
<td rowspan="2" width="235" id="nav">
|
||||
<h4 style="margin-top:0; margin-bottom:0;">Navigation</h4>
|
||||
<hr size="1" width="100%"/>
|
||||
<ul>
|
||||
<li><a href="@Url.Action("Index", "Home")">Home</a></li>
|
||||
<li>Extensions
|
||||
<ul>
|
||||
<li><a href="@Url.Action("Index", "Extension")">My Extensions</a></li>
|
||||
<li><a href="@Url.Action("New", "Extension")">Create New Extension</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Phones
|
||||
<ul>
|
||||
<li><a href="@Url.Action("Index", "Phone")">My Phones</a></li>
|
||||
<li><a href="@Url.Action("New", "Phone")">Provision New Phone</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Media
|
||||
<ul>
|
||||
<li><a href="#">Ringtones and Hold Music</a></li>
|
||||
<li><a href="#">Wallpapers</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
<!-- context header -->
|
||||
<td width="237" height="10" id="context">
|
||||
@await RenderSectionAsync("CtxOptions", false)
|
||||
<!--
|
||||
<form style="padding:0;margin:0;" action="" method="GET">
|
||||
<label style="vertical-align:middle;" for="ext">TODO-- EDIT THIS PER VIEW: </label>
|
||||
<select name="ext">
|
||||
<option value="1001">1001</option></select> <input type="submit" value=" Go "/><br>
|
||||
</form>
|
||||
-->
|
||||
</td>
|
||||
<td width="237" height="10" id="userinfo">
|
||||
@if (userIdent != null) {
|
||||
<span>Hello, @userIdent.Name!</span> @("[")<a href="#">Logout</a>@("]")
|
||||
} else {
|
||||
<a href="#">Login</a>
|
||||
}
|
||||
</td>
|
||||
<!-- body text -->
|
||||
<tr>
|
||||
<td width="475" id="content" colspan="2">
|
||||
<h2>@ViewData["Title"]</h2>
|
||||
@if (TempData["Message"] != null) {
|
||||
var msg = JsonSerializer.Deserialize<FormMessage>((string)TempData["Message"]);
|
||||
<div class="status @msg!.CssClassName()">
|
||||
@msg.Message
|
||||
</div>
|
||||
}
|
||||
@RenderBody()
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td id="footer" align="center" colspan="4">
|
||||
Best viewed on a display with 800x600 or higher resolution with <a href="#">Netscape Navigator</a>.
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
49
PhoneToolMX/Views/Shared/_Layout.cshtml.css
Normal file
49
PhoneToolMX/Views/Shared/_Layout.cshtml.css
Normal file
|
@ -0,0 +1,49 @@
|
|||
/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
|
||||
for details on configuring this project to bundle and minify static web assets. */
|
||||
|
||||
a.navbar-brand {
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0077cc;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.nav-pills .nav-link.active, .nav-pills .show > .nav-link {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.border-top {
|
||||
border-top: 1px solid #e5e5e5;
|
||||
}
|
||||
.border-bottom {
|
||||
border-bottom: 1px solid #e5e5e5;
|
||||
}
|
||||
|
||||
.box-shadow {
|
||||
box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05);
|
||||
}
|
||||
|
||||
button.accept-policy {
|
||||
font-size: 1rem;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
line-height: 60px;
|
||||
}
|
||||
|
3
PhoneToolMX/Views/_ViewImports.cshtml
Normal file
3
PhoneToolMX/Views/_ViewImports.cshtml
Normal file
|
@ -0,0 +1,3 @@
|
|||
@using PhoneToolMX
|
||||
@using PhoneToolMX.Models
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
3
PhoneToolMX/Views/_ViewStart.cshtml
Normal file
3
PhoneToolMX/Views/_ViewStart.cshtml
Normal file
|
@ -0,0 +1,3 @@
|
|||
@{
|
||||
Layout = "_Layout";
|
||||
}
|
18
PhoneToolMX/appsettings.Development.json
Normal file
18
PhoneToolMX/appsettings.Development.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"Urls": "http://localhost:5001",
|
||||
"ConnectionStrings": {
|
||||
"DbConnection": "Host=yuuka.i.2ki.xyz; Database=ptmx_dev; Username=flurry; Password=PeE4PKpMUE2qPja4FyGrvB8FVArZKWa5YReFbfRW649chx38"
|
||||
},
|
||||
"OpenIdConnect": {
|
||||
"ClientId": "3f2b1348-6d6b-4332-8c43-0a7a5707ccbf",
|
||||
"ClientSecret": "gto_ljwf22pbzaop7tv6nv2upf6binv7siybye7xu5ytecfrdlils6ra",
|
||||
"MetadataUrl": "https://git.2ki.xyz/.well-known/openid-configuration",
|
||||
"Authority": "https://git.2ki.xyz"
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
9
PhoneToolMX/appsettings.json
Normal file
9
PhoneToolMX/appsettings.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
BIN
PhoneToolMX/wwwroot/favicon.ico
Normal file
BIN
PhoneToolMX/wwwroot/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
BIN
PhoneToolMX/wwwroot/images/banner.gif
Normal file
BIN
PhoneToolMX/wwwroot/images/banner.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
BIN
PhoneToolMX/wwwroot/images/del_x16.gif
Normal file
BIN
PhoneToolMX/wwwroot/images/del_x16.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 164 B |
BIN
PhoneToolMX/wwwroot/images/edit_x16.gif
Normal file
BIN
PhoneToolMX/wwwroot/images/edit_x16.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 220 B |
182
PhoneToolMX/wwwroot/main.css
Normal file
182
PhoneToolMX/wwwroot/main.css
Normal file
|
@ -0,0 +1,182 @@
|
|||
body {
|
||||
background-color:#333333;
|
||||
}
|
||||
|
||||
table#main {
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
width:722px;
|
||||
color:#000000;
|
||||
font-family:Geneva, Arial, Helvetica, sans-serif;
|
||||
font-size:12px;
|
||||
border:none;
|
||||
}
|
||||
|
||||
table#main td {
|
||||
background-color:#CCCCCC;
|
||||
padding:0.45em;
|
||||
border: 1px solid #000000;
|
||||
}
|
||||
|
||||
/*
|
||||
table#main table td {
|
||||
padding:unset;
|
||||
border:none;
|
||||
}
|
||||
*/
|
||||
|
||||
form table input[type="text"],
|
||||
form table select {
|
||||
width:100%;
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
}
|
||||
|
||||
table#main td#header {
|
||||
background-color:#ffffff;
|
||||
height:150px;
|
||||
padding: 0;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
table#main td#header img {
|
||||
width:720px;
|
||||
height:150px;
|
||||
padding:0;
|
||||
display:block;
|
||||
margin:1px;
|
||||
}
|
||||
|
||||
table#main td#nav {
|
||||
background-color:#ccccff;
|
||||
}
|
||||
|
||||
table#main td#nav, table#main td#content {
|
||||
vertical-align:top;
|
||||
}
|
||||
|
||||
table#main td#nav ul {
|
||||
list-style-type:square;
|
||||
margin: 0.5em 0 0.5em 1em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table#main td#nav ul li {
|
||||
margin:0 0 0.25em 0.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table#main td#footer {
|
||||
background-color:#000000;
|
||||
color:#ffffff;
|
||||
font-size:10px;
|
||||
}
|
||||
|
||||
table#main td#footer a:link {
|
||||
color:#00CCFF;
|
||||
}
|
||||
|
||||
table#main td#context,
|
||||
table#main td#userinfo {
|
||||
background-color:#000000;
|
||||
color: #ffffff;
|
||||
height:1.5em;
|
||||
vertical-align:middle;
|
||||
}
|
||||
|
||||
table#main td#userinfo a:link,
|
||||
table#main td#userinfo a:visited,
|
||||
table#main td#userinfo a:active,
|
||||
table#main td#footer a:link,
|
||||
table#main td#footer a:visited,
|
||||
table#main td#footer a:active {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
table#main td#context form label,
|
||||
table#main td#context form select,
|
||||
table#main td#context form input {
|
||||
display:table-cell;
|
||||
vertical-align:middle;
|
||||
}
|
||||
|
||||
table#main td#context form select,
|
||||
table#main td#context form input {
|
||||
height:1.5em;
|
||||
font-size:1em;
|
||||
padding:0;
|
||||
}
|
||||
|
||||
table#main td#userinfo {
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
table#main td#content {
|
||||
padding:1em;
|
||||
}
|
||||
|
||||
td#content h1,
|
||||
td#content h2,
|
||||
td#content h3 {
|
||||
margin-top:0.25em;
|
||||
margin-bottom:0.25em;
|
||||
}
|
||||
|
||||
table#listview {
|
||||
width: 100%;
|
||||
border: 1px outset;
|
||||
border-spacing: 1px;
|
||||
background-color: #666;
|
||||
}
|
||||
|
||||
table#listview td,
|
||||
table#listview th {
|
||||
padding: 0.25em;
|
||||
border: 1px inset;
|
||||
}
|
||||
|
||||
table#listview thead {
|
||||
background-color: #aaa;
|
||||
border: 1px #aaa inset;
|
||||
}
|
||||
|
||||
table#main form table td {
|
||||
border: unset;
|
||||
}
|
||||
|
||||
div.status {
|
||||
border: 2px ridge;
|
||||
margin: 0.25em 0;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
div.status.msg-success {
|
||||
border-color: #4a4;
|
||||
color: #161;
|
||||
background-color: #bfb;
|
||||
}
|
||||
|
||||
div.status.msg-warning {
|
||||
border-color: #aa4;
|
||||
color: #661;
|
||||
background-color: #ffb;
|
||||
}
|
||||
|
||||
div.status.msg-error {
|
||||
border-color: #a44;
|
||||
color: #611;
|
||||
background-color: #fbb;
|
||||
}
|
||||
|
||||
div.status.msg-info {
|
||||
border-color: #444;
|
||||
color: #111;
|
||||
background-color: #bbb;
|
||||
}
|
||||
|
||||
span.field-validation-error {
|
||||
width: 100%;
|
||||
display: block;
|
||||
font-size: 10px;
|
||||
color: #c00;
|
||||
}
|
7
global.json
Normal file
7
global.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"sdk": {
|
||||
"version": "6.0.0",
|
||||
"rollForward": "latestMajor",
|
||||
"allowPrerelease": true
|
||||
}
|
||||
}
|
28
ptmx-asp.sln
Normal file
28
ptmx-asp.sln
Normal file
|
@ -0,0 +1,28 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.30114.105
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhoneToolMX", "PhoneToolMX\PhoneToolMX.csproj", "{130DD8E2-5E99-47A9-8B30-2FF54A0DA89D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhoneToolMX.Models", "PhoneToolMX.Models\PhoneToolMX.Models.csproj", "{84FB1CE2-A4B6-4251-9427-8AEB175D6874}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{130DD8E2-5E99-47A9-8B30-2FF54A0DA89D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{130DD8E2-5E99-47A9-8B30-2FF54A0DA89D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{130DD8E2-5E99-47A9-8B30-2FF54A0DA89D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{130DD8E2-5E99-47A9-8B30-2FF54A0DA89D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{84FB1CE2-A4B6-4251-9427-8AEB175D6874}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{84FB1CE2-A4B6-4251-9427-8AEB175D6874}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{84FB1CE2-A4B6-4251-9427-8AEB175D6874}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{84FB1CE2-A4B6-4251-9427-8AEB175D6874}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
3
ptmx-asp.sln.DotSettings
Normal file
3
ptmx-asp.sln.DotSettings
Normal file
|
@ -0,0 +1,3 @@
|
|||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=exts/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=PTMX/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
Loading…
Reference in a new issue