Add support page, admin features, and auth hardening.
Rename public affiliate route to /support with localized nav labels, and include affiliate/social/wallpaper admin, 2FA login, and related site updates. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
202dc16a52
commit
fc45c35dbe
@@ -0,0 +1,41 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Web;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
public class AdminAffiliateLinkVM
|
||||
{
|
||||
public int id { get; set; }
|
||||
|
||||
[Display(Name = "Title (EN)")]
|
||||
[Required(ErrorMessage = "Title is required.")]
|
||||
[StringLength(200)]
|
||||
public string TitleEN { get; set; }
|
||||
|
||||
[Display(Name = "Description (EN)")]
|
||||
[StringLength(500)]
|
||||
public string DescEN { get; set; }
|
||||
|
||||
[Display(Name = "Title (TR)")]
|
||||
[StringLength(200)]
|
||||
public string TitleTR { get; set; }
|
||||
|
||||
[Display(Name = "Description (TR)")]
|
||||
[StringLength(500)]
|
||||
public string DescTR { get; set; }
|
||||
|
||||
[Display(Name = "URL")]
|
||||
[Required(ErrorMessage = "URL is required.")]
|
||||
[StringLength(1000)]
|
||||
public string url { get; set; }
|
||||
|
||||
[Display(Name = "Sort Order")]
|
||||
public int sort_order { get; set; }
|
||||
|
||||
// Path of the already-uploaded logo (used when editing)
|
||||
public string existingImage { get; set; }
|
||||
|
||||
// New logo file (required on create, optional on edit to replace it)
|
||||
public HttpPostedFileBase logoFile { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,16 @@
|
||||
namespace atakanozbancom.Models.classes
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
public class AdminMyProjectsEditVM
|
||||
{
|
||||
public int id { get; set; }
|
||||
|
||||
public string image { get; set; }
|
||||
public string iframe { get; set; }
|
||||
public string title { get; set; }
|
||||
public string description { get; set; }
|
||||
public string TitleEN { get; set; }
|
||||
public string DescEN { get; set; }
|
||||
public string TitleTR { get; set; }
|
||||
public string TitleEN { get; set; }
|
||||
public string DescTR { get; set; }
|
||||
public string DescEN { get; set; }
|
||||
|
||||
// NEW:
|
||||
public List<projectmedia> medias { get; set; } = new List<projectmedia>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
public class AdminSocialIconVM
|
||||
{
|
||||
public int id { get; set; }
|
||||
|
||||
[Display(Name = "Icon (Font Awesome class)")]
|
||||
[Required(ErrorMessage = "Icon class is required.")]
|
||||
[StringLength(100)]
|
||||
public string icon { get; set; }
|
||||
|
||||
[Display(Name = "URL")]
|
||||
[Required(ErrorMessage = "URL is required.")]
|
||||
[StringLength(500)]
|
||||
public string url { get; set; }
|
||||
|
||||
[Display(Name = "Sort Order")]
|
||||
public int sort_order { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
public class AffiliateLinkVM
|
||||
{
|
||||
public int id { get; set; }
|
||||
public string title { get; set; }
|
||||
public string description { get; set; }
|
||||
public string url { get; set; }
|
||||
public string image { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,11 @@ namespace atakanozbancom.Models.classes
|
||||
public DbSet<admin> admins { get; set; }
|
||||
public DbSet<myprojects> MyProjects { get; set; }
|
||||
public DbSet<myprojectstranslation> myprojectstranslations { get; set; }
|
||||
public DbSet<projectmedia> projectmedias { get; set; }
|
||||
public DbSet<projectmedia> projectmedia { get; set; }
|
||||
public DbSet<affiliatelink> AffiliateLinks { get; set; }
|
||||
public DbSet<affiliatelinktranslation> affiliatelinktranslations { get; set; }
|
||||
public DbSet<socialicon> SocialIcons { get; set; }
|
||||
public DbSet<sitesetting> SiteSettings { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders user-entered project/affiliate descriptions as safe HTML.
|
||||
/// The whole input is HTML-encoded first (so no raw tags/scripts can ever
|
||||
/// survive), then a very small Markdown-style link syntax is turned into
|
||||
/// real anchor tags: [Link text](https://example.com)
|
||||
/// Only http:// and https:// URLs are accepted; anything else is left as
|
||||
/// plain (encoded) text.
|
||||
/// </summary>
|
||||
public static class DescriptionFormatter
|
||||
{
|
||||
// [label](https://url) - label can't contain "]", url must be http(s) and can't contain whitespace or ")"
|
||||
private static readonly Regex LinkPattern = new Regex(
|
||||
@"\[([^\]\r\n]{1,200})\]\((https?://[^\s)]{1,2000})\)",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
public static string ToSafeHtml(string raw)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
return string.Empty;
|
||||
|
||||
// 1) Neutralize everything (script/tags/attributes) up front.
|
||||
var encoded = HttpUtility.HtmlEncode(raw);
|
||||
|
||||
// 2) Re-introduce ONLY safe <a> links for the [text](url) syntax.
|
||||
// Both groups are already HTML-encoded, so this can't break out
|
||||
// of the href attribute or inject new tags/attributes.
|
||||
var withLinks = LinkPattern.Replace(encoded, m =>
|
||||
{
|
||||
var label = m.Groups[1].Value;
|
||||
var url = m.Groups[2].Value;
|
||||
return $"<a href=\"{url}\" class=\"text-secondary text-decoration-none\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">{label}</a>";
|
||||
});
|
||||
|
||||
// 3) Preserve line breaks the admin typed in the textarea.
|
||||
var withBreaks = withLinks
|
||||
.Replace("\r\n", "\n")
|
||||
.Replace("\n", "<br />");
|
||||
|
||||
return withBreaks;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,27 @@
|
||||
namespace atakanozbancom.Models.classes
|
||||
using System.Collections.Generic;
|
||||
using System.Web.Mvc;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
public class MyProjectsVM
|
||||
{
|
||||
public int id { get; set; }
|
||||
public string title { get; set; }
|
||||
[AllowHtml]
|
||||
public string description { get; set; }
|
||||
|
||||
// legacy (şimdilik kalsın)
|
||||
public string image { get; set; }
|
||||
public string iframe { get; set; }
|
||||
|
||||
// NEW
|
||||
public List<ProjectMediaVM> medias { get; set; } = new List<ProjectMediaVM>();
|
||||
}
|
||||
}
|
||||
|
||||
public class ProjectMediaVM
|
||||
{
|
||||
public string type { get; set; }
|
||||
public string url { get; set; }
|
||||
public int order { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
public static class PasswordHasher
|
||||
{
|
||||
private const string Prefix = "pbkdf2";
|
||||
private const int DefaultIterations = 100000;
|
||||
private const int SaltSize = 16;
|
||||
private const int HashSize = 32;
|
||||
|
||||
public static string Hash(string password)
|
||||
{
|
||||
if (password == null)
|
||||
throw new ArgumentNullException("password");
|
||||
|
||||
var salt = new byte[SaltSize];
|
||||
using (var rng = RandomNumberGenerator.Create())
|
||||
{
|
||||
rng.GetBytes(salt);
|
||||
}
|
||||
|
||||
var hash = Pbkdf2(password, salt, DefaultIterations, HashSize);
|
||||
return string.Format(
|
||||
"{0}${1}${2}${3}",
|
||||
Prefix,
|
||||
DefaultIterations,
|
||||
Convert.ToBase64String(salt),
|
||||
Convert.ToBase64String(hash));
|
||||
}
|
||||
|
||||
public static bool IsHashed(string stored)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(stored)
|
||||
&& stored.StartsWith(Prefix + "$", StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
public static bool Verify(string password, string stored)
|
||||
{
|
||||
if (string.IsNullOrEmpty(password) || string.IsNullOrEmpty(stored))
|
||||
return false;
|
||||
|
||||
if (!IsHashed(stored))
|
||||
return FixedTimeEquals(password, stored);
|
||||
|
||||
var parts = stored.Split('$');
|
||||
if (parts.Length != 4 || !string.Equals(parts[0], Prefix, StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
int iterations;
|
||||
if (!int.TryParse(parts[1], out iterations) || iterations < 1000)
|
||||
return false;
|
||||
|
||||
byte[] salt;
|
||||
byte[] expected;
|
||||
try
|
||||
{
|
||||
salt = Convert.FromBase64String(parts[2]);
|
||||
expected = Convert.FromBase64String(parts[3]);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var actual = Pbkdf2(password, salt, iterations, expected.Length);
|
||||
return FixedTimeEquals(actual, expected);
|
||||
}
|
||||
|
||||
private static byte[] Pbkdf2(string password, byte[] salt, int iterations, int length)
|
||||
{
|
||||
using (var derive = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256))
|
||||
{
|
||||
return derive.GetBytes(length);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool FixedTimeEquals(byte[] a, byte[] b)
|
||||
{
|
||||
if (a == null || b == null || a.Length != b.Length)
|
||||
return false;
|
||||
|
||||
var diff = 0;
|
||||
for (var i = 0; i < a.Length; i++)
|
||||
diff |= a[i] ^ b[i];
|
||||
|
||||
return diff == 0;
|
||||
}
|
||||
|
||||
private static bool FixedTimeEquals(string a, string b)
|
||||
{
|
||||
if (a == null || b == null || a.Length != b.Length)
|
||||
return false;
|
||||
|
||||
var diff = 0;
|
||||
for (var i = 0; i < a.Length; i++)
|
||||
diff |= a[i] ^ b[i];
|
||||
|
||||
return diff == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
public class ProjectMediaVM
|
||||
{
|
||||
public int id { get; set; }
|
||||
|
||||
public int projectid { get; set; }
|
||||
|
||||
public string mediatype { get; set; } // "image" or "iframe"
|
||||
|
||||
public string mediaurl { get; set; }
|
||||
|
||||
public int sortorder { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Web.Security;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
public static class SecretProtector
|
||||
{
|
||||
private static readonly string[] Purposes = { "atakanozbancom.TotpSecret.v1" };
|
||||
|
||||
public static string Protect(string plaintext)
|
||||
{
|
||||
if (string.IsNullOrEmpty(plaintext))
|
||||
return plaintext;
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(plaintext);
|
||||
var protectedBytes = MachineKey.Protect(bytes, Purposes);
|
||||
return Convert.ToBase64String(protectedBytes);
|
||||
}
|
||||
|
||||
public static string Unprotect(string protectedValue)
|
||||
{
|
||||
if (string.IsNullOrEmpty(protectedValue))
|
||||
return protectedValue;
|
||||
|
||||
try
|
||||
{
|
||||
var protectedBytes = Convert.FromBase64String(protectedValue);
|
||||
var bytes = MachineKey.Unprotect(protectedBytes, Purposes);
|
||||
return bytes == null ? null : Encoding.UTF8.GetString(bytes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool LooksProtected(string value)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var bytes = Convert.FromBase64String(value);
|
||||
return bytes.Length > 16;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
public static class TotpHelper
|
||||
{
|
||||
private const string Base32Alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
public static string GenerateSecret(int byteLength = 20)
|
||||
{
|
||||
var bytes = new byte[byteLength];
|
||||
using (var rng = RandomNumberGenerator.Create())
|
||||
{
|
||||
rng.GetBytes(bytes);
|
||||
}
|
||||
return ToBase32(bytes);
|
||||
}
|
||||
|
||||
public static bool VerifyCode(string base32Secret, string code, int window = 1)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(base32Secret) || string.IsNullOrWhiteSpace(code))
|
||||
return false;
|
||||
|
||||
code = code.Trim().Replace(" ", "");
|
||||
if (code.Length != 6)
|
||||
return false;
|
||||
|
||||
long timestep;
|
||||
try
|
||||
{
|
||||
timestep = GetCurrentTimeStep();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = -window; i <= window; i++)
|
||||
{
|
||||
var expected = ComputeTotp(base32Secret, timestep + i);
|
||||
if (FixedTimeEquals(expected, code))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static string BuildOtpAuthUri(string issuer, string accountName, string base32Secret)
|
||||
{
|
||||
var label = HttpUtility.UrlEncode(issuer + ":" + accountName);
|
||||
var issuerParam = HttpUtility.UrlEncode(issuer);
|
||||
return string.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"otpauth://totp/{0}?secret={1}&issuer={2}&digits=6&period=30",
|
||||
label,
|
||||
base32Secret,
|
||||
issuerParam);
|
||||
}
|
||||
|
||||
private static long GetCurrentTimeStep()
|
||||
{
|
||||
return (long)Math.Floor((DateTime.UtcNow - UnixEpoch).TotalSeconds / 30.0);
|
||||
}
|
||||
|
||||
private static string ComputeTotp(string base32Secret, long timestep)
|
||||
{
|
||||
var key = FromBase32(base32Secret);
|
||||
var counter = BitConverter.GetBytes(timestep);
|
||||
if (BitConverter.IsLittleEndian)
|
||||
Array.Reverse(counter);
|
||||
|
||||
byte[] hash;
|
||||
using (var hmac = new HMACSHA1(key))
|
||||
{
|
||||
hash = hmac.ComputeHash(counter);
|
||||
}
|
||||
|
||||
var offset = hash[hash.Length - 1] & 0x0F;
|
||||
var binary =
|
||||
((hash[offset] & 0x7F) << 24)
|
||||
| ((hash[offset + 1] & 0xFF) << 16)
|
||||
| ((hash[offset + 2] & 0xFF) << 8)
|
||||
| (hash[offset + 3] & 0xFF);
|
||||
|
||||
var otp = binary % 1000000;
|
||||
return otp.ToString("D6", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static string ToBase32(byte[] data)
|
||||
{
|
||||
if (data == null || data.Length == 0)
|
||||
return string.Empty;
|
||||
|
||||
var sb = new StringBuilder((data.Length * 8 + 4) / 5);
|
||||
int buffer = data[0];
|
||||
var next = 1;
|
||||
var bitsLeft = 8;
|
||||
|
||||
while (bitsLeft > 0 || next < data.Length)
|
||||
{
|
||||
if (bitsLeft < 5)
|
||||
{
|
||||
if (next < data.Length)
|
||||
{
|
||||
buffer <<= 8;
|
||||
buffer |= data[next++] & 0xFF;
|
||||
bitsLeft += 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
var pad = 5 - bitsLeft;
|
||||
buffer <<= pad;
|
||||
bitsLeft += pad;
|
||||
}
|
||||
}
|
||||
|
||||
var index = (buffer >> (bitsLeft - 5)) & 0x1F;
|
||||
bitsLeft -= 5;
|
||||
sb.Append(Base32Alphabet[index]);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static byte[] FromBase32(string input)
|
||||
{
|
||||
var cleaned = input.Trim().Replace(" ", "").Replace("=", "").ToUpperInvariant();
|
||||
var output = new byte[cleaned.Length * 5 / 8];
|
||||
var bitBuffer = 0;
|
||||
var bitsLeft = 0;
|
||||
var index = 0;
|
||||
|
||||
foreach (var c in cleaned)
|
||||
{
|
||||
var val = Base32Alphabet.IndexOf(c);
|
||||
if (val < 0)
|
||||
throw new FormatException("Invalid Base32 character.");
|
||||
|
||||
bitBuffer = (bitBuffer << 5) | val;
|
||||
bitsLeft += 5;
|
||||
|
||||
if (bitsLeft >= 8)
|
||||
{
|
||||
output[index++] = (byte)((bitBuffer >> (bitsLeft - 8)) & 0xFF);
|
||||
bitsLeft -= 8;
|
||||
}
|
||||
}
|
||||
|
||||
if (index != output.Length)
|
||||
{
|
||||
var trimmed = new byte[index];
|
||||
Array.Copy(output, trimmed, index);
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private static bool FixedTimeEquals(string a, string b)
|
||||
{
|
||||
if (a == null || b == null || a.Length != b.Length)
|
||||
return false;
|
||||
|
||||
var diff = 0;
|
||||
for (var i = 0; i < a.Length; i++)
|
||||
diff |= a[i] ^ b[i];
|
||||
|
||||
return diff == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
-2
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
@@ -9,6 +11,26 @@ namespace atakanozbancom.Models.classes
|
||||
public int id { get; set; }
|
||||
public string username { get; set; }
|
||||
public string password { get; set; }
|
||||
|
||||
[Column("two_factor_enabled")]
|
||||
public bool two_factor_enabled { get; set; }
|
||||
|
||||
[Column("two_factor_secret")]
|
||||
[StringLength(512)]
|
||||
public string two_factor_secret { get; set; }
|
||||
|
||||
[Column("login_failed_count")]
|
||||
public int login_failed_count { get; set; }
|
||||
|
||||
[Column("login_lock_until")]
|
||||
public DateTime? login_lock_until { get; set; }
|
||||
|
||||
[Column("totp_failed_count")]
|
||||
public int totp_failed_count { get; set; }
|
||||
|
||||
[Column("totp_lock_until")]
|
||||
public DateTime? totp_lock_until { get; set; }
|
||||
|
||||
public List<myprojects> MyProjects { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
[Table("affiliatelinks")]
|
||||
public class affiliatelink
|
||||
{
|
||||
[Key]
|
||||
public int id { get; set; }
|
||||
|
||||
[Required, StringLength(500)]
|
||||
[Column("image")]
|
||||
public string image { get; set; }
|
||||
|
||||
[Required, StringLength(200)]
|
||||
[Column("title")]
|
||||
public string title { get; set; }
|
||||
|
||||
[StringLength(500)]
|
||||
[Column("description")]
|
||||
public string description { get; set; }
|
||||
|
||||
[Required, StringLength(1000)]
|
||||
[Column("url")]
|
||||
public string url { get; set; }
|
||||
|
||||
[Column("sortorder")]
|
||||
public int sort_order { get; set; } = 0;
|
||||
|
||||
public virtual ICollection<affiliatelinktranslation> Translations { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
[Table("affiliatelinktranslations")]
|
||||
public class affiliatelinktranslation
|
||||
{
|
||||
public int id { get; set; }
|
||||
|
||||
[Required]
|
||||
public int affiliatelink_id { get; set; }
|
||||
|
||||
[Required, StringLength(10)]
|
||||
public string culture { get; set; }
|
||||
|
||||
[StringLength(255)]
|
||||
public string title { get; set; }
|
||||
|
||||
public string description { get; set; }
|
||||
|
||||
[ForeignKey("affiliatelink_id")]
|
||||
public virtual affiliatelink AffiliateLink { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,6 @@ namespace atakanozbancom.Models.classes
|
||||
public string title { get; set; }
|
||||
[AllowHtml]
|
||||
public string description { get; set; }
|
||||
public string TitleEN { get; set; }
|
||||
public string DescEN { get; set; }
|
||||
public string TitleTR { get; set; }
|
||||
public string DescTR { get; set; }
|
||||
public object admins { get; internal set; }
|
||||
public virtual ICollection<myprojectstranslation> Translations { get; set; }
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
public enum MediaType
|
||||
{
|
||||
Image = 1,
|
||||
Iframe = 2,
|
||||
// Video = 0,
|
||||
//Document = 3
|
||||
}
|
||||
|
||||
[Table("projectmedia")]
|
||||
public class projectmedia
|
||||
{
|
||||
[Key]
|
||||
public int id { get; set; }
|
||||
|
||||
[Column("projectid")]
|
||||
public int project_id { get; set; }
|
||||
|
||||
[ForeignKey(nameof(project_id))]
|
||||
public virtual myprojects Project { get; set; }
|
||||
|
||||
[Column("mediatype")]
|
||||
public MediaType media_type { get; set; }
|
||||
|
||||
[Required, StringLength(1000)]
|
||||
[Column("mediaurl")]
|
||||
public string url { get; set; }
|
||||
|
||||
[Column("sortorder")]
|
||||
public int sort_order { get; set; } = 0;
|
||||
|
||||
// DB'de bu kolon yok → modelden kaldırıyoruz
|
||||
// public string title { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
[Table("sitesettings")]
|
||||
public class sitesetting
|
||||
{
|
||||
[Key]
|
||||
public int id { get; set; }
|
||||
|
||||
// Public URL of the current wallpaper. Null/empty falls back to the bundled default.
|
||||
[StringLength(500)]
|
||||
[Column("wallpaper")]
|
||||
public string wallpaper { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace atakanozbancom.Models.classes
|
||||
{
|
||||
[Table("socialicons")]
|
||||
public class socialicon
|
||||
{
|
||||
[Key]
|
||||
public int id { get; set; }
|
||||
|
||||
// Font Awesome class(es), e.g. "fa-brands fa-youtube"
|
||||
[Required, StringLength(100)]
|
||||
[Column("icon")]
|
||||
public string icon { get; set; }
|
||||
|
||||
[Required, StringLength(500)]
|
||||
[Column("url")]
|
||||
public string url { get; set; }
|
||||
|
||||
[Column("sortorder")]
|
||||
public int sort_order { get; set; } = 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user