diff --git a/BillingCore/BillingFactory.cs b/BillingCore/BillingFactory.cs index c2a36ff..f6d0135 100644 --- a/BillingCore/BillingFactory.cs +++ b/BillingCore/BillingFactory.cs @@ -9,7 +9,7 @@ namespace Teknik.BillingCore { public static class BillingFactory { - public static BillingService GetStorageService(BillingConfig config) + public static BillingService GetBillingService(BillingConfig config) { switch (config.Type) { diff --git a/BillingCore/BillingService.cs b/BillingCore/BillingService.cs index e5a8588..d301d16 100644 --- a/BillingCore/BillingService.cs +++ b/BillingCore/BillingService.cs @@ -1,4 +1,5 @@ -using System; +using Microsoft.AspNetCore.Http; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -18,7 +19,7 @@ namespace Teknik.BillingCore } public abstract object GetCustomer(string id); - public abstract bool CreateCustomer(string email); + public abstract string CreateCustomer(string username, string email); public abstract List GetProductList(); public abstract Product GetProduct(string productId); @@ -28,10 +29,15 @@ namespace Teknik.BillingCore public abstract List GetSubscriptionList(string customerId); public abstract Subscription GetSubscription(string subscriptionId); - public abstract Tuple CreateSubscription(string customerId, string priceId); - public abstract bool EditSubscription(Subscription subscription); - public abstract bool RemoveSubscription(string subscriptionId); + public abstract Subscription CreateSubscription(string customerId, string priceId); + public abstract Subscription EditSubscriptionPrice(string subscriptionId, string priceId); + public abstract bool CancelSubscription(string subscriptionId); - public abstract void SyncSubscriptions(); + public abstract CheckoutSession CreateCheckoutSession(string customerId, string priceId, string successUrl, string cancelUrl); + public abstract CheckoutSession GetCheckoutSession(string sessionId); + + public abstract Task ParseEvent(HttpRequest request); + public abstract CheckoutSession ProcessCheckoutCompletedEvent(Event e); + public abstract Customer ProcessCustomerEvent(Event e); } } diff --git a/BillingCore/Models/CheckoutResult.cs b/BillingCore/Models/CheckoutResult.cs new file mode 100644 index 0000000..54d145f --- /dev/null +++ b/BillingCore/Models/CheckoutResult.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Teknik.BillingCore.Models +{ + public class CheckoutResult + { + public string CustomerId { get; set; } + + public string SubscriptionId { get; set; } + + public PaymentStatus PaymentStatus { get; set; } + } +} diff --git a/BillingCore/Models/CheckoutSession.cs b/BillingCore/Models/CheckoutSession.cs new file mode 100644 index 0000000..b6d3d33 --- /dev/null +++ b/BillingCore/Models/CheckoutSession.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Teknik.BillingCore.Models +{ + public class CheckoutSession + { + public string PaymentIntentId { get; set; } + public string CustomerId { get; set; } + public string SubscriptionId { get; set; } + public PaymentStatus PaymentStatus { get; set; } + public string Url { get; set; } + } +} diff --git a/BillingCore/Models/Customer.cs b/BillingCore/Models/Customer.cs new file mode 100644 index 0000000..9525113 --- /dev/null +++ b/BillingCore/Models/Customer.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Teknik.BillingCore.Models +{ + public class Customer + { + public string CustomerId { get; set; } + + public List Subscriptions { get; set; } + + public Customer() + { + Subscriptions = new List(); + } + } +} diff --git a/BillingCore/Models/Event.cs b/BillingCore/Models/Event.cs new file mode 100644 index 0000000..b1e2eb7 --- /dev/null +++ b/BillingCore/Models/Event.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Teknik.BillingCore.Models +{ + public class Event + { + public EventType EventType { get; set; } + + public object Data { get; set; } + } +} diff --git a/BillingCore/Models/EventType.cs b/BillingCore/Models/EventType.cs new file mode 100644 index 0000000..e2e5c1d --- /dev/null +++ b/BillingCore/Models/EventType.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Teknik.BillingCore.Models +{ + public enum EventType + { + Unknown, + CheckoutComplete, + SubscriptionDeleted, + SubscriptionUpdated + } +} diff --git a/BillingCore/Models/PaymentStatus.cs b/BillingCore/Models/PaymentStatus.cs new file mode 100644 index 0000000..fb66f00 --- /dev/null +++ b/BillingCore/Models/PaymentStatus.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Teknik.BillingCore.Models +{ + public enum PaymentStatus + { + Paid, + Unpaid, + NoPaymentRequired + } +} diff --git a/BillingCore/Models/Price.cs b/BillingCore/Models/Price.cs index 639bf0c..1eb2755 100644 --- a/BillingCore/Models/Price.cs +++ b/BillingCore/Models/Price.cs @@ -16,6 +16,7 @@ namespace Teknik.BillingCore.Models public decimal? Amount { get; set; } public string Currency { get; set; } public long Storage { get; set; } + public long FileSize { get; set; } public Interval Interval { get; set; } } } diff --git a/BillingCore/Models/Subscription.cs b/BillingCore/Models/Subscription.cs index 8094875..afb4d8c 100644 --- a/BillingCore/Models/Subscription.cs +++ b/BillingCore/Models/Subscription.cs @@ -12,5 +12,6 @@ namespace Teknik.BillingCore.Models public string CustomerId { get; set; } public SubscriptionStatus Status { get; set; } public List Prices { get; set; } + public string ClientSecret { get; set; } } } diff --git a/BillingCore/StripeService.cs b/BillingCore/StripeService.cs index f0e4921..9c3996e 100644 --- a/BillingCore/StripeService.cs +++ b/BillingCore/StripeService.cs @@ -1,9 +1,12 @@ -using Stripe; +using Microsoft.AspNetCore.Http; +using Stripe; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; +using System.Web; using Teknik.BillingCore.Models; using Teknik.Configuration; @@ -16,21 +19,33 @@ namespace Teknik.BillingCore StripeConfiguration.ApiKey = config.StripeSecretApiKey; } - public override object GetCustomer(string id) + public override string GetCustomer(string email) { - var service = new CustomerService(); - return service.Get(id); + if (!string.IsNullOrEmpty(email)) + { + var service = new CustomerService(); + var foundCustomer = service.Get(email); + if (foundCustomer != null) + return foundCustomer.Id; + } + + return null; } - public override bool CreateCustomer(string email) + public override string CreateCustomer(string username, string email) { + if (string.IsNullOrEmpty(username)) + throw new ArgumentNullException("username"); + var options = new CustomerCreateOptions { + Name = username, Email = email, + Description = $"Customer for account {username}" }; var service = new CustomerService(); var customer = service.Create(options); - return customer != null; + return customer.Id; } public override List GetProductList() @@ -52,10 +67,13 @@ namespace Teknik.BillingCore public override Models.Product GetProduct(string productId) { - var productService = new ProductService(); - Stripe.Product product = productService.Get(productId); - if (product != null) - return ConvertProduct(product); + if (!string.IsNullOrEmpty(productId)) + { + var productService = new ProductService(); + Stripe.Product product = productService.Get(productId); + if (product != null) + return ConvertProduct(product); + } return null; } @@ -63,31 +81,39 @@ namespace Teknik.BillingCore public override List GetPriceList(string productId) { var foundPrices = new List(); - var options = new PriceListOptions + if (!string.IsNullOrEmpty(productId)) { - Active = true, - Product = productId - }; - - var priceService = new PriceService(); - var priceList = priceService.List(options); - if (priceList != null) - { - foreach (var price in priceList) + var options = new PriceListOptions { - foundPrices.Add(ConvertPrice(price)); + Active = true, + Product = productId + }; + options.AddExpand("data.product"); + + var priceService = new PriceService(); + var priceList = priceService.List(options); + if (priceList != null) + { + foreach (var price in priceList) + { + foundPrices.Add(ConvertPrice(price)); + } } } + return foundPrices; } public override Models.Price GetPrice(string priceId) { - - var priceService = new PriceService(); - var price = priceService.Get(priceId); - if (price != null) - return ConvertPrice(price); + if (!string.IsNullOrEmpty(priceId)) + { + var options = new PriceGetOptions(); + var priceService = new PriceService(); + var price = priceService.Get(priceId, options); + if (price != null) + return ConvertPrice(price); + } return null; } @@ -118,62 +144,176 @@ namespace Teknik.BillingCore public override Models.Subscription GetSubscription(string subscriptionId) { - var subService = new SubscriptionService(); - var sub = subService.Get(subscriptionId); - if (sub != null) - return ConvertSubscription(sub); + if (!string.IsNullOrEmpty(subscriptionId)) + { + var subService = new SubscriptionService(); + var sub = subService.Get(subscriptionId); + if (sub != null) + return ConvertSubscription(sub); + } return null; } - public override Tuple CreateSubscription(string customerId, string priceId) + public override Models.Subscription CreateSubscription(string customerId, string priceId) { - // Create the subscription. Note we're expanding the Subscription's - // latest invoice and that invoice's payment_intent - // so we can pass it to the front end to confirm the payment - var subscriptionOptions = new SubscriptionCreateOptions + if (!string.IsNullOrEmpty(customerId) && + !string.IsNullOrEmpty(priceId)) { - Customer = customerId, - Items = new List + // Create the subscription. Note we're expanding the Subscription's + // latest invoice and that invoice's payment_intent + // so we can pass it to the front end to confirm the payment + var subscriptionOptions = new SubscriptionCreateOptions { - new SubscriptionItemOptions + Customer = customerId, + Items = new List + { + new SubscriptionItemOptions + { + Price = priceId, + }, + }, + PaymentBehavior = "default_incomplete", + CancelAtPeriodEnd = false + }; + subscriptionOptions.AddExpand("latest_invoice.payment_intent"); + var subscriptionService = new SubscriptionService(); + var subscription = subscriptionService.Create(subscriptionOptions); + + return ConvertSubscription(subscription); + } + return null; + } + + public override Models.Subscription EditSubscriptionPrice(string subscriptionId, string priceId) + { + if (!string.IsNullOrEmpty(subscriptionId)) + { + var subscriptionService = new SubscriptionService(); + var subscription = subscriptionService.Get(subscriptionId); + if (subscription != null) + { + var subscriptionOptions = new SubscriptionUpdateOptions() + { + Items = new List + { + new SubscriptionItemOptions + { + Id = subscription.Items.Data[0].Id, + Price = priceId, + }, + }, + CancelAtPeriodEnd = false, + ProrationBehavior = "create_prorations" + }; + subscriptionOptions.AddExpand("latest_invoice.payment_intent"); + var result = subscriptionService.Update(subscriptionId, subscriptionOptions); + if (result != null) + return ConvertSubscription(result); + } + } + return null; + } + + public override bool CancelSubscription(string subscriptionId) + { + if (!string.IsNullOrEmpty(subscriptionId)) + { + var cancelOptions = new SubscriptionCancelOptions() + { + InvoiceNow = true + }; + var subscriptionService = new SubscriptionService(); + var subscription = subscriptionService.Cancel(subscriptionId, cancelOptions); + return subscription.Status == "canceled"; + } + return false; + } + + public override CheckoutSession CreateCheckoutSession(string customerId, string priceId, string successUrl, string cancelUrl) + { + // Modify Success URL to include session ID variable + var uriBuilder = new UriBuilder(successUrl); + var paramValues = HttpUtility.ParseQueryString(uriBuilder.Query); + paramValues.Add("session_id", "{CHECKOUT_SESSION_ID}"); + uriBuilder.Query = paramValues.ToString(); + successUrl = uriBuilder.Uri.ToString(); + + var checkoutService = new Stripe.Checkout.SessionService(); + var sessionOptions = new Stripe.Checkout.SessionCreateOptions() + { + LineItems = new List() + { + new Stripe.Checkout.SessionLineItemOptions() { Price = priceId, - }, + Quantity = 1 + } }, - PaymentBehavior = "default_incomplete", + PaymentMethodTypes = new List() + { + "card" + }, + Mode = "subscription", + SuccessUrl = successUrl, + CancelUrl = cancelUrl, + Customer = customerId }; - subscriptionOptions.AddExpand("latest_invoice.payment_intent"); - var subscriptionService = new SubscriptionService(); + sessionOptions.AddExpand("customer"); + var session = checkoutService.Create(sessionOptions); + return ConvertCheckoutSession(session); + } + + public override async Task ParseEvent(HttpRequest request) + { + var json = await new StreamReader(request.Body).ReadToEndAsync(); + try { - Stripe.Subscription subscription = subscriptionService.Create(subscriptionOptions); + var stripeEvent = EventUtility.ConstructEvent( + json, + request.Headers["Stripe-Signature"], + Config.StripeWebhookSecret + ); - return new Tuple(true, subscription.Id, subscription.LatestInvoice.PaymentIntent.ClientSecret); + return ConvertEvent(stripeEvent); } - catch (StripeException e) + catch (StripeException) { - return new Tuple(false, $"Failed to create subscription. {e}", null); } + return null; } - public override bool EditSubscription(Models.Subscription subscription) + public override CheckoutSession ProcessCheckoutCompletedEvent(Models.Event ev) { - throw new NotImplementedException(); + // Handle the checkout.session.completed event + var session = ev.Data as Stripe.Checkout.Session; + + return ConvertCheckoutSession(session); } - public override bool RemoveSubscription(string subscriptionId) + public override Models.Customer ProcessCustomerEvent(Models.Event ev) { - throw new NotImplementedException(); + // Handle the checkout.session.completed event + var customer = ev.Data as Stripe.Customer; + + return ConvertCustomer(customer); } - public override void SyncSubscriptions() + public override CheckoutSession GetCheckoutSession(string sessionId) { - throw new NotImplementedException(); + var checkoutService = new Stripe.Checkout.SessionService(); + var sessionOptions = new Stripe.Checkout.SessionGetOptions(); + sessionOptions.AddExpand("customer"); + var session = checkoutService.Get(sessionId, sessionOptions); + + return ConvertCheckoutSession(session); } private Models.Product ConvertProduct(Stripe.Product product) { + if (product == null) + return null; return new Models.Product() { ProductId = product.Id, @@ -185,6 +325,8 @@ namespace Teknik.BillingCore private Models.Price ConvertPrice(Stripe.Price price) { + if (price == null) + return null; var interval = Interval.Once; if (price.Type == "recurring") { @@ -216,11 +358,15 @@ namespace Teknik.BillingCore convPrice.Amount = price.UnitAmountDecimal / 100; if (price.Metadata.ContainsKey("storage")) convPrice.Storage = long.Parse(price.Metadata["storage"]); + if (price.Metadata.ContainsKey("fileSize")) + convPrice.FileSize = long.Parse(price.Metadata["fileSize"]); return convPrice; } private Models.Subscription ConvertSubscription(Stripe.Subscription subscription) { + if (subscription == null) + return null; var status = SubscriptionStatus.Incomplete; switch (subscription.Status) { @@ -259,7 +405,77 @@ namespace Teknik.BillingCore Id = subscription.Id, CustomerId = subscription.CustomerId, Status = status, - Prices = prices + Prices = prices, + ClientSecret = subscription.LatestInvoice?.PaymentIntent?.ClientSecret + }; + } + + private CheckoutSession ConvertCheckoutSession(Stripe.Checkout.Session session) + { + if (session == null) + return null; + + + var paymentStatus = PaymentStatus.Unpaid; + switch (session.PaymentStatus) + { + case "paid": + paymentStatus = PaymentStatus.Paid; + break; + case "unpaid": + paymentStatus = PaymentStatus.Unpaid; + break; + case "no_payment_required": + paymentStatus = PaymentStatus.NoPaymentRequired; + break; + } + + return new CheckoutSession() + { + PaymentIntentId = session.PaymentIntentId, + CustomerId = session.Customer.Id, + SubscriptionId = session.SubscriptionId, + PaymentStatus = paymentStatus, + Url = session.Url + }; + } + + private Models.Customer ConvertCustomer(Stripe.Customer customer) + { + var returnCust = new Models.Customer() + { + CustomerId = customer.Id + }; + + if (customer.Subscriptions.Any()) + returnCust.Subscriptions = customer.Subscriptions.Select(s => ConvertSubscription(s)).ToList(); + + return returnCust; + } + + private Models.Event ConvertEvent(Stripe.Event ev) + { + if (ev == null) + return null; + + var eventType = EventType.Unknown; + switch (ev.Type) + { + case Events.CheckoutSessionCompleted: + eventType = EventType.CheckoutComplete; + break; + case Events.CustomerSubscriptionDeleted: + eventType = EventType.SubscriptionDeleted; + break; + case Events.CustomerSubscriptionUpdated: + eventType = EventType.SubscriptionUpdated; + break; + } + + return new Models.Event() + { + EventType = eventType, + Data = ev.Data.Object }; } } diff --git a/BillingService/Program.cs b/BillingService/Program.cs index 30de78e..c842f44 100644 --- a/BillingService/Program.cs +++ b/BillingService/Program.cs @@ -1,9 +1,14 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Reflection; using CommandLine; using Microsoft.EntityFrameworkCore; +using Teknik.Areas.Users.Models; +using Teknik.Areas.Users.Utility; +using Teknik.BillingCore; +using Teknik.BillingCore.Models; using Teknik.Configuration; using Teknik.Data; using Teknik.Utilities; @@ -43,6 +48,7 @@ namespace Teknik.BillingService if (options.SyncSubscriptions) { // Sync subscription information + SyncSubscriptions(config, db); } } @@ -65,6 +71,44 @@ namespace Teknik.BillingService return -1; } + public static void SyncSubscriptions(Config config, TeknikEntities db) + { + // Get Biling Service + var billingService = BillingFactory.GetBillingService(config.BillingConfig); + + foreach (var user in db.Users) + { + string email = UserHelper.GetUserEmailAddress(config, user.Username); + if (user.BillingCustomer != null) + { + // get the subscriptions for this user + var subscriptions = billingService.GetSubscriptionList(user.BillingCustomer.CustomerId); + var uploadPrice = subscriptions.SelectMany(s => s.Prices).FirstOrDefault(p => p.ProductId == config.BillingConfig.UploadProductId); + if (uploadPrice != null) + { + // Process Upload Settings + user.UploadSettings.MaxUploadStorage = uploadPrice.Storage; + user.UploadSettings.MaxUploadFileSize = uploadPrice.FileSize; + } + var emailPrice = subscriptions.SelectMany(s => s.Prices).FirstOrDefault(p => p.ProductId == config.BillingConfig.EmailProductId); + if (emailPrice != null) + { + UserHelper.EnableUserEmail(config, email); + UserHelper.EditUserEmailMaxSize(config, email, (int)emailPrice.Storage); + } + } + else + { + // No customer, so let's reset their info + user.UploadSettings.MaxUploadStorage = config.UploadConfig.MaxStorage; + user.UploadSettings.MaxUploadFileSize = config.UploadConfig.MaxUploadFileSize; + UserHelper.DisableUserEmail(config, email); + } + db.Entry(user).State = EntityState.Modified; + db.SaveChanges(); + } + } + public static void Output(string message) { Console.WriteLine(message); diff --git a/Configuration/BillingConfig.cs b/Configuration/BillingConfig.cs index 93e86db..f5c9eb9 100644 --- a/Configuration/BillingConfig.cs +++ b/Configuration/BillingConfig.cs @@ -11,6 +11,7 @@ namespace Teknik.Configuration public BillingType Type { get; set; } public string StripePublishApiKey { get; set; } public string StripeSecretApiKey { get; set; } + public string StripeWebhookSecret { get; set; } public string UploadProductId { get; set; } public string EmailProductId { get; set; } @@ -20,6 +21,7 @@ namespace Teknik.Configuration Type = BillingType.Stripe; StripePublishApiKey = null; StripeSecretApiKey = null; + StripeWebhookSecret = null; } } } diff --git a/Configuration/UploadConfig.cs b/Configuration/UploadConfig.cs index a939e7e..f136460 100644 --- a/Configuration/UploadConfig.cs +++ b/Configuration/UploadConfig.cs @@ -7,18 +7,12 @@ namespace Teknik.Configuration { public bool UploadEnabled { get; set; } public bool DownloadEnabled { get; set; } - // Max upload size in bytes - public long MaxUploadSize { get; set; } - // Max Upload Size for basic users - public long MaxUploadSizeBasic { get; set; } - // Max Upload Size for premium users - public long MaxUploadSizePremium { get; set; } - // Gets the maximum download size before they are forced to the download page - public long MaxDownloadSize { get; set; } - // Maximum total size for basic users - public long MaxTotalSizeBasic { get; set; } - // Maximum total size for basic users - public long MaxTotalSizePremium { get; set; } + // Max upload size in bytes for free/anonymous users + public long MaxUploadFileSize { get; set; } + // Maximum file size before they are forced to the download page + public long MaxDownloadFileSize { get; set; } + // Maximum storage for free users + public long MaxStorage { get; set; } public int UrlLength { get; set; } public int DeleteKeyLength { get; set; } public int KeySize { get; set; } @@ -45,12 +39,9 @@ namespace Teknik.Configuration { UploadEnabled = true; DownloadEnabled = true; - MaxUploadSize = 100000000; - MaxUploadSizeBasic = 100000000; - MaxUploadSizePremium = 100000000; - MaxDownloadSize = 100000000; - MaxTotalSizeBasic = 1000000000; - MaxTotalSizePremium = 5000000000; + MaxUploadFileSize = 1073741824; + MaxDownloadFileSize = 1073741824; + MaxStorage = 5368709120; UrlLength = 5; DeleteKeyLength = 24; KeySize = 256; diff --git a/ServiceWorker/Program.cs b/ServiceWorker/Program.cs index 5ee3145..e30239d 100644 --- a/ServiceWorker/Program.cs +++ b/ServiceWorker/Program.cs @@ -163,15 +163,12 @@ namespace Teknik.ServiceWorker byte[] ivBytes = Encoding.UTF8.GetBytes(upload.IV); - long maxUploadSize = config.UploadConfig.MaxUploadSize; + long maxUploadSize = config.UploadConfig.MaxUploadFileSize; if (upload.User != null) { - maxUploadSize = config.UploadConfig.MaxUploadSizeBasic; - IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(config, upload.User.Username); - if (userInfo.AccountType == AccountType.Premium) - { - maxUploadSize = config.UploadConfig.MaxUploadSizePremium; - } + // Check account total limits + if (upload.User.UploadSettings.MaxUploadFileSize != null) + maxUploadSize = upload.User.UploadSettings.MaxUploadFileSize.Value; } using (AesCounterStream aesStream = new AesCounterStream(fileStream, false, keyBytes, ivBytes)) diff --git a/ServiceWorker/TeknikMigration.cs b/ServiceWorker/TeknikMigration.cs index c4bf384..415df7b 100644 --- a/ServiceWorker/TeknikMigration.cs +++ b/ServiceWorker/TeknikMigration.cs @@ -3,12 +3,15 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Text; using System.Threading.Tasks; using Teknik.Areas.Paste; using Teknik.Areas.Paste.Models; using Teknik.Configuration; using Teknik.Data; +using Teknik.StorageService; using Teknik.Utilities; +using Teknik.Utilities.Cryptography; namespace Teknik.ServiceWorker { @@ -17,51 +20,9 @@ namespace Teknik.ServiceWorker public static bool RunMigration(TeknikEntities db, Config config) { bool success = false; - - MigratePastes(db, config); + return success; } - - public static void MigratePastes(TeknikEntities db, Config config) - { - if (!Directory.Exists(config.PasteConfig.PasteDirectory)) - { - Directory.CreateDirectory(config.PasteConfig.PasteDirectory); - } - - var pastes = db.Pastes.Select(p => p.PasteId).ToList(); - - foreach (var pasteId in pastes) - { - var paste = db.Pastes.Where(p => p.PasteId == pasteId).FirstOrDefault(); - if (!string.IsNullOrEmpty(paste.Content) && string.IsNullOrEmpty(paste.FileName) && string.IsNullOrEmpty(paste.HashedPassword)) - { - // Generate a unique file name that does not currently exist - string filePath = FileHelper.GenerateRandomFileName(config.PasteConfig.PasteDirectory, config.PasteConfig.FileExtension, 10); - string fileName = Path.GetFileName(filePath); - - string key = PasteHelper.GenerateKey(config.PasteConfig.KeySize); - string iv = PasteHelper.GenerateIV(config.PasteConfig.BlockSize); - - // Encrypt the contents to the file - PasteHelper.EncryptContents(paste.Content, filePath, null, key, iv, config.PasteConfig.KeySize, config.PasteConfig.ChunkSize); - - // Generate a deletion key - paste.DeleteKey = StringHelper.RandomString(config.PasteConfig.DeleteKeyLength); - - paste.Key = key; - paste.KeySize = config.PasteConfig.KeySize; - paste.IV = iv; - paste.BlockSize = config.PasteConfig.BlockSize; - - paste.FileName = fileName; - paste.Content = string.Empty; - - db.Entry(paste).State = EntityState.Modified; - db.SaveChanges(); - } - } - } } } diff --git a/Teknik/App_Data/endpointMappings.json b/Teknik/App_Data/endpointMappings.json index 8e3aa3a..60c32c5 100644 --- a/Teknik/App_Data/endpointMappings.json +++ b/Teknik/App_Data/endpointMappings.json @@ -187,6 +187,116 @@ "action": "Index" } }, + { + "Name": "Billing.Index", + "HostTypes": [ "Full" ], + "SubDomains": [ "billing" ], + "Pattern": "", + "Area": "Billing", + "Defaults": { + "controller": "Billing", + "action": "Index" + } + }, + { + "Name": "Billing.Subscriptions", + "HostTypes": [ "Full" ], + "SubDomains": [ "billing" ], + "Pattern": "Subscriptions", + "Area": "Billing", + "Defaults": { + "controller": "Billing", + "action": "ViewSubscriptions" + } + }, + { + "Name": "Billing.ViewPaymentInfo", + "HostTypes": [ "Full" ], + "SubDomains": [ "billing" ], + "Pattern": "PaymentInfo", + "Area": "Billing", + "Defaults": { + "controller": "Billing", + "action": "ViewPaymentInfo" + } + }, + { + "Name": "Billing.Subscribe", + "HostTypes": [ "Full" ], + "SubDomains": [ "billing" ], + "Pattern": "Subscribe/{priceId}/{subscriptionId?}", + "Area": "Billing", + "Defaults": { + "controller": "Billing", + "action": "Subscribe" + } + }, + { + "Name": "Billing.Checkout", + "HostTypes": [ "Full" ], + "SubDomains": [ "billing" ], + "Pattern": "Checkout/{priceId}", + "Area": "Billing", + "Defaults": { + "controller": "Billing", + "action": "Checkout" + } + }, + { + "Name": "Billing.CheckoutComplete", + "HostTypes": [ "Full" ], + "SubDomains": [ "billing" ], + "Pattern": "CheckoutComplete", + "Area": "Billing", + "Defaults": { + "controller": "Billing", + "action": "CheckoutComplete" + } + }, + { + "Name": "Billing.EditSubscription", + "HostTypes": [ "Full" ], + "SubDomains": [ "billing" ], + "Pattern": "EditSubscription/{priceId}", + "Area": "Billing", + "Defaults": { + "controller": "Billing", + "action": "EditSubscription" + } + }, + { + "Name": "Billing.CancelSubscription", + "HostTypes": [ "Full" ], + "SubDomains": [ "billing" ], + "Pattern": "CancelSubscription/{subscriptionId}/{productId}", + "Area": "Billing", + "Defaults": { + "controller": "Billing", + "action": "CancelSubscription" + } + }, + { + "Name": "Billing.SubscriptionSuccess", + "HostTypes": [ "Full" ], + "SubDomains": [ "billing" ], + "Pattern": "SubscriptionSuccess/{priceId}", + "Area": "Billing", + "Defaults": { + "controller": "Billing", + "action": "SubscriptionSuccess" + } + }, + { + "Name": "Billing.Action", + "HostTypes": [ "Full" ], + "SubDomains": [ "billing" ], + "Pattern": "Action/{action}", + "Area": "Billing", + "Defaults": { + "controller": "Billing", + "action": "Index" + } + }, { "Name": "Blog.Blog", "HostTypes": [ "Full" ], @@ -902,6 +1012,17 @@ "action": "AccountSettings" } }, + { + "Name": "User.BillingSettings", + "HostTypes": [ "Full" ], + "SubDomains": [ "account" ], + "Pattern": "Settings/Billing", + "Area": "User", + "Defaults": { + "controller": "User", + "action": "BillingSettings" + } + }, { "Name": "User.SecuritySettings", "HostTypes": [ "Full" ], @@ -1045,50 +1166,6 @@ "action": "ViewRawPGP" } }, - { - "Name": "Billing.Index", - "HostTypes": [ "Full" ], - "SubDomains": [ "billing" ], - "Pattern": "", - "Area": "Billing", - "Defaults": { - "controller": "Billing", - "action": "Index" - } - }, - { - "Name": "Billing.Subscriptions", - "HostTypes": [ "Full" ], - "SubDomains": [ "billing" ], - "Pattern": "Subscriptions", - "Area": "Billing", - "Defaults": { - "controller": "Billing", - "action": "ViewSubscriptions" - } - }, - { - "Name": "Billing.ViewPaymentInfo", - "HostTypes": [ "Full" ], - "SubDomains": [ "billing" ], - "Pattern": "PaymentInfo", - "Area": "Billing", - "Defaults": { - "controller": "Billing", - "action": "ViewPaymentInfo" - } - }, - { - "Name": "Billing.Action", - "HostTypes": [ "Full" ], - "SubDomains": [ "billing" ], - "Pattern": "Action/{action}", - "Area": "Billing", - "Defaults": { - "controller": "Billing", - "action": "Index" - } - }, { "Name": "Vault.Index", "HostTypes": [ "Full" ], diff --git a/Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs b/Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs new file mode 100644 index 0000000..a78d8bd --- /dev/null +++ b/Teknik/Areas/API/V1/Controllers/BillingAPIv1Controller.cs @@ -0,0 +1,104 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Teknik.Areas.Users.Models; +using Teknik.Areas.Users.Utility; +using Teknik.BillingCore; +using Teknik.BillingCore.Models; +using Teknik.Configuration; +using Teknik.Data; +using Teknik.Logging; + +namespace Teknik.Areas.API.V1.Controllers +{ + public class BillingAPIv1Controller : APIv1Controller + { + public BillingAPIv1Controller(ILogger logger, Config config, TeknikEntities dbContext) : base(logger, config, dbContext) { } + + public async Task HandleCheckoutCompleteEvent() + { + var billingService = BillingFactory.GetBillingService(_config.BillingConfig); + + var billingEvent = await billingService.ParseEvent(Request); + + if (billingEvent == null) + return BadRequest(); + + var session = billingService.ProcessCheckoutCompletedEvent(billingEvent); + if (session.PaymentStatus == PaymentStatus.Paid) + { + var subscription = billingService.GetSubscription(session.SubscriptionId); + + ProcessSubscription(session.CustomerId, subscription); + } + + return Ok(); + } + + public async Task HandleSubscriptionChange() + { + var billingService = BillingFactory.GetBillingService(_config.BillingConfig); + + var billingEvent = await billingService.ParseEvent(Request); + + if (billingEvent == null) + return BadRequest(); + + var customerEvent = billingService.ProcessCustomerEvent(billingEvent); + + foreach (var subscription in customerEvent.Subscriptions) + { + ProcessSubscription(customerEvent.CustomerId, subscription); + } + + return Ok(); + } + + private void ProcessSubscription(string customerId, Subscription subscription) + { + // They have paid, so let's get their subscription and update their user settings + var user = _dbContext.Users.FirstOrDefault(u => u.BillingCustomer != null && + u.BillingCustomer.CustomerId == customerId); + if (user != null) + { + var isActive = subscription.Status == SubscriptionStatus.Active; + foreach (var price in subscription.Prices) + { + ProcessPrice(user, price, isActive); + } + } + } + + private void ProcessPrice(User user, Price price, bool active) + { + // What type of subscription is this + if (_config.BillingConfig.UploadProductId == price.ProductId) + { + // Process Upload Settings + user.UploadSettings.MaxUploadStorage = active ? price.Storage : _config.UploadConfig.MaxStorage; + user.UploadSettings.MaxUploadFileSize = active ? price.FileSize : _config.UploadConfig.MaxUploadFileSize; + _dbContext.Entry(user).State = EntityState.Modified; + _dbContext.SaveChanges(); + } + else if (_config.BillingConfig.EmailProductId == price.ProductId) + { + // Process an email subscription + string email = UserHelper.GetUserEmailAddress(_config, user.Username); + if (active) + { + UserHelper.EnableUserEmail(_config, email); + UserHelper.EditUserEmailMaxSize(_config, email, (int)price.Storage); + } + else + { + UserHelper.DisableUserEmail(_config, email); + } + } + } + } +} diff --git a/Teknik/Areas/API/V1/Controllers/UploadAPIv1Controller.cs b/Teknik/Areas/API/V1/Controllers/UploadAPIv1Controller.cs index fb521ed..57db0b0 100644 --- a/Teknik/Areas/API/V1/Controllers/UploadAPIv1Controller.cs +++ b/Teknik/Areas/API/V1/Controllers/UploadAPIv1Controller.cs @@ -40,22 +40,17 @@ namespace Teknik.Areas.API.V1.Controllers { if (model.file != null) { - long maxUploadSize = _config.UploadConfig.MaxUploadSize; + long maxUploadSize = _config.UploadConfig.MaxUploadFileSize; + long maxTotalSize = _config.UploadConfig.MaxUploadFileSize; if (User.Identity.IsAuthenticated) { - maxUploadSize = _config.UploadConfig.MaxUploadSizeBasic; - long maxTotalSize = _config.UploadConfig.MaxTotalSizeBasic; - IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); - if (userInfo.AccountType == AccountType.Premium) - { - maxUploadSize = _config.UploadConfig.MaxUploadSizePremium; - maxTotalSize = _config.UploadConfig.MaxTotalSizePremium; - } - // Check account total limits var user = UserHelper.GetUser(_dbContext, User.Identity.Name); if (user.UploadSettings.MaxUploadStorage != null) maxTotalSize = user.UploadSettings.MaxUploadStorage.Value; + if (user.UploadSettings.MaxUploadFileSize != null) + maxUploadSize = user.UploadSettings.MaxUploadFileSize.Value; + var userUploadSize = user.Uploads.Sum(u => u.ContentLength); if (userUploadSize + model.file.Length > maxTotalSize) { diff --git a/Teknik/Areas/About/Controllers/AboutController.cs b/Teknik/Areas/About/Controllers/AboutController.cs index 5a86901..b47d382 100644 --- a/Teknik/Areas/About/Controllers/AboutController.cs +++ b/Teknik/Areas/About/Controllers/AboutController.cs @@ -6,12 +6,16 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Teknik.Areas.About.ViewModels; +using Teknik.Areas.Billing.ViewModels; +using Teknik.Areas.Users.Utility; using Teknik.Attributes; +using Teknik.BillingCore; using Teknik.Configuration; using Teknik.Controllers; using Teknik.Data; using Teknik.Filters; using Teknik.Logging; +using Teknik.Utilities.Routing; namespace Teknik.Areas.About.Controllers { @@ -27,8 +31,123 @@ namespace Teknik.Areas.About.Controllers { ViewBag.Title = "About"; ViewBag.Description = "What is Teknik?"; + var vm = new AboutViewModel(); - return View(new AboutViewModel()); + // Get Biling Service + var billingService = BillingFactory.GetBillingService(_config.BillingConfig); + + var subVM = new SubscriptionsViewModel(); + + // Get current subscriptions + var curPrices = new Dictionary>(); + + if (User.Identity.IsAuthenticated) + { + var user = UserHelper.GetUser(_dbContext, User.Identity.Name); + if (user.BillingCustomer != null) + { + var currentSubs = billingService.GetSubscriptionList(user.BillingCustomer.CustomerId); + foreach (var curSub in currentSubs) + { + foreach (var price in curSub.Prices) + { + if (!curPrices.ContainsKey(price.ProductId)) + curPrices[price.ProductId] = new List(); + curPrices[price.ProductId].Add(price.Id); + } + } + } + } + bool hasUploadProduct = curPrices.ContainsKey(_config.BillingConfig.UploadProductId); + bool hasEmailProduct = curPrices.ContainsKey(_config.BillingConfig.EmailProductId); + var curUploadPrice = string.Empty; + if (curPrices.ContainsKey(_config.BillingConfig.UploadProductId)) + curUploadPrice = curPrices[_config.BillingConfig.UploadProductId].FirstOrDefault(); + var curEmailPrice = string.Empty; + if (curPrices.ContainsKey(_config.BillingConfig.EmailProductId)) + curEmailPrice = curPrices[_config.BillingConfig.EmailProductId].FirstOrDefault(); + + // Show Free Subscription + subVM.UploadSubscriptions.Add(new SubscriptionViewModel() + { + CurrentSubMonthly = !hasUploadProduct && User.Identity.IsAuthenticated, + SubscribeText = "Free", + SubscribeUrlMonthly = Url.SubRouteUrl("about", "About.Index"), + BaseStorage = _config.UploadConfig.MaxStorage + }); + + // Get Upload Prices + var uploadProduct = billingService.GetProduct(_config.BillingConfig.UploadProductId); + if (uploadProduct != null) + { + bool handledFirst = false; + foreach (var priceGrp in uploadProduct.Prices.GroupBy(p => p.Storage).OrderBy(p => p.Key)) + { + // Get Monthly prices + var priceMonth = priceGrp.FirstOrDefault(p => p.Interval == BillingCore.Models.Interval.Month); + + // Get Yearly prices + var priceYear = priceGrp.FirstOrDefault(p => p.Interval == BillingCore.Models.Interval.Year); + + var curPrice = priceGrp.FirstOrDefault(p => p.Id == curUploadPrice); + subVM.UploadSubscriptions.Add(new SubscriptionViewModel() + { + Recommended = !handledFirst, + CurrentSubMonthly = curPrice?.Id == priceMonth?.Id, + CurrentSubYearly = curPrice?.Id == priceYear?.Id, + SubscribeUrlMonthly = Url.SubRouteUrl("billing", + hasUploadProduct ? "Billing.EditSubscription" : "Billing.Checkout", + new { priceId = priceMonth?.Id }), + SubscribeUrlYearly = Url.SubRouteUrl("billing", + hasUploadProduct ? "Billing.EditSubscription" : "Billing.Checkout", + new { priceId = priceYear?.Id }), + BaseStorage = priceMonth?.Storage, + BasePriceMonthly = priceMonth?.Amount, + BasePriceYearly = priceYear?.Amount + }); + handledFirst = true; + } + } + + // Get Email Prices + var emailProduct = billingService.GetProduct(_config.BillingConfig.EmailProductId); + if (emailProduct != null) + { + bool handledFirst = false; + foreach (var priceGrp in emailProduct.Prices.GroupBy(p => p.Storage).OrderBy(p => p.Key)) + { + // Get Monthly prices + var priceMonth = priceGrp.FirstOrDefault(p => p.Interval == BillingCore.Models.Interval.Month); + + // Get Yearly prices + var priceYear = priceGrp.FirstOrDefault(p => p.Interval == BillingCore.Models.Interval.Year); + + var curPrice = priceGrp.FirstOrDefault(p => p.Id == curEmailPrice); + var emailSub = new SubscriptionViewModel() + { + Recommended = !handledFirst, + CurrentSubMonthly = curPrice?.Id == priceMonth?.Id, + CurrentSubYearly = curPrice?.Id == priceYear?.Id, + SubscribeUrlMonthly = Url.SubRouteUrl("billing", + hasEmailProduct ? "Billing.EditSubscription" : "Billing.Checkout", + new { priceId = priceMonth?.Id }), + SubscribeUrlYearly = Url.SubRouteUrl("billing", + hasEmailProduct ? "Billing.EditSubscription" : "Billing.Checkout", + new { priceId = priceYear?.Id }), + BaseStorage = priceMonth?.Storage, + BasePriceMonthly = priceMonth?.Amount, + BasePriceYearly = priceYear?.Amount + }; + if (!handledFirst) + emailSub.PanelOffset = "3"; + subVM.EmailSubscriptions.Add(emailSub); + handledFirst = true; + } + } + + vm.Subscriptions = subVM; + + return View(vm); } } } \ No newline at end of file diff --git a/Teknik/Areas/About/ViewModels/AboutViewModel.cs b/Teknik/Areas/About/ViewModels/AboutViewModel.cs index 577ac42..cd0fa21 100644 --- a/Teknik/Areas/About/ViewModels/AboutViewModel.cs +++ b/Teknik/Areas/About/ViewModels/AboutViewModel.cs @@ -2,11 +2,18 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Teknik.Areas.Billing.ViewModels; using Teknik.ViewModels; namespace Teknik.Areas.About.ViewModels { public class AboutViewModel : ViewModelBase { + public SubscriptionsViewModel Subscriptions { get; set; } + + public AboutViewModel() + { + Subscriptions = new SubscriptionsViewModel(); + } } } diff --git a/Teknik/Areas/About/Views/About/Index.cshtml b/Teknik/Areas/About/Views/About/Index.cshtml index 2bdd327..fd562f1 100644 --- a/Teknik/Areas/About/Views/About/Index.cshtml +++ b/Teknik/Areas/About/Views/About/Index.cshtml @@ -1,8 +1,7 @@ @model Teknik.Areas.About.ViewModels.AboutViewModel -@{ - string lifetimeAccount = "The account will not get deleted for inactivity as defined in the ToS"; - string apiAccountAccess = "Passing user credentials allows linking for Uploads/Pastes/Shortened URLs and other account specific features"; +@{ + string extraUsage = string.Empty; }
@@ -16,57 +15,35 @@

You can view our complete activity and statistics by visiting the statistics page.

+
-
+
+

What can you do with Teknik?


+
- - - + + - - - - - - - - - - - - - - - - - - - - - - - - @@ -81,101 +58,72 @@ - + + + + + - - - - - + + - - - + + - - + - - - - - - - - - - - - - - - - - - - - - -
Premium AccountBasic AccountNo AccountFree AccountAnonymous
$@Config.UserConfig.PremiumAccountPrice @Config.UserConfig.PaymentTypeFreeFree
Lifetime Account *
Email Account
Git Repositories Unlimited Public & Private ReposUnlimited Public & Private Repos
Personal Blog
Vault and Paste Editing
Service API
Pastebin
File Uploads
Max Upload Filesize@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadSizePremium)@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadSizeBasic)@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadSize)@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadFileSize)@StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadFileSize)
Max Embedded FilesizeUnlimited@StringHelper.GetBytesReadable(Config.UploadConfig.MaxDownloadSize)@StringHelper.GetBytesReadable(Config.UploadConfig.MaxDownloadSize)@StringHelper.GetBytesReadable(Config.UploadConfig.MaxDownloadFileSize)@StringHelper.GetBytesReadable(Config.UploadConfig.MaxDownloadFileSize)
Upload Storage@StringHelper.GetBytesReadable(Config.UploadConfig.MaxTotalSizePremium)@StringHelper.GetBytesReadable(Config.UploadConfig.MaxTotalSizeBasic)@StringHelper.GetBytesReadable(Config.UploadConfig.MaxStorage) No Limit
Upload Expiration NeverNever 24 Hours
Url Shortening
Vault Creation
Technical Podcasts
Mumble Server
IRC Server
Service API **
-
-
-

- - * @lifetimeAccount -
- ** @apiAccountAccess -
-

-
-
+
+ + @await Html.PartialAsync("../../Areas/Billing/Views/Billing/ViewSubscriptions", Model.Subscriptions); +

+

Want to help?


-

+

Teknik's source code can be located on our Git Repository as well as all our internal tools and projects.

diff --git a/Teknik/Areas/Billing/Controllers/BillingController.cs b/Teknik/Areas/Billing/Controllers/BillingController.cs index c480411..e0e3a25 100644 --- a/Teknik/Areas/Billing/Controllers/BillingController.cs +++ b/Teknik/Areas/Billing/Controllers/BillingController.cs @@ -1,11 +1,15 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Teknik.Areas.Billing.Models; using Teknik.Areas.Billing.ViewModels; +using Teknik.Areas.Users.Models; +using Teknik.Areas.Users.Utility; using Teknik.BillingCore; using Teknik.Configuration; using Teknik.Controllers; @@ -15,6 +19,7 @@ using Teknik.Utilities.Routing; namespace Teknik.Areas.Billing.Controllers { + [Authorize] [Area("Billing")] public class BillingController : DefaultController { @@ -32,39 +37,47 @@ namespace Teknik.Areas.Billing.Controllers var subVM = new SubscriptionsViewModel(); // Get Biling Service - var billingService = BillingFactory.GetStorageService(_config.BillingConfig); + var billingService = BillingFactory.GetBillingService(_config.BillingConfig); // Get current subscriptions - string curSubId = null; - var curSubs = new Dictionary>(); + var curPrices = new Dictionary>(); if (User.Identity.IsAuthenticated) { - var currentSubs = billingService.GetSubscriptionList(User.Identity.Name); - foreach (var curSub in currentSubs) + var user = UserHelper.GetUser(_dbContext, User.Identity.Name); + if (user.BillingCustomer != null) { - foreach (var price in curSub.Prices) + var currentSubs = billingService.GetSubscriptionList(user.BillingCustomer.CustomerId); + foreach (var curSub in currentSubs) { - if (!curSubs.ContainsKey(price.ProductId)) - curSubs[price.ProductId] = new List(); - curSubs[price.ProductId].Add(price.Id); + foreach (var price in curSub.Prices) + { + if (!curPrices.ContainsKey(price.ProductId)) + curPrices[price.ProductId] = new List(); + curPrices[price.ProductId].Add(price.Id); + } } } } + bool hasUploadProduct = curPrices.ContainsKey(_config.BillingConfig.UploadProductId); + bool hasEmailProduct = curPrices.ContainsKey(_config.BillingConfig.EmailProductId); + var curUploadPrice = string.Empty; + if (curPrices.ContainsKey(_config.BillingConfig.UploadProductId)) + curUploadPrice = curPrices[_config.BillingConfig.UploadProductId].FirstOrDefault(); + var curEmailPrice = string.Empty; + if (curPrices.ContainsKey(_config.BillingConfig.EmailProductId)) + curEmailPrice = curPrices[_config.BillingConfig.EmailProductId].FirstOrDefault(); // Show Free Subscription subVM.UploadSubscriptions.Add(new SubscriptionViewModel() { - CurrentPlan = curSubId == null, + CurrentSubMonthly = !hasUploadProduct && User.Identity.IsAuthenticated, SubscribeText = "Free", - SubscribeUrlMonthly = Url.SubRouteUrl("account", "User.Register"), - BaseStorage = _config.UploadConfig.MaxUploadSizeBasic + SubscribeUrlMonthly = Url.SubRouteUrl("about", "About.Index"), + BaseStorage = _config.UploadConfig.MaxStorage }); // Get Upload Prices - var curUploadSubs = new List(); - if (curSubs.ContainsKey(_config.BillingConfig.UploadProductId)) - curUploadSubs = curSubs[_config.BillingConfig.UploadProductId]; var uploadProduct = billingService.GetProduct(_config.BillingConfig.UploadProductId); if (uploadProduct != null) { @@ -77,13 +90,18 @@ namespace Teknik.Areas.Billing.Controllers // Get Yearly prices var priceYear = priceGrp.FirstOrDefault(p => p.Interval == BillingCore.Models.Interval.Year); - var isCurrent = curUploadSubs.Exists(s => priceGrp.FirstOrDefault(p => p.ProductId == s) != null); + var curPrice = priceGrp.FirstOrDefault(p => p.Id == curUploadPrice); subVM.UploadSubscriptions.Add(new SubscriptionViewModel() { Recommended = !handledFirst, - CurrentPlan = isCurrent, - SubscribeUrlMonthly = Url.SubRouteUrl("billing", "Billing.Subscribe", new { priceId = priceMonth?.Id }), - SubscribeUrlYearly = Url.SubRouteUrl("billing", "Billing.Subscribe", new { priceId = priceYear?.Id }), + CurrentSubMonthly = curPrice?.Id == priceMonth?.Id, + CurrentSubYearly = curPrice?.Id == priceYear?.Id, + SubscribeUrlMonthly = Url.SubRouteUrl("billing", + hasUploadProduct ? "Billing.EditSubscription" : "Billing.Checkout", + new { priceId = priceMonth?.Id }), + SubscribeUrlYearly = Url.SubRouteUrl("billing", + hasUploadProduct ? "Billing.EditSubscription" : "Billing.Checkout", + new { priceId = priceYear?.Id }), BaseStorage = priceMonth?.Storage, BasePriceMonthly = priceMonth?.Amount, BasePriceYearly = priceYear?.Amount @@ -93,9 +111,6 @@ namespace Teknik.Areas.Billing.Controllers } // Get Email Prices - var curEmailSubs = new List(); - if (curSubs.ContainsKey(_config.BillingConfig.EmailProductId)) - curEmailSubs = curSubs[_config.BillingConfig.EmailProductId]; var emailProduct = billingService.GetProduct(_config.BillingConfig.EmailProductId); if (emailProduct != null) { @@ -108,13 +123,18 @@ namespace Teknik.Areas.Billing.Controllers // Get Yearly prices var priceYear = priceGrp.FirstOrDefault(p => p.Interval == BillingCore.Models.Interval.Year); - var isCurrent = curUploadSubs.Exists(s => priceGrp.FirstOrDefault(p => p.ProductId == s) != null); + var curPrice = priceGrp.FirstOrDefault(p => p.Id == curEmailPrice); var emailSub = new SubscriptionViewModel() { Recommended = !handledFirst, - CurrentPlan = isCurrent, - SubscribeUrlMonthly = Url.SubRouteUrl("billing", "Billing.Subscribe", new { priceId = priceMonth?.Id }), - SubscribeUrlYearly = Url.SubRouteUrl("billing", "Billing.Subscribe", new { priceId = priceYear?.Id }), + CurrentSubMonthly = curPrice?.Id == priceMonth?.Id, + CurrentSubYearly = curPrice?.Id == priceYear?.Id, + SubscribeUrlMonthly = Url.SubRouteUrl("billing", + hasEmailProduct ? "Billing.EditSubscription" : "Billing.Checkout", + new { priceId = priceMonth?.Id }), + SubscribeUrlYearly = Url.SubRouteUrl("billing", + hasEmailProduct ? "Billing.EditSubscription" : "Billing.Checkout", + new { priceId = priceYear?.Id }), BaseStorage = priceMonth?.Storage, BasePriceMonthly = priceMonth?.Amount, BasePriceYearly = priceYear?.Amount @@ -136,13 +156,152 @@ namespace Teknik.Areas.Billing.Controllers } [AllowAnonymous] - public IActionResult Subscribe(string priceId) + public IActionResult Subscribe(string priceId, string subscriptionId) { // Get Subscription Info - var billingService = BillingFactory.GetStorageService(_config.BillingConfig); - var price = billingService.GetPrice(priceId); + var billingService = BillingFactory.GetBillingService(_config.BillingConfig); - return View(new SubscriptionViewModel()); + var vm = new SubscribeViewModel(); + vm.Price = billingService.GetPrice(priceId); + vm.Subscription = billingService.GetSubscription(subscriptionId); + + return View(vm); + } + + public IActionResult Checkout(string priceId) + { + // Get Subscription Info + var billingService = BillingFactory.GetBillingService(_config.BillingConfig); + + var price = billingService.GetPrice(priceId); + if (price == null) + throw new ArgumentException("Invalid Price ID", "priceId"); + + User user = UserHelper.GetUser(_dbContext, User.Identity.Name); + if (user == null) + throw new UnauthorizedAccessException(); + + if (user.BillingCustomer == null) + { + var custId = billingService.CreateCustomer(user.Username, null); + var customer = new Customer() + { + CustomerId = custId, + User = user + }; + _dbContext.Customers.Add(customer); + user.BillingCustomer = customer; + _dbContext.Entry(user).State = EntityState.Modified; + _dbContext.SaveChanges(); + } + + var session = billingService.CreateCheckoutSession(user.BillingCustomer.CustomerId, + priceId, + Url.SubRouteUrl("billing", "Billing.CheckoutComplete", new { productId = price.ProductId }), + Url.SubRouteUrl("billing", "Billing.Subscriptions")); + return Redirect(session.Url); + } + + public IActionResult CheckoutComplete(string productId, string session_id) + { + // Get Checkout Info + var billingService = BillingFactory.GetBillingService(_config.BillingConfig); + var checkoutSession = billingService.GetCheckoutSession(session_id); + if (checkoutSession != null) + { + var subscription = billingService.GetSubscription(checkoutSession.SubscriptionId); + if (subscription != null) + { + foreach (var price in subscription.Prices) + { + if (price.ProductId == productId) + { + return Redirect(Url.SubRouteUrl("billing", "Billing.SubscriptionSuccess", new { priceId = price.Id })); + } + } + } + } + + return Redirect(Url.SubRouteUrl("billing", "Billing.ViewSubscriptions")); + } + + public IActionResult EditSubscription(string priceId) + { + // Get Subscription Info + var billingService = BillingFactory.GetBillingService(_config.BillingConfig); + + var price = billingService.GetPrice(priceId); + if (price == null) + throw new ArgumentException("Invalid Price ID", "priceId"); + + User user = UserHelper.GetUser(_dbContext, User.Identity.Name); + if (user == null) + throw new UnauthorizedAccessException(); + + if (user.BillingCustomer == null) + { + return Checkout(priceId); + } + else + { + var currentSubs = billingService.GetSubscriptionList(user.BillingCustomer.CustomerId); + foreach (var curSub in currentSubs) + { + if (curSub.Prices.Exists(p => p.ProductId == price.ProductId)) + { + billingService.EditSubscriptionPrice(curSub.Id, price.Id); + return Redirect(Url.SubRouteUrl("billing", "Billing.SubscriptionSuccess", new { priceId = price.Id })); + } + } + } + + return Redirect(Url.SubRouteUrl("billing", "Billing.ViewSubscriptions")); + } + + public IActionResult CancelSubscription(string subscriptionId, string productId) + { + // Get Subscription Info + var billingService = BillingFactory.GetBillingService(_config.BillingConfig); + + var subscription = billingService.GetSubscription(subscriptionId); + if (subscription == null) + throw new ArgumentException("Invalid Subscription Id", "subscriptionId"); + + if (!subscription.Prices.Exists(p => p.ProductId == productId)) + throw new ArgumentException("Subscription does not relate to product", "productId"); + + var product = billingService.GetProduct(productId); + if (product == null) + throw new ArgumentException("Product does not exist", "productId"); + + var result = billingService.CancelSubscription(subscriptionId); + + var vm = new CancelSubscriptionViewModel() + { + ProductName = product.Name + }; + + return View(vm); + } + + public IActionResult SubscriptionSuccess(string priceId) + { + var vm = new SubscriptionSuccessViewModel(); + + // Get Subscription Info + var billingService = BillingFactory.GetBillingService(_config.BillingConfig); + + var price = billingService.GetPrice(priceId); + if (price == null) + throw new ArgumentException("Invalid Price ID", "priceId"); + + var product = billingService.GetProduct(price.ProductId); + vm.ProductName = product.Name; + vm.Price = price.Amount; + vm.Interval = price.Interval.ToString(); + vm.Storage = price.Storage; + + return View(vm); } } } diff --git a/Teknik/Areas/Billing/Models/Customer.cs b/Teknik/Areas/Billing/Models/Customer.cs new file mode 100644 index 0000000..0af6389 --- /dev/null +++ b/Teknik/Areas/Billing/Models/Customer.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.Areas.Users.Models; +using Teknik.Attributes; + +namespace Teknik.Areas.Billing.Models +{ + public class Customer + { + [CaseSensitive] + public string CustomerId { get; set; } + + public int UserId { get; set; } + + public virtual User User { get; set; } + } +} diff --git a/Teknik/Areas/Billing/ViewModels/CancelSubscriptionViewModel.cs b/Teknik/Areas/Billing/ViewModels/CancelSubscriptionViewModel.cs new file mode 100644 index 0000000..2c569ea --- /dev/null +++ b/Teknik/Areas/Billing/ViewModels/CancelSubscriptionViewModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.ViewModels; + +namespace Teknik.Areas.Billing.ViewModels +{ + public class CancelSubscriptionViewModel : ViewModelBase + { + public string ProductName { get; set; } + } +} diff --git a/Teknik/Areas/Billing/ViewModels/PriceViewModel.cs b/Teknik/Areas/Billing/ViewModels/PriceViewModel.cs new file mode 100644 index 0000000..891e852 --- /dev/null +++ b/Teknik/Areas/Billing/ViewModels/PriceViewModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.ViewModels; + +namespace Teknik.Areas.Billing.ViewModels +{ + public class PriceViewModel : ViewModelBase + { + public string ProductName { get; set; } + + public string Name { get; set; } + + public long Storage { get; set; } + } +} diff --git a/Teknik/Areas/Billing/ViewModels/SubscribeViewModel.cs b/Teknik/Areas/Billing/ViewModels/SubscribeViewModel.cs new file mode 100644 index 0000000..ecb4825 --- /dev/null +++ b/Teknik/Areas/Billing/ViewModels/SubscribeViewModel.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.BillingCore.Models; +using Teknik.ViewModels; + +namespace Teknik.Areas.Billing.ViewModels +{ + public class SubscribeViewModel : ViewModelBase + { + public Subscription Subscription { get; set; } + public Price Price { get; set; } + } +} diff --git a/Teknik/Areas/Billing/ViewModels/SubscriptionSuccessViewModel.cs b/Teknik/Areas/Billing/ViewModels/SubscriptionSuccessViewModel.cs new file mode 100644 index 0000000..cbfeb98 --- /dev/null +++ b/Teknik/Areas/Billing/ViewModels/SubscriptionSuccessViewModel.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.BillingCore.Models; +using Teknik.ViewModels; + +namespace Teknik.Areas.Billing.ViewModels +{ + public class SubscriptionSuccessViewModel : ViewModelBase + { + public string ProductName { get; set; } + + public decimal? Price { get; set; } + + public string Interval { get; set; } + + public long Storage { get; set; } + } +} diff --git a/Teknik/Areas/Billing/ViewModels/SubscriptionViewModel.cs b/Teknik/Areas/Billing/ViewModels/SubscriptionViewModel.cs index 38a01a6..7d89ff8 100644 --- a/Teknik/Areas/Billing/ViewModels/SubscriptionViewModel.cs +++ b/Teknik/Areas/Billing/ViewModels/SubscriptionViewModel.cs @@ -9,7 +9,8 @@ namespace Teknik.Areas.Billing.ViewModels public class SubscriptionViewModel : ViewModelBase { public bool Recommended { get; set; } - public bool CurrentPlan { get; set; } + public bool CurrentSubMonthly { get; set; } + public bool CurrentSubYearly { get; set; } public string SubscriptionId { get; set; } public decimal? BasePriceMonthly { get; set; } public decimal? BasePriceYearly { get; set; } @@ -28,7 +29,8 @@ namespace Teknik.Areas.Billing.ViewModels public SubscriptionViewModel() { Recommended = false; - CurrentPlan = false; + CurrentSubMonthly = false; + CurrentSubYearly = false; OverageAllowed = false; } } diff --git a/Teknik/Areas/Billing/Views/Billing/CancelSubscription.cshtml b/Teknik/Areas/Billing/Views/Billing/CancelSubscription.cshtml new file mode 100644 index 0000000..438571d --- /dev/null +++ b/Teknik/Areas/Billing/Views/Billing/CancelSubscription.cshtml @@ -0,0 +1,21 @@ +@model Teknik.Areas.Billing.ViewModels.CancelSubscriptionViewModel + +

+ @if (Model.Error) + { +
+
+

@Model.ErrorMessage

+
+
+ } + else + { +
+
+

You have successfully canceled your subscription to @Model.ProductName

+

View Active Subscriptions

+
+
+ } +
\ No newline at end of file diff --git a/Teknik/Areas/Billing/Views/Billing/Subscribe.cshtml b/Teknik/Areas/Billing/Views/Billing/Subscribe.cshtml new file mode 100644 index 0000000..01ff1a2 --- /dev/null +++ b/Teknik/Areas/Billing/Views/Billing/Subscribe.cshtml @@ -0,0 +1,45 @@ +@model Teknik.Areas.Billing.ViewModels.SubscribeViewModel + +@{ + var price = Model.Price.Amount != null ? $"${Model.Price.Amount:0.00}" : "Free"; + var interval = Model.Price.Interval.ToString(); + var priceText = Model.Price.Amount != null ? $"{price}/{interval}" : "Free"; + var storageText = $"{StringHelper.GetBytesReadable(Model.Price.Storage)}"; + + if (Model.Subscription != null) + { + } +} + +
+
+
+
+ @if (Model.Error) + { +
@Model.ErrorMessage
+ } +
+
+
+
+
@storageText
+
@priceText
+
+
+
no tax
+
+
+
+

You'll be charged @price automatically every @interval until you cancel. Your price may change as described in the Teknik Terms of Service.

+

Cancel anytime in your Subscriptions Settings.

+

+
+
+
+
+ + Subscribe +
+
+
\ No newline at end of file diff --git a/Teknik/Areas/Billing/Views/Billing/SubscriptionSuccess.cshtml b/Teknik/Areas/Billing/Views/Billing/SubscriptionSuccess.cshtml new file mode 100644 index 0000000..877831c --- /dev/null +++ b/Teknik/Areas/Billing/Views/Billing/SubscriptionSuccess.cshtml @@ -0,0 +1,21 @@ +@model Teknik.Areas.Billing.ViewModels.SubscriptionSuccessViewModel + +
+ @if (Model.Error) + { +
+
+

@Model.ErrorMessage

+
+
+ } + else + { +
+
+

Thank you for subscribing to @Model.ProductName: @(StringHelper.GetBytesReadable(Model.Storage)) for @($"${Model.Price:0.00} / {Model.Interval}")

+

View Active Subscriptions

+
+
+ } +
\ No newline at end of file diff --git a/Teknik/Areas/Billing/Views/Billing/ViewSubscription.cshtml b/Teknik/Areas/Billing/Views/Billing/ViewSubscription.cshtml index 8ae4a4d..e68a1f2 100644 --- a/Teknik/Areas/Billing/Views/Billing/ViewSubscription.cshtml +++ b/Teknik/Areas/Billing/Views/Billing/ViewSubscription.cshtml @@ -13,10 +13,10 @@ var btnClass = ""; var subscribeText = Model.BasePriceMonthly.HasValue ? $"${Model.BasePriceMonthly:0.00} / month" : "Free"; - if (Model.CurrentPlan) + if (Model.CurrentSubMonthly) { btnClass = "disabled"; - subscribeText = "Current plan"; + subscribeText = "Current Plan"; } var panelColor = "default"; @@ -53,12 +53,19 @@

@subscribeText

@if (Model.BasePriceYearly != null) { + var yearBtnClass = ""; + var yearSubscribeText = $"${Model.BasePriceYearly:0.00} / year"; + if (Model.CurrentSubYearly) + { + yearBtnClass = "disabled"; + yearSubscribeText = "Current Plan"; + } var discount = 1 - (Model.BasePriceYearly.Value / (Model.BasePriceMonthly.Value * 12));

Or prepay annually
(save @Math.Round(100 * discount)%):

-

@($"${Model.BasePriceYearly:0.00}") / year

+

@yearSubscribeText

}
diff --git a/Teknik/Areas/Billing/Views/Billing/ViewSubscriptions.cshtml b/Teknik/Areas/Billing/Views/Billing/ViewSubscriptions.cshtml index 79c0d68..382e901 100644 --- a/Teknik/Areas/Billing/Views/Billing/ViewSubscriptions.cshtml +++ b/Teknik/Areas/Billing/Views/Billing/ViewSubscriptions.cshtml @@ -8,10 +8,7 @@ @if (Model.UploadSubscriptions.Any()) {

Upload Storage Subscriptions

-

- By subscribing to a Teknik Service plan, you agree to the Teknik Terms of Service.
- Note: The Teknik Privacy Policy describes how data is handled in this service. -

+

Want some extra storage? Upgrade with one of the storage subscriptions!


@foreach (var subVM in Model.UploadSubscriptions) @@ -22,24 +19,13 @@ @await Html.PartialAsync("../../Areas/Billing/Views/Billing/ViewSubscription", subVM) }
- @if (!string.IsNullOrEmpty(extraUsage)) - { -
-
-

- - * @extraUsage - -

-
-
- } } @if (Model.EmailSubscriptions.Any()) {

Email Subscriptions

+

Get a personal email address, complete with web mail client.


@foreach (var subVM in Model.EmailSubscriptions) @@ -51,4 +37,25 @@ }
} - \ No newline at end of file + +
+ +
+
+

+ By subscribing to a Teknik Service plan, you agree to the Teknik Terms of Service.
+ Note: The Teknik Privacy Policy describes how data is handled in this service. +

+ @if (!string.IsNullOrEmpty(extraUsage)) + { +

+ + * @extraUsage + +

+ } +
+
+ + + \ No newline at end of file diff --git a/Teknik/Areas/FAQ/Views/FAQ/Index.cshtml b/Teknik/Areas/FAQ/Views/FAQ/Index.cshtml index 90637c8..a2fd8cc 100644 --- a/Teknik/Areas/FAQ/Views/FAQ/Index.cshtml +++ b/Teknik/Areas/FAQ/Views/FAQ/Index.cshtml @@ -119,7 +119,7 @@
- The max file size for everyone is @StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadSize), basic accounts are @StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadSizeBasic), and Premium accounts are @StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadSizePremium). + The max file size for everyone is @StringHelper.GetBytesReadable(Config.UploadConfig.MaxUploadFileSize).
diff --git a/Teknik/Areas/Help/Controllers/HelpController.cs b/Teknik/Areas/Help/Controllers/HelpController.cs index 9dc3a64..1613011 100644 --- a/Teknik/Areas/Help/Controllers/HelpController.cs +++ b/Teknik/Areas/Help/Controllers/HelpController.cs @@ -118,18 +118,14 @@ namespace Teknik.Areas.Help.Controllers ViewBag.Title = "Upload Service Help"; UploadHelpViewModel model = new UploadHelpViewModel(); - model.MaxUploadSize = _config.UploadConfig.MaxUploadSize; + model.MaxUploadSize = _config.UploadConfig.MaxUploadFileSize; if (User.Identity.IsAuthenticated) { User user = UserHelper.GetUser(_dbContext, User.Identity.Name); if (user != null) { - model.MaxUploadSize = _config.UploadConfig.MaxUploadSizeBasic; - IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); - if (userInfo.AccountType == AccountType.Premium) - { - model.MaxUploadSize = _config.UploadConfig.MaxUploadSizePremium; - } + if (user.UploadSettings.MaxUploadFileSize != null) + model.MaxUploadSize = user.UploadSettings.MaxUploadFileSize.Value; } } return View("~/Areas/Help/Views/Help/Upload.cshtml", model); diff --git a/Teknik/Areas/Home/Views/Home/Index.cshtml b/Teknik/Areas/Home/Views/Home/Index.cshtml index d7d1c5c..ff0e417 100644 --- a/Teknik/Areas/Home/Views/Home/Index.cshtml +++ b/Teknik/Areas/Home/Views/Home/Index.cshtml @@ -131,7 +131,7 @@

-

Upgrade to Premium for even more features!

+

Subscribe for even more features!

@@ -154,18 +154,18 @@

- +
-

No Embed Limits

+

Additional Storage

- +

- +
-

Lifetime Account

+

No Embed Limits

diff --git a/Teknik/Areas/Paste/Controllers/PasteController.cs b/Teknik/Areas/Paste/Controllers/PasteController.cs index 28af97d..47e747b 100644 --- a/Teknik/Areas/Paste/Controllers/PasteController.cs +++ b/Teknik/Areas/Paste/Controllers/PasteController.cs @@ -93,7 +93,7 @@ namespace Teknik.Areas.Paste.Controllers string hash = string.Empty; if (!string.IsNullOrEmpty(password)) { - hash = PasteHelper.HashPassword(paste.Key, password); + hash = Crypto.HashPassword(paste.Key, password); keyBytes = AesCounterManaged.CreateKey(password, ivBytes, paste.KeySize); } if (string.IsNullOrEmpty(password) || hash != paste.HashedPassword) @@ -242,7 +242,7 @@ namespace Teknik.Areas.Paste.Controllers string hash = string.Empty; if (!string.IsNullOrEmpty(password)) { - hash = PasteHelper.HashPassword(paste.Key, password); + hash = Crypto.HashPassword(paste.Key, password); keyBytes = AesCounterManaged.CreateKey(password, ivBytes, paste.KeySize); } if (string.IsNullOrEmpty(password) || hash != paste.HashedPassword) @@ -307,7 +307,7 @@ namespace Teknik.Areas.Paste.Controllers string hash = string.Empty; if (!string.IsNullOrEmpty(password)) { - hash = PasteHelper.HashPassword(paste.Key, password); + hash = Crypto.HashPassword(paste.Key, password); } if (string.IsNullOrEmpty(password) || hash != paste.HashedPassword) { @@ -333,8 +333,8 @@ namespace Teknik.Areas.Paste.Controllers // Generate a unique file name that does not currently exist string fileName = storageService.GetUniqueFileName(); - string key = PasteHelper.GenerateKey(_config.PasteConfig.KeySize); - string iv = PasteHelper.GenerateIV(_config.PasteConfig.BlockSize); + string key = Crypto.GenerateKey(_config.PasteConfig.KeySize); + string iv = Crypto.GenerateIV(_config.PasteConfig.BlockSize); PasteHelper.EncryptContents(storageService, model.Content, fileName, password, key, iv, _config.PasteConfig.KeySize, _config.PasteConfig.ChunkSize); @@ -344,7 +344,7 @@ namespace Teknik.Areas.Paste.Controllers paste.BlockSize = _config.PasteConfig.BlockSize; if (!string.IsNullOrEmpty(password)) - paste.HashedPassword = PasteHelper.HashPassword(paste.Key, password); + paste.HashedPassword = Crypto.HashPassword(paste.Key, password); paste.FileName = fileName; paste.Title = model.Title; paste.Syntax = model.Syntax; diff --git a/Teknik/Areas/Paste/PasteHelper.cs b/Teknik/Areas/Paste/PasteHelper.cs index 64991c9..0599a20 100644 --- a/Teknik/Areas/Paste/PasteHelper.cs +++ b/Teknik/Areas/Paste/PasteHelper.cs @@ -59,15 +59,14 @@ namespace Teknik.Areas.Paste break; } - string key = GenerateKey(config.PasteConfig.KeySize); - string iv = GenerateIV(config.PasteConfig.BlockSize); + string key = Crypto.GenerateKey(config.PasteConfig.KeySize); + string iv = Crypto.GenerateIV(config.PasteConfig.BlockSize); if (!string.IsNullOrEmpty(password)) { - paste.HashedPassword = HashPassword(key, password); + paste.HashedPassword = Crypto.HashPassword(key, password); } - // Generate a unique file name that does not currently exist var storageService = StorageServiceFactory.GetStorageService(config.PasteConfig.StorageConfig); var fileName = storageService.GetUniqueFileName(); @@ -102,21 +101,6 @@ namespace Teknik.Areas.Paste return false; } - public static string GenerateKey(int keySize) - { - return StringHelper.RandomString(keySize / 8); - } - - public static string GenerateIV(int ivSize) - { - return StringHelper.RandomString(ivSize / 16); - } - - public static string HashPassword(string key, string password) - { - return SHA384.Hash(key, password).ToHex(); - } - public static void EncryptContents(IStorageService storageService, string content, string fileName, string password, string key, string iv, int keySize, int chunkSize) { byte[] ivBytes = Encoding.Unicode.GetBytes(iv); diff --git a/Teknik/Areas/Upload/Controllers/UploadController.cs b/Teknik/Areas/Upload/Controllers/UploadController.cs index a80f7c9..b361de3 100644 --- a/Teknik/Areas/Upload/Controllers/UploadController.cs +++ b/Teknik/Areas/Upload/Controllers/UploadController.cs @@ -45,7 +45,7 @@ namespace Teknik.Areas.Upload.Controllers model.Encrypt = false; model.ExpirationLength = 1; model.ExpirationUnit = ExpirationUnit.Days; - model.MaxUploadSize = _config.UploadConfig.MaxUploadSize; + model.MaxUploadSize = _config.UploadConfig.MaxUploadFileSize; if (User.Identity.IsAuthenticated) { User user = UserHelper.GetUser(_dbContext, User.Identity.Name); @@ -58,16 +58,12 @@ namespace Teknik.Areas.Upload.Controllers model.CurrentTotalSize = user.Uploads.Sum(u => u.ContentLength); - model.MaxUploadSize = _config.UploadConfig.MaxUploadSizeBasic; - model.MaxTotalSize = _config.UploadConfig.MaxTotalSizeBasic; - IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); - if (userInfo.AccountType == AccountType.Premium) - { - model.MaxUploadSize = _config.UploadConfig.MaxUploadSizePremium; - model.MaxTotalSize = _config.UploadConfig.MaxTotalSizePremium; - } + model.MaxUploadSize = _config.UploadConfig.MaxUploadFileSize; + model.MaxTotalSize = _config.UploadConfig.MaxStorage; if (user.UploadSettings.MaxUploadStorage != null) model.MaxTotalSize = user.UploadSettings.MaxUploadStorage.Value; + if (user.UploadSettings.MaxUploadFileSize != null) + model.MaxUploadSize = user.UploadSettings.MaxUploadFileSize.Value; if (model.CurrentTotalSize >= model.MaxTotalSize) { @@ -88,22 +84,17 @@ namespace Teknik.Areas.Upload.Controllers { if (_config.UploadConfig.UploadEnabled) { - long maxUploadSize = _config.UploadConfig.MaxUploadSize; + long maxUploadSize = _config.UploadConfig.MaxUploadFileSize; + long maxTotalSize = _config.UploadConfig.MaxStorage; if (User.Identity.IsAuthenticated) { - maxUploadSize = _config.UploadConfig.MaxUploadSizeBasic; - long maxTotalSize = _config.UploadConfig.MaxTotalSizeBasic; - IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); - if (userInfo.AccountType == AccountType.Premium) - { - maxUploadSize = _config.UploadConfig.MaxUploadSizePremium; - maxTotalSize = _config.UploadConfig.MaxTotalSizePremium; - } - // Check account total limits var user = UserHelper.GetUser(_dbContext, User.Identity.Name); if (user.UploadSettings.MaxUploadStorage != null) maxTotalSize = user.UploadSettings.MaxUploadStorage.Value; + if (user.UploadSettings.MaxUploadFileSize != null) + maxUploadSize = user.UploadSettings.MaxUploadFileSize.Value; + var userUploadSize = user.Uploads.Sum(u => u.ContentLength); if (userUploadSize + uploadFile.file.Length > maxTotalSize) { @@ -232,7 +223,6 @@ namespace Teknik.Areas.Upload.Controllers string iv = string.Empty; string contentType = string.Empty; long contentLength = 0; - bool premiumAccount = false; DateTime dateUploaded = new DateTime(); Models.Upload upload = _dbContext.Uploads.Where(up => up.Url == file).FirstOrDefault(); @@ -256,16 +246,6 @@ namespace Teknik.Areas.Upload.Controllers contentType = upload.ContentType; contentLength = upload.ContentLength; dateUploaded = upload.DateUploaded; - if (User.Identity.IsAuthenticated) - { - IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, User.Identity.Name); - premiumAccount = userInfo.AccountType == AccountType.Premium; - } - if (!premiumAccount && upload.UserId != null) - { - IdentityUserInfo userInfo = await IdentityHelper.GetIdentityUserInfo(_config, upload.User.Username); - premiumAccount = userInfo.AccountType == AccountType.Premium; - } } else { @@ -285,7 +265,7 @@ namespace Teknik.Areas.Upload.Controllers return View(model); } - else if (!premiumAccount && _config.UploadConfig.MaxDownloadSize < contentLength) + else if (_config.UploadConfig.MaxDownloadFileSize < contentLength) { // We want to force them to the dl page due to them being over the max download size for embedded content DownloadViewModel model = new DownloadViewModel(); diff --git a/Teknik/Areas/User/Controllers/UserController.cs b/Teknik/Areas/User/Controllers/UserController.cs index bdd25e4..ef38ff8 100644 --- a/Teknik/Areas/User/Controllers/UserController.cs +++ b/Teknik/Areas/User/Controllers/UserController.cs @@ -37,6 +37,7 @@ using System.IdentityModel.Tokens.Jwt; using Microsoft.AspNetCore.Http; using IdentityServer4.Models; using Teknik.Utilities.Routing; +using Teknik.BillingCore; namespace Teknik.Areas.Users.Controllers { @@ -357,6 +358,51 @@ namespace Teknik.Areas.Users.Controllers return new StatusCodeResult(StatusCodes.Status403Forbidden); } + [TrackPageView] + public IActionResult BillingSettings() + { + string username = User.Identity.Name; + User user = UserHelper.GetUser(_dbContext, username); + + if (user != null) + { + ViewBag.Title = "Billing Settings"; + ViewBag.Description = "Your " + _config.Title + " Billing Settings"; + + BillingSettingsViewModel model = new BillingSettingsViewModel(); + model.Page = "Billing"; + model.UserID = user.UserId; + model.Username = user.Username; + + if (user.BillingCustomer != null) + { + var billingService = BillingFactory.GetBillingService(_config.BillingConfig); + var subs = billingService.GetSubscriptionList(user.BillingCustomer.CustomerId); + foreach (var sub in subs) + { + foreach (var price in sub.Prices) + { + var product = billingService.GetProduct(price.ProductId); + var subView = new SubscriptionViewModel() + { + SubscriptionId = sub.Id, + ProductId = product.ProductId, + ProductName = product.Name, + Storage = price.Storage, + Price = price.Amount, + Interval = price.Interval.ToString() + }; + model.Subscriptions.Add(subView); + } + } + } + + return View("/Areas/User/Views/User/Settings/BillingSettings.cshtml", model); + } + + return new StatusCodeResult(StatusCodes.Status403Forbidden); + } + [TrackPageView] public async Task SecuritySettings() { diff --git a/Teknik/Areas/User/Models/UploadSettings.cs b/Teknik/Areas/User/Models/UploadSettings.cs index 533c2f2..8d43113 100644 --- a/Teknik/Areas/User/Models/UploadSettings.cs +++ b/Teknik/Areas/User/Models/UploadSettings.cs @@ -21,14 +21,20 @@ namespace Teknik.Areas.Users.Models public ExpirationUnit ExpirationUnit { get; set; } [Column("MaxUploadStorage")] + // Maximum Allotted total upload storage public long? MaxUploadStorage { get; set; } + [Column("MaxUploadFileSize")] + // Maximum allowed file size + public long? MaxUploadFileSize { get; set; } + public UploadSettings() { Encrypt = false; ExpirationLength = 1; ExpirationUnit = ExpirationUnit.Never; MaxUploadStorage = null; + MaxUploadFileSize = null; } } } diff --git a/Teknik/Areas/User/Models/User.cs b/Teknik/Areas/User/Models/User.cs index a1c6a90..47fed46 100644 --- a/Teknik/Areas/User/Models/User.cs +++ b/Teknik/Areas/User/Models/User.cs @@ -6,6 +6,7 @@ using Teknik.Attributes; using Teknik.Models; using Teknik.Utilities; using Microsoft.AspNetCore.Identity; +using Teknik.Areas.Billing.Models; namespace Teknik.Areas.Users.Models { @@ -15,6 +16,8 @@ namespace Teknik.Areas.Users.Models public string Username { get; set; } + public virtual Customer BillingCustomer { get; set; } + public virtual ICollection Features { get; set; } public virtual InviteCode ClaimedInviteCode { get; set; } diff --git a/Teknik/Areas/User/ViewModels/BillingSettingsViewModel.cs b/Teknik/Areas/User/ViewModels/BillingSettingsViewModel.cs new file mode 100644 index 0000000..0efc6fa --- /dev/null +++ b/Teknik/Areas/User/ViewModels/BillingSettingsViewModel.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.BillingCore.Models; + +namespace Teknik.Areas.Users.ViewModels +{ + public class BillingSettingsViewModel : SettingsViewModel + { + public List Subscriptions { get; set; } + + public BillingSettingsViewModel() + { + Subscriptions = new List(); + } + } +} diff --git a/Teknik/Areas/User/ViewModels/SubscriptionViewModel.cs b/Teknik/Areas/User/ViewModels/SubscriptionViewModel.cs new file mode 100644 index 0000000..7b3ada2 --- /dev/null +++ b/Teknik/Areas/User/ViewModels/SubscriptionViewModel.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Teknik.ViewModels; + +namespace Teknik.Areas.Users.ViewModels +{ + public class SubscriptionViewModel : SettingsViewModel + { + public string SubscriptionId { get; set; } + + public string ProductId { get; set; } + + public string ProductName { get; set; } + + public decimal? Price { get; set; } + + public string Interval { get; set; } + + public long Storage { get; set; } + } +} diff --git a/Teknik/Areas/User/Views/User/Settings/BillingSettings.cshtml b/Teknik/Areas/User/Views/User/Settings/BillingSettings.cshtml new file mode 100644 index 0000000..f325081 --- /dev/null +++ b/Teknik/Areas/User/Views/User/Settings/BillingSettings.cshtml @@ -0,0 +1,32 @@ +@model Teknik.Areas.Users.ViewModels.BillingSettingsViewModel + +@using Teknik.BillingCore.Models + +@{ + Layout = "~/Areas/User/Views/User/Settings/Settings.cshtml"; +} + +
+
+
+ +
+
    + @if (Model.Subscriptions.Any()) + { + foreach (var subscription in Model.Subscriptions) + { +
  • +

    @subscription.ProductName: @(StringHelper.GetBytesReadable(subscription.Storage)) for @($"${subscription.Price:0.00} / {subscription.Interval}")

    +

    Cancel Subscription

    +
  • + } + } + else + { +
  • No Active Subscriptions
  • + } +
+
+
+
diff --git a/Teknik/Areas/User/Views/User/Settings/Settings.cshtml b/Teknik/Areas/User/Views/User/Settings/Settings.cshtml index ffe6b07..e6c1127 100644 --- a/Teknik/Areas/User/Views/User/Settings/Settings.cshtml +++ b/Teknik/Areas/User/Views/User/Settings/Settings.cshtml @@ -17,6 +17,7 @@
Personal Settings
Profile Account + Billing Security Invite Codes Blogging diff --git a/Teknik/Data/Migrations/20211009192142_AddBillingCustomers.Designer.cs b/Teknik/Data/Migrations/20211009192142_AddBillingCustomers.Designer.cs new file mode 100644 index 0000000..3851a49 --- /dev/null +++ b/Teknik/Data/Migrations/20211009192142_AddBillingCustomers.Designer.cs @@ -0,0 +1,1057 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Teknik.Data; + +namespace Teknik.Data.Migrations +{ + [DbContext(typeof(TeknikEntities))] + [Migration("20211009192142_AddBillingCustomers")] + partial class AddBillingCustomers + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.7") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Teknik.Areas.Billing.Models.Customer", b => + { + b.Property("CustomerId") + .HasColumnType("nvarchar(450)") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("CustomerId"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b => + { + b.Property("BlogId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("BlogId"); + + b.HasIndex("UserId"); + + b.ToTable("Blogs"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b => + { + b.Property("BlogPostId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Article") + .HasColumnType("nvarchar(max)"); + + b.Property("BlogId") + .HasColumnType("int"); + + b.Property("DateEdited") + .HasColumnType("datetime2"); + + b.Property("DatePosted") + .HasColumnType("datetime2"); + + b.Property("DatePublished") + .HasColumnType("datetime2"); + + b.Property("Published") + .HasColumnType("bit"); + + b.Property("System") + .HasColumnType("bit"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("BlogPostId"); + + b.HasIndex("BlogId"); + + b.ToTable("BlogPosts"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostComment", b => + { + b.Property("BlogPostCommentId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Article") + .HasColumnType("nvarchar(max)"); + + b.Property("BlogPostId") + .HasColumnType("int"); + + b.Property("DateEdited") + .HasColumnType("datetime2"); + + b.Property("DatePosted") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("BlogPostCommentId"); + + b.HasIndex("BlogPostId"); + + b.HasIndex("UserId"); + + b.ToTable("BlogPostComments"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostTag", b => + { + b.Property("BlogPostTagId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("BlogPostId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("BlogPostTagId"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostTags"); + }); + + modelBuilder.Entity("Teknik.Areas.Contact.Models.Contact", b => + { + b.Property("ContactId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateAdded") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("Message") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Subject") + .HasColumnType("nvarchar(max)"); + + b.HasKey("ContactId"); + + b.ToTable("Contact"); + }); + + modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b => + { + b.Property("PasteId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("BlockSize") + .HasColumnType("int"); + + b.Property("Content") + .HasColumnType("nvarchar(max)"); + + b.Property("DateEdited") + .HasColumnType("datetime2"); + + b.Property("DatePosted") + .HasColumnType("datetime2"); + + b.Property("DeleteKey") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("ExpireDate") + .HasColumnType("datetime2"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("HashedPassword") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("IV") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("Key") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("KeySize") + .HasColumnType("int"); + + b.Property("MaxViews") + .HasColumnType("int"); + + b.Property("Syntax") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Views") + .HasColumnType("int"); + + b.HasKey("PasteId"); + + b.HasIndex("UserId"); + + b.ToTable("Pastes"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.Podcast", b => + { + b.Property("PodcastId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateEdited") + .HasColumnType("datetime2"); + + b.Property("DatePosted") + .HasColumnType("datetime2"); + + b.Property("DatePublished") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Episode") + .HasColumnType("int"); + + b.Property("Published") + .HasColumnType("bit"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("PodcastId"); + + b.ToTable("Podcasts"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastComment", b => + { + b.Property("PodcastCommentId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Article") + .HasColumnType("nvarchar(max)"); + + b.Property("DateEdited") + .HasColumnType("datetime2"); + + b.Property("DatePosted") + .HasColumnType("datetime2"); + + b.Property("PodcastId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("PodcastCommentId"); + + b.HasIndex("PodcastId"); + + b.HasIndex("UserId"); + + b.ToTable("PodcastComments"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastFile", b => + { + b.Property("PodcastFileId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ContentLength") + .HasColumnType("bigint"); + + b.Property("ContentType") + .HasColumnType("nvarchar(max)"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)"); + + b.Property("Path") + .HasColumnType("nvarchar(max)"); + + b.Property("PodcastId") + .HasColumnType("int"); + + b.Property("Size") + .HasColumnType("int"); + + b.HasKey("PodcastFileId"); + + b.HasIndex("PodcastId"); + + b.ToTable("PodcastFiles"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastTag", b => + { + b.Property("PodcastTagId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("PodcastId") + .HasColumnType("int"); + + b.HasKey("PodcastTagId"); + + b.HasIndex("PodcastId"); + + b.ToTable("PodcastTags"); + }); + + modelBuilder.Entity("Teknik.Areas.Shortener.Models.ShortenedUrl", b => + { + b.Property("ShortenedUrlId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateAdded") + .HasColumnType("datetime2"); + + b.Property("OriginalUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("ShortUrl") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Views") + .HasColumnType("int"); + + b.HasKey("ShortenedUrlId"); + + b.HasIndex("UserId"); + + b.ToTable("ShortenedUrls"); + }); + + modelBuilder.Entity("Teknik.Areas.Stats.Models.Takedown", b => + { + b.Property("TakedownId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ActionTaken") + .HasColumnType("nvarchar(max)"); + + b.Property("DateActionTaken") + .HasColumnType("datetime2"); + + b.Property("DateRequested") + .HasColumnType("datetime2"); + + b.Property("Reason") + .HasColumnType("nvarchar(max)"); + + b.Property("Requester") + .HasColumnType("nvarchar(max)"); + + b.Property("RequesterContact") + .HasColumnType("nvarchar(max)"); + + b.HasKey("TakedownId"); + + b.ToTable("Takedowns"); + }); + + modelBuilder.Entity("Teknik.Areas.Stats.Models.Transaction", b => + { + b.Property("TransactionId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Amount") + .HasColumnType("decimal(19,5)"); + + b.Property("Currency") + .HasColumnType("int"); + + b.Property("DateSent") + .HasColumnType("datetime2"); + + b.Property("Reason") + .HasColumnType("nvarchar(max)"); + + b.HasKey("TransactionId"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("Teknik.Areas.Upload.Models.Upload", b => + { + b.Property("UploadId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("BlockSize") + .HasColumnType("int"); + + b.Property("ContentLength") + .HasColumnType("bigint"); + + b.Property("ContentType") + .HasColumnType("nvarchar(max)"); + + b.Property("DateUploaded") + .HasColumnType("datetime2"); + + b.Property("DeleteKey") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("Downloads") + .HasColumnType("int"); + + b.Property("ExpireDate") + .HasColumnType("datetime2"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("IV") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("Key") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("KeySize") + .HasColumnType("int"); + + b.Property("MaxDownloads") + .HasColumnType("int"); + + b.Property("Takedown_TakedownId") + .HasColumnType("int"); + + b.Property("Url") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("UploadId"); + + b.HasIndex("Takedown_TakedownId"); + + b.HasIndex("UserId"); + + b.ToTable("Uploads"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.Feature", b => + { + b.Property("FeatureId") + .HasColumnType("nvarchar(450)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("FeatureId"); + + b.ToTable("Features"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.InviteCode", b => + { + b.Property("InviteCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("ClaimedUserId") + .HasColumnType("int"); + + b.Property("Code") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.HasKey("InviteCodeId"); + + b.HasIndex("ClaimedUserId") + .IsUnique() + .HasFilter("[ClaimedUserId] IS NOT NULL"); + + b.HasIndex("OwnerId"); + + b.ToTable("InviteCodes"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.UserFeature", b => + { + b.Property("UserFeatureId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FeatureId") + .HasColumnType("nvarchar(450)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("UserFeatureId"); + + b.HasIndex("FeatureId"); + + b.HasIndex("UserId"); + + b.ToTable("UserFeatures"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => + { + b.Property("VaultId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateEdited") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Views") + .HasColumnType("int"); + + b.HasKey("VaultId"); + + b.HasIndex("UserId"); + + b.ToTable("Vaults"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.VaultItem", b => + { + b.Property("VaultItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateAdded") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Index") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("VaultId") + .HasColumnType("int"); + + b.HasKey("VaultItemId"); + + b.HasIndex("VaultId"); + + b.ToTable("VaultItems"); + + b.HasDiscriminator("Discriminator").HasValue("VaultItem"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.PasteVaultItem", b => + { + b.HasBaseType("Teknik.Areas.Vault.Models.VaultItem"); + + b.Property("PasteId") + .HasColumnType("int"); + + b.HasIndex("PasteId"); + + b.HasDiscriminator().HasValue("PasteVaultItem"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.UploadVaultItem", b => + { + b.HasBaseType("Teknik.Areas.Vault.Models.VaultItem"); + + b.Property("UploadId") + .HasColumnType("int"); + + b.HasIndex("UploadId"); + + b.HasDiscriminator().HasValue("UploadVaultItem"); + }); + + modelBuilder.Entity("Teknik.Areas.Billing.Models.Customer", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithOne("BillingCustomer") + .HasForeignKey("Teknik.Areas.Billing.Models.Customer", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b => + { + b.HasOne("Teknik.Areas.Blog.Models.Blog", "Blog") + .WithMany("BlogPosts") + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Blog"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostComment", b => + { + b.HasOne("Teknik.Areas.Blog.Models.BlogPost", "BlogPost") + .WithMany("Comments") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("BlogPost"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostTag", b => + { + b.HasOne("Teknik.Areas.Blog.Models.BlogPost", "BlogPost") + .WithMany("Tags") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Pastes") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastComment", b => + { + b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") + .WithMany("Comments") + .HasForeignKey("PodcastId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Podcast"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastFile", b => + { + b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") + .WithMany("Files") + .HasForeignKey("PodcastId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Podcast"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastTag", b => + { + b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") + .WithMany("Tags") + .HasForeignKey("PodcastId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Podcast"); + }); + + modelBuilder.Entity("Teknik.Areas.Shortener.Models.ShortenedUrl", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("ShortenedUrls") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Upload.Models.Upload", b => + { + b.HasOne("Teknik.Areas.Stats.Models.Takedown", null) + .WithMany("Attachments") + .HasForeignKey("Takedown_TakedownId"); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Uploads") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.InviteCode", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "ClaimedUser") + .WithOne("ClaimedInviteCode") + .HasForeignKey("Teknik.Areas.Users.Models.InviteCode", "ClaimedUserId"); + + b.HasOne("Teknik.Areas.Users.Models.User", "Owner") + .WithMany("OwnedInviteCodes") + .HasForeignKey("OwnerId"); + + b.Navigation("ClaimedUser"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => + { + b.OwnsOne("Teknik.Areas.Users.Models.BlogSettings", "BlogSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("Description") + .HasColumnType("nvarchar(max)") + .HasColumnName("Description"); + + b1.Property("Title") + .HasColumnType("nvarchar(max)") + .HasColumnName("Title"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.OwnsOne("Teknik.Areas.Users.Models.UploadSettings", "UploadSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("Encrypt") + .HasColumnType("bit") + .HasColumnName("Encrypt"); + + b1.Property("ExpirationLength") + .HasColumnType("int") + .HasColumnName("ExpirationLength"); + + b1.Property("ExpirationUnit") + .HasColumnType("int") + .HasColumnName("ExpirationUnit"); + + b1.Property("MaxUploadStorage") + .HasColumnType("bigint") + .HasColumnName("MaxUploadStorage"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.OwnsOne("Teknik.Areas.Users.Models.UserSettings", "UserSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("About") + .HasColumnType("nvarchar(max)") + .HasColumnName("About"); + + b1.Property("Quote") + .HasColumnType("nvarchar(max)") + .HasColumnName("Quote"); + + b1.Property("Website") + .HasColumnType("nvarchar(max)") + .HasColumnName("Website"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.Navigation("BlogSettings"); + + b.Navigation("UploadSettings"); + + b.Navigation("UserSettings"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.UserFeature", b => + { + b.HasOne("Teknik.Areas.Users.Models.Feature", "Feature") + .WithMany() + .HasForeignKey("FeatureId"); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Features") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Feature"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Vaults") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.VaultItem", b => + { + b.HasOne("Teknik.Areas.Vault.Models.Vault", "Vault") + .WithMany("VaultItems") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.PasteVaultItem", b => + { + b.HasOne("Teknik.Areas.Paste.Models.Paste", "Paste") + .WithMany("PasteVaultItems") + .HasForeignKey("PasteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Paste"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.UploadVaultItem", b => + { + b.HasOne("Teknik.Areas.Upload.Models.Upload", "Upload") + .WithMany("UploadVaultItems") + .HasForeignKey("UploadId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Upload"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b => + { + b.Navigation("BlogPosts"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b => + { + b.Navigation("Comments"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b => + { + b.Navigation("PasteVaultItems"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.Podcast", b => + { + b.Navigation("Comments"); + + b.Navigation("Files"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("Teknik.Areas.Stats.Models.Takedown", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("Teknik.Areas.Upload.Models.Upload", b => + { + b.Navigation("UploadVaultItems"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => + { + b.Navigation("BillingCustomer"); + + b.Navigation("ClaimedInviteCode"); + + b.Navigation("Features"); + + b.Navigation("OwnedInviteCodes"); + + b.Navigation("Pastes"); + + b.Navigation("ShortenedUrls"); + + b.Navigation("Uploads"); + + b.Navigation("Vaults"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => + { + b.Navigation("VaultItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Teknik/Data/Migrations/20211009192142_AddBillingCustomers.cs b/Teknik/Data/Migrations/20211009192142_AddBillingCustomers.cs new file mode 100644 index 0000000..e113491 --- /dev/null +++ b/Teknik/Data/Migrations/20211009192142_AddBillingCustomers.cs @@ -0,0 +1,40 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Teknik.Data.Migrations +{ + public partial class AddBillingCustomers : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Customers", + columns: table => new + { + CustomerId = table.Column(type: "nvarchar(450)", nullable: false), + UserId = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Customers", x => x.CustomerId); + table.ForeignKey( + name: "FK_Customers_Users_UserId", + column: x => x.UserId, + principalTable: "Users", + principalColumn: "UserId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_Customers_UserId", + table: "Customers", + column: "UserId", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Customers"); + } + } +} diff --git a/Teknik/Data/Migrations/20211113052031_RenameUploadSettings.Designer.cs b/Teknik/Data/Migrations/20211113052031_RenameUploadSettings.Designer.cs new file mode 100644 index 0000000..75dfa9c --- /dev/null +++ b/Teknik/Data/Migrations/20211113052031_RenameUploadSettings.Designer.cs @@ -0,0 +1,1061 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Teknik.Data; + +namespace Teknik.Data.Migrations +{ + [DbContext(typeof(TeknikEntities))] + [Migration("20211113052031_RenameUploadSettings")] + partial class RenameUploadSettings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.7") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Teknik.Areas.Billing.Models.Customer", b => + { + b.Property("CustomerId") + .HasColumnType("nvarchar(450)") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("CustomerId"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Customers"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b => + { + b.Property("BlogId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("BlogId"); + + b.HasIndex("UserId"); + + b.ToTable("Blogs"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b => + { + b.Property("BlogPostId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Article") + .HasColumnType("nvarchar(max)"); + + b.Property("BlogId") + .HasColumnType("int"); + + b.Property("DateEdited") + .HasColumnType("datetime2"); + + b.Property("DatePosted") + .HasColumnType("datetime2"); + + b.Property("DatePublished") + .HasColumnType("datetime2"); + + b.Property("Published") + .HasColumnType("bit"); + + b.Property("System") + .HasColumnType("bit"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("BlogPostId"); + + b.HasIndex("BlogId"); + + b.ToTable("BlogPosts"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostComment", b => + { + b.Property("BlogPostCommentId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Article") + .HasColumnType("nvarchar(max)"); + + b.Property("BlogPostId") + .HasColumnType("int"); + + b.Property("DateEdited") + .HasColumnType("datetime2"); + + b.Property("DatePosted") + .HasColumnType("datetime2"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("BlogPostCommentId"); + + b.HasIndex("BlogPostId"); + + b.HasIndex("UserId"); + + b.ToTable("BlogPostComments"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostTag", b => + { + b.Property("BlogPostTagId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("BlogPostId") + .HasColumnType("int"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("BlogPostTagId"); + + b.HasIndex("BlogPostId"); + + b.ToTable("BlogPostTags"); + }); + + modelBuilder.Entity("Teknik.Areas.Contact.Models.Contact", b => + { + b.Property("ContactId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateAdded") + .HasColumnType("datetime2"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("Message") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("Subject") + .HasColumnType("nvarchar(max)"); + + b.HasKey("ContactId"); + + b.ToTable("Contact"); + }); + + modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b => + { + b.Property("PasteId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("BlockSize") + .HasColumnType("int"); + + b.Property("Content") + .HasColumnType("nvarchar(max)"); + + b.Property("DateEdited") + .HasColumnType("datetime2"); + + b.Property("DatePosted") + .HasColumnType("datetime2"); + + b.Property("DeleteKey") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("ExpireDate") + .HasColumnType("datetime2"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("HashedPassword") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("IV") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("Key") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("KeySize") + .HasColumnType("int"); + + b.Property("MaxViews") + .HasColumnType("int"); + + b.Property("Syntax") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Views") + .HasColumnType("int"); + + b.HasKey("PasteId"); + + b.HasIndex("UserId"); + + b.ToTable("Pastes"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.Podcast", b => + { + b.Property("PodcastId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateEdited") + .HasColumnType("datetime2"); + + b.Property("DatePosted") + .HasColumnType("datetime2"); + + b.Property("DatePublished") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Episode") + .HasColumnType("int"); + + b.Property("Published") + .HasColumnType("bit"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.HasKey("PodcastId"); + + b.ToTable("Podcasts"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastComment", b => + { + b.Property("PodcastCommentId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Article") + .HasColumnType("nvarchar(max)"); + + b.Property("DateEdited") + .HasColumnType("datetime2"); + + b.Property("DatePosted") + .HasColumnType("datetime2"); + + b.Property("PodcastId") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("PodcastCommentId"); + + b.HasIndex("PodcastId"); + + b.HasIndex("UserId"); + + b.ToTable("PodcastComments"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastFile", b => + { + b.Property("PodcastFileId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ContentLength") + .HasColumnType("bigint"); + + b.Property("ContentType") + .HasColumnType("nvarchar(max)"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)"); + + b.Property("Path") + .HasColumnType("nvarchar(max)"); + + b.Property("PodcastId") + .HasColumnType("int"); + + b.Property("Size") + .HasColumnType("int"); + + b.HasKey("PodcastFileId"); + + b.HasIndex("PodcastId"); + + b.ToTable("PodcastFiles"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastTag", b => + { + b.Property("PodcastTagId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.Property("PodcastId") + .HasColumnType("int"); + + b.HasKey("PodcastTagId"); + + b.HasIndex("PodcastId"); + + b.ToTable("PodcastTags"); + }); + + modelBuilder.Entity("Teknik.Areas.Shortener.Models.ShortenedUrl", b => + { + b.Property("ShortenedUrlId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateAdded") + .HasColumnType("datetime2"); + + b.Property("OriginalUrl") + .HasColumnType("nvarchar(max)"); + + b.Property("ShortUrl") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Views") + .HasColumnType("int"); + + b.HasKey("ShortenedUrlId"); + + b.HasIndex("UserId"); + + b.ToTable("ShortenedUrls"); + }); + + modelBuilder.Entity("Teknik.Areas.Stats.Models.Takedown", b => + { + b.Property("TakedownId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ActionTaken") + .HasColumnType("nvarchar(max)"); + + b.Property("DateActionTaken") + .HasColumnType("datetime2"); + + b.Property("DateRequested") + .HasColumnType("datetime2"); + + b.Property("Reason") + .HasColumnType("nvarchar(max)"); + + b.Property("Requester") + .HasColumnType("nvarchar(max)"); + + b.Property("RequesterContact") + .HasColumnType("nvarchar(max)"); + + b.HasKey("TakedownId"); + + b.ToTable("Takedowns"); + }); + + modelBuilder.Entity("Teknik.Areas.Stats.Models.Transaction", b => + { + b.Property("TransactionId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Amount") + .HasColumnType("decimal(19,5)"); + + b.Property("Currency") + .HasColumnType("int"); + + b.Property("DateSent") + .HasColumnType("datetime2"); + + b.Property("Reason") + .HasColumnType("nvarchar(max)"); + + b.HasKey("TransactionId"); + + b.ToTable("Transactions"); + }); + + modelBuilder.Entity("Teknik.Areas.Upload.Models.Upload", b => + { + b.Property("UploadId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("BlockSize") + .HasColumnType("int"); + + b.Property("ContentLength") + .HasColumnType("bigint"); + + b.Property("ContentType") + .HasColumnType("nvarchar(max)"); + + b.Property("DateUploaded") + .HasColumnType("datetime2"); + + b.Property("DeleteKey") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("Downloads") + .HasColumnType("int"); + + b.Property("ExpireDate") + .HasColumnType("datetime2"); + + b.Property("FileName") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("IV") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("Key") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("KeySize") + .HasColumnType("int"); + + b.Property("MaxDownloads") + .HasColumnType("int"); + + b.Property("Takedown_TakedownId") + .HasColumnType("int"); + + b.Property("Url") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("UploadId"); + + b.HasIndex("Takedown_TakedownId"); + + b.HasIndex("UserId"); + + b.ToTable("Uploads"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.Feature", b => + { + b.Property("FeatureId") + .HasColumnType("nvarchar(450)"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("Name") + .HasColumnType("nvarchar(max)"); + + b.HasKey("FeatureId"); + + b.ToTable("Features"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.InviteCode", b => + { + b.Property("InviteCodeId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Active") + .HasColumnType("bit"); + + b.Property("ClaimedUserId") + .HasColumnType("int"); + + b.Property("Code") + .HasColumnType("nvarchar(max)") + .HasAnnotation("CaseSensitive", true); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.HasKey("InviteCodeId"); + + b.HasIndex("ClaimedUserId") + .IsUnique() + .HasFilter("[ClaimedUserId] IS NOT NULL"); + + b.HasIndex("OwnerId"); + + b.ToTable("InviteCodes"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => + { + b.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Username") + .HasColumnType("nvarchar(max)"); + + b.HasKey("UserId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.UserFeature", b => + { + b.Property("UserFeatureId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("FeatureId") + .HasColumnType("nvarchar(450)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("UserFeatureId"); + + b.HasIndex("FeatureId"); + + b.HasIndex("UserId"); + + b.ToTable("UserFeatures"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => + { + b.Property("VaultId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateCreated") + .HasColumnType("datetime2"); + + b.Property("DateEdited") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("int"); + + b.Property("Views") + .HasColumnType("int"); + + b.HasKey("VaultId"); + + b.HasIndex("UserId"); + + b.ToTable("Vaults"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.VaultItem", b => + { + b.Property("VaultItemId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("DateAdded") + .HasColumnType("datetime2"); + + b.Property("Description") + .HasColumnType("nvarchar(max)"); + + b.Property("Discriminator") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Index") + .HasColumnType("int"); + + b.Property("Title") + .HasColumnType("nvarchar(max)"); + + b.Property("VaultId") + .HasColumnType("int"); + + b.HasKey("VaultItemId"); + + b.HasIndex("VaultId"); + + b.ToTable("VaultItems"); + + b.HasDiscriminator("Discriminator").HasValue("VaultItem"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.PasteVaultItem", b => + { + b.HasBaseType("Teknik.Areas.Vault.Models.VaultItem"); + + b.Property("PasteId") + .HasColumnType("int"); + + b.HasIndex("PasteId"); + + b.HasDiscriminator().HasValue("PasteVaultItem"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.UploadVaultItem", b => + { + b.HasBaseType("Teknik.Areas.Vault.Models.VaultItem"); + + b.Property("UploadId") + .HasColumnType("int"); + + b.HasIndex("UploadId"); + + b.HasDiscriminator().HasValue("UploadVaultItem"); + }); + + modelBuilder.Entity("Teknik.Areas.Billing.Models.Customer", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithOne("BillingCustomer") + .HasForeignKey("Teknik.Areas.Billing.Models.Customer", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b => + { + b.HasOne("Teknik.Areas.Blog.Models.Blog", "Blog") + .WithMany("BlogPosts") + .HasForeignKey("BlogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Blog"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostComment", b => + { + b.HasOne("Teknik.Areas.Blog.Models.BlogPost", "BlogPost") + .WithMany("Comments") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany() + .HasForeignKey("UserId"); + + b.Navigation("BlogPost"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostTag", b => + { + b.HasOne("Teknik.Areas.Blog.Models.BlogPost", "BlogPost") + .WithMany("Tags") + .HasForeignKey("BlogPostId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); + }); + + modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Pastes") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastComment", b => + { + b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") + .WithMany("Comments") + .HasForeignKey("PodcastId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Podcast"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastFile", b => + { + b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") + .WithMany("Files") + .HasForeignKey("PodcastId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Podcast"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastTag", b => + { + b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") + .WithMany("Tags") + .HasForeignKey("PodcastId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Podcast"); + }); + + modelBuilder.Entity("Teknik.Areas.Shortener.Models.ShortenedUrl", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("ShortenedUrls") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Upload.Models.Upload", b => + { + b.HasOne("Teknik.Areas.Stats.Models.Takedown", null) + .WithMany("Attachments") + .HasForeignKey("Takedown_TakedownId"); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Uploads") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.InviteCode", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "ClaimedUser") + .WithOne("ClaimedInviteCode") + .HasForeignKey("Teknik.Areas.Users.Models.InviteCode", "ClaimedUserId"); + + b.HasOne("Teknik.Areas.Users.Models.User", "Owner") + .WithMany("OwnedInviteCodes") + .HasForeignKey("OwnerId"); + + b.Navigation("ClaimedUser"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => + { + b.OwnsOne("Teknik.Areas.Users.Models.BlogSettings", "BlogSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("Description") + .HasColumnType("nvarchar(max)") + .HasColumnName("Description"); + + b1.Property("Title") + .HasColumnType("nvarchar(max)") + .HasColumnName("Title"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.OwnsOne("Teknik.Areas.Users.Models.UploadSettings", "UploadSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("Encrypt") + .HasColumnType("bit") + .HasColumnName("Encrypt"); + + b1.Property("ExpirationLength") + .HasColumnType("int") + .HasColumnName("ExpirationLength"); + + b1.Property("ExpirationUnit") + .HasColumnType("int") + .HasColumnName("ExpirationUnit"); + + b1.Property("MaxUploadFileSize") + .HasColumnType("bigint") + .HasColumnName("MaxUploadFileSize"); + + b1.Property("MaxUploadStorage") + .HasColumnType("bigint") + .HasColumnName("MaxUploadStorage"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.OwnsOne("Teknik.Areas.Users.Models.UserSettings", "UserSettings", b1 => + { + b1.Property("UserId") + .ValueGeneratedOnAdd() + .HasColumnType("int") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b1.Property("About") + .HasColumnType("nvarchar(max)") + .HasColumnName("About"); + + b1.Property("Quote") + .HasColumnType("nvarchar(max)") + .HasColumnName("Quote"); + + b1.Property("Website") + .HasColumnType("nvarchar(max)") + .HasColumnName("Website"); + + b1.HasKey("UserId"); + + b1.ToTable("Users"); + + b1.WithOwner() + .HasForeignKey("UserId"); + }); + + b.Navigation("BlogSettings"); + + b.Navigation("UploadSettings"); + + b.Navigation("UserSettings"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.UserFeature", b => + { + b.HasOne("Teknik.Areas.Users.Models.Feature", "Feature") + .WithMany() + .HasForeignKey("FeatureId"); + + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Features") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Feature"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithMany("Vaults") + .HasForeignKey("UserId"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.VaultItem", b => + { + b.HasOne("Teknik.Areas.Vault.Models.Vault", "Vault") + .WithMany("VaultItems") + .HasForeignKey("VaultId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Vault"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.PasteVaultItem", b => + { + b.HasOne("Teknik.Areas.Paste.Models.Paste", "Paste") + .WithMany("PasteVaultItems") + .HasForeignKey("PasteId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Paste"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.UploadVaultItem", b => + { + b.HasOne("Teknik.Areas.Upload.Models.Upload", "Upload") + .WithMany("UploadVaultItems") + .HasForeignKey("UploadId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Upload"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b => + { + b.Navigation("BlogPosts"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b => + { + b.Navigation("Comments"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b => + { + b.Navigation("PasteVaultItems"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.Podcast", b => + { + b.Navigation("Comments"); + + b.Navigation("Files"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("Teknik.Areas.Stats.Models.Takedown", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("Teknik.Areas.Upload.Models.Upload", b => + { + b.Navigation("UploadVaultItems"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => + { + b.Navigation("BillingCustomer"); + + b.Navigation("ClaimedInviteCode"); + + b.Navigation("Features"); + + b.Navigation("OwnedInviteCodes"); + + b.Navigation("Pastes"); + + b.Navigation("ShortenedUrls"); + + b.Navigation("Uploads"); + + b.Navigation("Vaults"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => + { + b.Navigation("VaultItems"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Teknik/Data/Migrations/20211113052031_RenameUploadSettings.cs b/Teknik/Data/Migrations/20211113052031_RenameUploadSettings.cs new file mode 100644 index 0000000..03fbeda --- /dev/null +++ b/Teknik/Data/Migrations/20211113052031_RenameUploadSettings.cs @@ -0,0 +1,23 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Teknik.Data.Migrations +{ + public partial class RenameUploadSettings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MaxUploadFileSize", + table: "Users", + type: "bigint", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MaxUploadFileSize", + table: "Users"); + } + } +} diff --git a/Teknik/Data/Migrations/TeknikEntitiesModelSnapshot.cs b/Teknik/Data/Migrations/TeknikEntitiesModelSnapshot.cs index 1f574fb..1f88cb4 100644 --- a/Teknik/Data/Migrations/TeknikEntitiesModelSnapshot.cs +++ b/Teknik/Data/Migrations/TeknikEntitiesModelSnapshot.cs @@ -15,17 +15,36 @@ namespace Teknik.Data.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.2.6-servicing-10079") .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("ProductVersion", "5.0.7") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + modelBuilder.Entity("Teknik.Areas.Billing.Models.Customer", b => + { + b.Property("CustomerId") + .HasColumnType("nvarchar(450)") + .HasAnnotation("CaseSensitive", true); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("CustomerId"); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("Customers"); + }); + modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b => { b.Property("BlogId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("UserId"); + b.Property("UserId") + .HasColumnType("int"); b.HasKey("BlogId"); @@ -38,23 +57,32 @@ namespace Teknik.Data.Migrations { b.Property("BlogPostId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("Article"); + b.Property("Article") + .HasColumnType("nvarchar(max)"); - b.Property("BlogId"); + b.Property("BlogId") + .HasColumnType("int"); - b.Property("DateEdited"); + b.Property("DateEdited") + .HasColumnType("datetime2"); - b.Property("DatePosted"); + b.Property("DatePosted") + .HasColumnType("datetime2"); - b.Property("DatePublished"); + b.Property("DatePublished") + .HasColumnType("datetime2"); - b.Property("Published"); + b.Property("Published") + .HasColumnType("bit"); - b.Property("System"); + b.Property("System") + .HasColumnType("bit"); - b.Property("Title"); + b.Property("Title") + .HasColumnType("nvarchar(max)"); b.HasKey("BlogPostId"); @@ -67,17 +95,23 @@ namespace Teknik.Data.Migrations { b.Property("BlogPostCommentId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("Article"); + b.Property("Article") + .HasColumnType("nvarchar(max)"); - b.Property("BlogPostId"); + b.Property("BlogPostId") + .HasColumnType("int"); - b.Property("DateEdited"); + b.Property("DateEdited") + .HasColumnType("datetime2"); - b.Property("DatePosted"); + b.Property("DatePosted") + .HasColumnType("datetime2"); - b.Property("UserId"); + b.Property("UserId") + .HasColumnType("int"); b.HasKey("BlogPostCommentId"); @@ -92,13 +126,17 @@ namespace Teknik.Data.Migrations { b.Property("BlogPostTagId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("BlogPostId"); + b.Property("BlogPostId") + .HasColumnType("int"); - b.Property("Description"); + b.Property("Description") + .HasColumnType("nvarchar(max)"); - b.Property("Name"); + b.Property("Name") + .HasColumnType("nvarchar(max)"); b.HasKey("BlogPostTagId"); @@ -111,17 +149,23 @@ namespace Teknik.Data.Migrations { b.Property("ContactId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("DateAdded"); + b.Property("DateAdded") + .HasColumnType("datetime2"); - b.Property("Email"); + b.Property("Email") + .HasColumnType("nvarchar(max)"); - b.Property("Message"); + b.Property("Message") + .HasColumnType("nvarchar(max)"); - b.Property("Name"); + b.Property("Name") + .HasColumnType("nvarchar(max)"); - b.Property("Subject"); + b.Property("Subject") + .HasColumnType("nvarchar(max)"); b.HasKey("ContactId"); @@ -132,47 +176,65 @@ namespace Teknik.Data.Migrations { b.Property("PasteId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("BlockSize"); + b.Property("BlockSize") + .HasColumnType("int"); - b.Property("Content"); + b.Property("Content") + .HasColumnType("nvarchar(max)"); - b.Property("DateEdited"); + b.Property("DateEdited") + .HasColumnType("datetime2"); - b.Property("DatePosted"); + b.Property("DatePosted") + .HasColumnType("datetime2"); b.Property("DeleteKey") + .HasColumnType("nvarchar(max)") .HasAnnotation("CaseSensitive", true); - b.Property("ExpireDate"); + b.Property("ExpireDate") + .HasColumnType("datetime2"); b.Property("FileName") + .HasColumnType("nvarchar(max)") .HasAnnotation("CaseSensitive", true); b.Property("HashedPassword") + .HasColumnType("nvarchar(max)") .HasAnnotation("CaseSensitive", true); b.Property("IV") + .HasColumnType("nvarchar(max)") .HasAnnotation("CaseSensitive", true); b.Property("Key") + .HasColumnType("nvarchar(max)") .HasAnnotation("CaseSensitive", true); - b.Property("KeySize"); + b.Property("KeySize") + .HasColumnType("int"); - b.Property("MaxViews"); + b.Property("MaxViews") + .HasColumnType("int"); - b.Property("Syntax"); + b.Property("Syntax") + .HasColumnType("nvarchar(max)"); - b.Property("Title"); + b.Property("Title") + .HasColumnType("nvarchar(max)"); b.Property("Url") + .HasColumnType("nvarchar(max)") .HasAnnotation("CaseSensitive", true); - b.Property("UserId"); + b.Property("UserId") + .HasColumnType("int"); - b.Property("Views"); + b.Property("Views") + .HasColumnType("int"); b.HasKey("PasteId"); @@ -185,21 +247,29 @@ namespace Teknik.Data.Migrations { b.Property("PodcastId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("DateEdited"); + b.Property("DateEdited") + .HasColumnType("datetime2"); - b.Property("DatePosted"); + b.Property("DatePosted") + .HasColumnType("datetime2"); - b.Property("DatePublished"); + b.Property("DatePublished") + .HasColumnType("datetime2"); - b.Property("Description"); + b.Property("Description") + .HasColumnType("nvarchar(max)"); - b.Property("Episode"); + b.Property("Episode") + .HasColumnType("int"); - b.Property("Published"); + b.Property("Published") + .HasColumnType("bit"); - b.Property("Title"); + b.Property("Title") + .HasColumnType("nvarchar(max)"); b.HasKey("PodcastId"); @@ -210,17 +280,23 @@ namespace Teknik.Data.Migrations { b.Property("PodcastCommentId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("Article"); + b.Property("Article") + .HasColumnType("nvarchar(max)"); - b.Property("DateEdited"); + b.Property("DateEdited") + .HasColumnType("datetime2"); - b.Property("DatePosted"); + b.Property("DatePosted") + .HasColumnType("datetime2"); - b.Property("PodcastId"); + b.Property("PodcastId") + .HasColumnType("int"); - b.Property("UserId"); + b.Property("UserId") + .HasColumnType("int"); b.HasKey("PodcastCommentId"); @@ -235,19 +311,26 @@ namespace Teknik.Data.Migrations { b.Property("PodcastFileId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("ContentLength"); + b.Property("ContentLength") + .HasColumnType("bigint"); - b.Property("ContentType"); + b.Property("ContentType") + .HasColumnType("nvarchar(max)"); - b.Property("FileName"); + b.Property("FileName") + .HasColumnType("nvarchar(max)"); - b.Property("Path"); + b.Property("Path") + .HasColumnType("nvarchar(max)"); - b.Property("PodcastId"); + b.Property("PodcastId") + .HasColumnType("int"); - b.Property("Size"); + b.Property("Size") + .HasColumnType("int"); b.HasKey("PodcastFileId"); @@ -260,13 +343,17 @@ namespace Teknik.Data.Migrations { b.Property("PodcastTagId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("Description"); + b.Property("Description") + .HasColumnType("nvarchar(max)"); - b.Property("Name"); + b.Property("Name") + .HasColumnType("nvarchar(max)"); - b.Property("PodcastId"); + b.Property("PodcastId") + .HasColumnType("int"); b.HasKey("PodcastTagId"); @@ -279,18 +366,24 @@ namespace Teknik.Data.Migrations { b.Property("ShortenedUrlId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("DateAdded"); + b.Property("DateAdded") + .HasColumnType("datetime2"); - b.Property("OriginalUrl"); + b.Property("OriginalUrl") + .HasColumnType("nvarchar(max)"); b.Property("ShortUrl") + .HasColumnType("nvarchar(max)") .HasAnnotation("CaseSensitive", true); - b.Property("UserId"); + b.Property("UserId") + .HasColumnType("int"); - b.Property("Views"); + b.Property("Views") + .HasColumnType("int"); b.HasKey("ShortenedUrlId"); @@ -303,19 +396,26 @@ namespace Teknik.Data.Migrations { b.Property("TakedownId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("ActionTaken"); + b.Property("ActionTaken") + .HasColumnType("nvarchar(max)"); - b.Property("DateActionTaken"); + b.Property("DateActionTaken") + .HasColumnType("datetime2"); - b.Property("DateRequested"); + b.Property("DateRequested") + .HasColumnType("datetime2"); - b.Property("Reason"); + b.Property("Reason") + .HasColumnType("nvarchar(max)"); - b.Property("Requester"); + b.Property("Requester") + .HasColumnType("nvarchar(max)"); - b.Property("RequesterContact"); + b.Property("RequesterContact") + .HasColumnType("nvarchar(max)"); b.HasKey("TakedownId"); @@ -326,16 +426,20 @@ namespace Teknik.Data.Migrations { b.Property("TransactionId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); b.Property("Amount") - .HasColumnType("decimal(19, 5)"); + .HasColumnType("decimal(19,5)"); - b.Property("Currency"); + b.Property("Currency") + .HasColumnType("int"); - b.Property("DateSent"); + b.Property("DateSent") + .HasColumnType("datetime2"); - b.Property("Reason"); + b.Property("Reason") + .HasColumnType("nvarchar(max)"); b.HasKey("TransactionId"); @@ -346,42 +450,58 @@ namespace Teknik.Data.Migrations { b.Property("UploadId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("BlockSize"); + b.Property("BlockSize") + .HasColumnType("int"); - b.Property("ContentLength"); + b.Property("ContentLength") + .HasColumnType("bigint"); - b.Property("ContentType"); + b.Property("ContentType") + .HasColumnType("nvarchar(max)"); - b.Property("DateUploaded"); + b.Property("DateUploaded") + .HasColumnType("datetime2"); b.Property("DeleteKey") + .HasColumnType("nvarchar(max)") .HasAnnotation("CaseSensitive", true); - b.Property("Downloads"); + b.Property("Downloads") + .HasColumnType("int"); - b.Property("ExpireDate"); + b.Property("ExpireDate") + .HasColumnType("datetime2"); b.Property("FileName") + .HasColumnType("nvarchar(max)") .HasAnnotation("CaseSensitive", true); b.Property("IV") + .HasColumnType("nvarchar(max)") .HasAnnotation("CaseSensitive", true); b.Property("Key") + .HasColumnType("nvarchar(max)") .HasAnnotation("CaseSensitive", true); - b.Property("KeySize"); + b.Property("KeySize") + .HasColumnType("int"); - b.Property("MaxDownloads"); + b.Property("MaxDownloads") + .HasColumnType("int"); - b.Property("Takedown_TakedownId"); + b.Property("Takedown_TakedownId") + .HasColumnType("int"); b.Property("Url") + .HasColumnType("nvarchar(max)") .HasAnnotation("CaseSensitive", true); - b.Property("UserId"); + b.Property("UserId") + .HasColumnType("int"); b.HasKey("UploadId"); @@ -395,13 +515,16 @@ namespace Teknik.Data.Migrations modelBuilder.Entity("Teknik.Areas.Users.Models.Feature", b => { b.Property("FeatureId") - .ValueGeneratedOnAdd(); + .HasColumnType("nvarchar(450)"); - b.Property("Description"); + b.Property("Description") + .HasColumnType("nvarchar(max)"); - b.Property("Enabled"); + b.Property("Enabled") + .HasColumnType("bit"); - b.Property("Name"); + b.Property("Name") + .HasColumnType("nvarchar(max)"); b.HasKey("FeatureId"); @@ -412,16 +535,21 @@ namespace Teknik.Data.Migrations { b.Property("InviteCodeId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("Active"); + b.Property("Active") + .HasColumnType("bit"); - b.Property("ClaimedUserId"); + b.Property("ClaimedUserId") + .HasColumnType("int"); b.Property("Code") + .HasColumnType("nvarchar(max)") .HasAnnotation("CaseSensitive", true); - b.Property("OwnerId"); + b.Property("OwnerId") + .HasColumnType("int"); b.HasKey("InviteCodeId"); @@ -438,9 +566,11 @@ namespace Teknik.Data.Migrations { b.Property("UserId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("Username"); + b.Property("Username") + .HasColumnType("nvarchar(max)"); b.HasKey("UserId"); @@ -451,11 +581,14 @@ namespace Teknik.Data.Migrations { b.Property("UserFeatureId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("FeatureId"); + b.Property("FeatureId") + .HasColumnType("nvarchar(450)"); - b.Property("UserId"); + b.Property("UserId") + .HasColumnType("int"); b.HasKey("UserFeatureId"); @@ -470,21 +603,29 @@ namespace Teknik.Data.Migrations { b.Property("VaultId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("DateCreated"); + b.Property("DateCreated") + .HasColumnType("datetime2"); - b.Property("DateEdited"); + b.Property("DateEdited") + .HasColumnType("datetime2"); - b.Property("Description"); + b.Property("Description") + .HasColumnType("nvarchar(max)"); - b.Property("Title"); + b.Property("Title") + .HasColumnType("nvarchar(max)"); - b.Property("Url"); + b.Property("Url") + .HasColumnType("nvarchar(max)"); - b.Property("UserId"); + b.Property("UserId") + .HasColumnType("int"); - b.Property("Views"); + b.Property("Views") + .HasColumnType("int"); b.HasKey("VaultId"); @@ -497,20 +638,27 @@ namespace Teknik.Data.Migrations { b.Property("VaultItemId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); - b.Property("DateAdded"); + b.Property("DateAdded") + .HasColumnType("datetime2"); - b.Property("Description"); + b.Property("Description") + .HasColumnType("nvarchar(max)"); b.Property("Discriminator") - .IsRequired(); + .IsRequired() + .HasColumnType("nvarchar(max)"); - b.Property("Index"); + b.Property("Index") + .HasColumnType("int"); - b.Property("Title"); + b.Property("Title") + .HasColumnType("nvarchar(max)"); - b.Property("VaultId"); + b.Property("VaultId") + .HasColumnType("int"); b.HasKey("VaultItemId"); @@ -525,7 +673,8 @@ namespace Teknik.Data.Migrations { b.HasBaseType("Teknik.Areas.Vault.Models.VaultItem"); - b.Property("PasteId"); + b.Property("PasteId") + .HasColumnType("int"); b.HasIndex("PasteId"); @@ -536,19 +685,34 @@ namespace Teknik.Data.Migrations { b.HasBaseType("Teknik.Areas.Vault.Models.VaultItem"); - b.Property("UploadId"); + b.Property("UploadId") + .HasColumnType("int"); b.HasIndex("UploadId"); b.HasDiscriminator().HasValue("UploadVaultItem"); }); + modelBuilder.Entity("Teknik.Areas.Billing.Models.Customer", b => + { + b.HasOne("Teknik.Areas.Users.Models.User", "User") + .WithOne("BillingCustomer") + .HasForeignKey("Teknik.Areas.Billing.Models.Customer", "UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b => { b.HasOne("Teknik.Areas.Users.Models.User", "User") .WithMany() .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); }); modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b => @@ -556,7 +720,10 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Blog.Models.Blog", "Blog") .WithMany("BlogPosts") .HasForeignKey("BlogId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Blog"); }); modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostComment", b => @@ -564,11 +731,16 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Blog.Models.BlogPost", "BlogPost") .WithMany("Comments") .HasForeignKey("BlogPostId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("Teknik.Areas.Users.Models.User", "User") .WithMany() .HasForeignKey("UserId"); + + b.Navigation("BlogPost"); + + b.Navigation("User"); }); modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostTag", b => @@ -576,7 +748,10 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Blog.Models.BlogPost", "BlogPost") .WithMany("Tags") .HasForeignKey("BlogPostId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("BlogPost"); }); modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b => @@ -584,6 +759,8 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Users.Models.User", "User") .WithMany("Pastes") .HasForeignKey("UserId"); + + b.Navigation("User"); }); modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastComment", b => @@ -591,12 +768,18 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") .WithMany("Comments") .HasForeignKey("PodcastId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); b.HasOne("Teknik.Areas.Users.Models.User", "User") .WithMany() .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Podcast"); + + b.Navigation("User"); }); modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastFile", b => @@ -604,7 +787,10 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") .WithMany("Files") .HasForeignKey("PodcastId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Podcast"); }); modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastTag", b => @@ -612,7 +798,10 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Podcast.Models.Podcast", "Podcast") .WithMany("Tags") .HasForeignKey("PodcastId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Podcast"); }); modelBuilder.Entity("Teknik.Areas.Shortener.Models.ShortenedUrl", b => @@ -620,17 +809,21 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Users.Models.User", "User") .WithMany("ShortenedUrls") .HasForeignKey("UserId"); + + b.Navigation("User"); }); modelBuilder.Entity("Teknik.Areas.Upload.Models.Upload", b => { - b.HasOne("Teknik.Areas.Stats.Models.Takedown") + b.HasOne("Teknik.Areas.Stats.Models.Takedown", null) .WithMany("Attachments") .HasForeignKey("Takedown_TakedownId"); b.HasOne("Teknik.Areas.Users.Models.User", "User") .WithMany("Uploads") .HasForeignKey("UserId"); + + b.Navigation("User"); }); modelBuilder.Entity("Teknik.Areas.Users.Models.InviteCode", b => @@ -642,6 +835,10 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Users.Models.User", "Owner") .WithMany("OwnedInviteCodes") .HasForeignKey("OwnerId"); + + b.Navigation("ClaimedUser"); + + b.Navigation("Owner"); }); modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => @@ -650,76 +847,92 @@ namespace Teknik.Data.Migrations { b1.Property("UserId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); b1.Property("Description") + .HasColumnType("nvarchar(max)") .HasColumnName("Description"); b1.Property("Title") + .HasColumnType("nvarchar(max)") .HasColumnName("Title"); b1.HasKey("UserId"); b1.ToTable("Users"); - b1.HasOne("Teknik.Areas.Users.Models.User") - .WithOne("BlogSettings") - .HasForeignKey("Teknik.Areas.Users.Models.BlogSettings", "UserId") - .OnDelete(DeleteBehavior.Cascade); + b1.WithOwner() + .HasForeignKey("UserId"); }); b.OwnsOne("Teknik.Areas.Users.Models.UploadSettings", "UploadSettings", b1 => { b1.Property("UserId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); b1.Property("Encrypt") + .HasColumnType("bit") .HasColumnName("Encrypt"); b1.Property("ExpirationLength") + .HasColumnType("int") .HasColumnName("ExpirationLength"); b1.Property("ExpirationUnit") + .HasColumnType("int") .HasColumnName("ExpirationUnit"); + b1.Property("MaxUploadFileSize") + .HasColumnType("bigint") + .HasColumnName("MaxUploadFileSize"); + b1.Property("MaxUploadStorage") + .HasColumnType("bigint") .HasColumnName("MaxUploadStorage"); b1.HasKey("UserId"); b1.ToTable("Users"); - b1.HasOne("Teknik.Areas.Users.Models.User") - .WithOne("UploadSettings") - .HasForeignKey("Teknik.Areas.Users.Models.UploadSettings", "UserId") - .OnDelete(DeleteBehavior.Cascade); + b1.WithOwner() + .HasForeignKey("UserId"); }); b.OwnsOne("Teknik.Areas.Users.Models.UserSettings", "UserSettings", b1 => { b1.Property("UserId") .ValueGeneratedOnAdd() + .HasColumnType("int") .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); b1.Property("About") + .HasColumnType("nvarchar(max)") .HasColumnName("About"); b1.Property("Quote") + .HasColumnType("nvarchar(max)") .HasColumnName("Quote"); b1.Property("Website") + .HasColumnType("nvarchar(max)") .HasColumnName("Website"); b1.HasKey("UserId"); b1.ToTable("Users"); - b1.HasOne("Teknik.Areas.Users.Models.User") - .WithOne("UserSettings") - .HasForeignKey("Teknik.Areas.Users.Models.UserSettings", "UserId") - .OnDelete(DeleteBehavior.Cascade); + b1.WithOwner() + .HasForeignKey("UserId"); }); + + b.Navigation("BlogSettings"); + + b.Navigation("UploadSettings"); + + b.Navigation("UserSettings"); }); modelBuilder.Entity("Teknik.Areas.Users.Models.UserFeature", b => @@ -731,7 +944,12 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Users.Models.User", "User") .WithMany("Features") .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Feature"); + + b.Navigation("User"); }); modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => @@ -739,6 +957,8 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Users.Models.User", "User") .WithMany("Vaults") .HasForeignKey("UserId"); + + b.Navigation("User"); }); modelBuilder.Entity("Teknik.Areas.Vault.Models.VaultItem", b => @@ -746,7 +966,10 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Vault.Models.Vault", "Vault") .WithMany("VaultItems") .HasForeignKey("VaultId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Vault"); }); modelBuilder.Entity("Teknik.Areas.Vault.Models.PasteVaultItem", b => @@ -754,7 +977,10 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Paste.Models.Paste", "Paste") .WithMany("PasteVaultItems") .HasForeignKey("PasteId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Paste"); }); modelBuilder.Entity("Teknik.Areas.Vault.Models.UploadVaultItem", b => @@ -762,7 +988,70 @@ namespace Teknik.Data.Migrations b.HasOne("Teknik.Areas.Upload.Models.Upload", "Upload") .WithMany("UploadVaultItems") .HasForeignKey("UploadId") - .OnDelete(DeleteBehavior.Cascade); + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Upload"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b => + { + b.Navigation("BlogPosts"); + }); + + modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b => + { + b.Navigation("Comments"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b => + { + b.Navigation("PasteVaultItems"); + }); + + modelBuilder.Entity("Teknik.Areas.Podcast.Models.Podcast", b => + { + b.Navigation("Comments"); + + b.Navigation("Files"); + + b.Navigation("Tags"); + }); + + modelBuilder.Entity("Teknik.Areas.Stats.Models.Takedown", b => + { + b.Navigation("Attachments"); + }); + + modelBuilder.Entity("Teknik.Areas.Upload.Models.Upload", b => + { + b.Navigation("UploadVaultItems"); + }); + + modelBuilder.Entity("Teknik.Areas.Users.Models.User", b => + { + b.Navigation("BillingCustomer"); + + b.Navigation("ClaimedInviteCode"); + + b.Navigation("Features"); + + b.Navigation("OwnedInviteCodes"); + + b.Navigation("Pastes"); + + b.Navigation("ShortenedUrls"); + + b.Navigation("Uploads"); + + b.Navigation("Vaults"); + }); + + modelBuilder.Entity("Teknik.Areas.Vault.Models.Vault", b => + { + b.Navigation("VaultItems"); }); #pragma warning restore 612, 618 } diff --git a/Teknik/Data/TeknikEntities.cs b/Teknik/Data/TeknikEntities.cs index 306d9dc..359a278 100644 --- a/Teknik/Data/TeknikEntities.cs +++ b/Teknik/Data/TeknikEntities.cs @@ -12,6 +12,7 @@ using Teknik.Areas.Vault.Models; using Microsoft.EntityFrameworkCore; using Teknik.Models; using System.Configuration; +using Teknik.Areas.Billing.Models; namespace Teknik.Data { @@ -25,6 +26,8 @@ namespace Teknik.Data public DbSet UserSettings { get; set; } public DbSet BlogSettings { get; set; } public DbSet UploadSettings { get; set; } + // Billing Customers + public DbSet Customers { get; set; } // Features public DbSet Features { get; set; } // Blogs @@ -72,6 +75,7 @@ namespace Teknik.Data modelBuilder.Entity().OwnsOne(u => u.UserSettings, us => us.ToTable("Users")); modelBuilder.Entity().OwnsOne(u => u.BlogSettings, bs => bs.ToTable("Users")); modelBuilder.Entity().OwnsOne(u => u.UploadSettings, us => us.ToTable("Users")); + modelBuilder.Entity().HasOne(u => u.BillingCustomer).WithOne(i => i.User); modelBuilder.Entity().HasMany(u => u.Features).WithOne(u => u.User); modelBuilder.Entity().HasMany(u => u.Uploads).WithOne(u => u.User); modelBuilder.Entity().HasMany(u => u.Pastes).WithOne(u => u.User); @@ -86,6 +90,9 @@ namespace Teknik.Data //modelBuilder.Entity().HasOne(t => t.ClaimedUser).WithOne(u => u.ClaimedInviteCode).HasPrincipalKey("ClaimedUserId").HasForeignKey("ClaimedUser_UserId"); // Legacy??? //modelBuilder.Entity().HasOne(t => t.Owner).WithMany(u => u.OwnedInviteCodes).HasPrincipalKey("ClaimedUserId").HasForeignKey("Owner_UserId"); // Legacy??? + // Billing Customers + modelBuilder.Entity().HasOne(u => u.User); + // Features modelBuilder.Entity().HasOne(f => f.Feature); modelBuilder.Entity().HasOne(f => f.User); @@ -119,6 +126,8 @@ namespace Teknik.Data //modelBuilder.Entity().ToTable("Users"); //modelBuilder.Entity().ToTable("Users"); //modelBuilder.Entity().ToTable("Users"); + // Billing Customers + modelBuilder.Entity().ToTable("Customers"); // Features modelBuilder.Entity().ToTable("Features"); // Blogs diff --git a/Teknik/Images/logo-blue-1920.png b/Teknik/Images/logo-blue-1920.png new file mode 100644 index 0000000..b0b60d1 Binary files /dev/null and b/Teknik/Images/logo-blue-1920.png differ diff --git a/Teknik/Images/logo-blue-2400.png b/Teknik/Images/logo-blue-2400.png new file mode 100644 index 0000000..20fa3ed Binary files /dev/null and b/Teknik/Images/logo-blue-2400.png differ diff --git a/Teknik/Scripts/Billing/Subscriptions.js b/Teknik/Scripts/Billing/Subscriptions.js new file mode 100644 index 0000000..9e7ca4e --- /dev/null +++ b/Teknik/Scripts/Billing/Subscriptions.js @@ -0,0 +1,49 @@ + +$(document).ready(function () { + $('#subscribeModal').on('shown.bs.modal', function () { + $("#subscribeStatus").css('display', 'none', 'important'); + $("#subscribeStatus").html(''); + }); + + $("#subscribeSubmit").click(function () { + // Reset register status messages + $("#subscribeStatus").css('display', 'none', 'important'); + $("#subscribeStatus").html(''); + + // Disable the register button + disableButton('#subscribeSubmit', 'Subscribing...'); + + var form = $('#registrationForm'); + $.ajax({ + type: "POST", + url: form.attr('action'), + data: form.serialize(), + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + xhrFields: { + withCredentials: true + }, + success: function (html) { + if (html.success) { + $('#registerModal').modal('hide'); + + $("#top_msg").css('display', 'inline', 'important'); + $("#top_msg").html('
Registration Successful. Redirecting...
'); + + window.location = html.redirectUrl; + } + else { + $("#registerStatus").css('display', 'inline', 'important'); + $("#registerStatus").html('
' + parseErrorMessage(html) + '
'); + } + }, + error: function (response) { + $("#registerStatus").css('display', 'inline', 'important'); + $("#registerStatus").html('
' + parseErrorMessage(response.responseText) + '
'); + } + }).always(function () { + enableButton('#registerSubmit', 'Sign Up'); + }); + return false; + }); + +}); \ No newline at end of file diff --git a/Utilities/Cryptography/Crypto.cs b/Utilities/Cryptography/Crypto.cs new file mode 100644 index 0000000..ad70cbc --- /dev/null +++ b/Utilities/Cryptography/Crypto.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Teknik.Utilities.Cryptography +{ + public static class Crypto + { + public static string GenerateKey(int keySize) + { + return StringHelper.RandomString(keySize / 8); + } + + public static string GenerateIV(int ivSize) + { + return StringHelper.RandomString(ivSize / 16); + } + + public static string HashPassword(string key, string password) + { + return SHA384.Hash(key, password).ToHex(); + } + } +}